Skip to content

Commit cfdd67a

Browse files
committed
mirror react/reactDOM render funcs
1 parent e622858 commit cfdd67a

File tree

6 files changed

+95
-67
lines changed

6 files changed

+95
-67
lines changed

docs/source/examples/super_simple_chart.js

+4-10
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,12 @@
1-
import {
2-
h,
3-
Component,
4-
render as preactRender,
5-
} from "https://unpkg.com/preact?module";
1+
import { h, Component, render } from "https://unpkg.com/preact?module";
62
import htm from "https://unpkg.com/htm?module";
73

84
const html = htm.bind(h);
95

10-
export function render(element, component, props) {
11-
preactRender(html`<${component} ...${props} />`, element);
12-
}
6+
export { h as createElement, render as renderElement };
137

14-
export function unmount(element) {
15-
preactRender(null, element);
8+
export function unmountElement(container) {
9+
preactRender(null, container);
1610
}
1711

1812
export function SuperSimpleChart(props) {

docs/source/faq.rst

+4-10
Original file line numberDiff line numberDiff line change
@@ -48,22 +48,16 @@ Yes, but with some restrictions:
4848

4949
1. The Javascript in question must be distributed as an ECMAScript Module
5050
(`ESM <https://hacks.mozilla.org/2018/03/es-modules-a-cartoon-deep-dive/>`__)
51-
2. The module must export the following functions:
52-
53-
- ``render(element: HTMLElement, component: any, props: Object) => void``
54-
- ``unmount(element: HTMLElement) => void``
55-
56-
51+
2. The module must export the :ref:`required interface <Custom Javascript Components>`.
5752

5853
These restrictions apply because the Javascript from the CDN must be able to run
59-
natively in the browser, the module must be able to run in isolation from the main
60-
application, and the server must tell the client to use the exported ``mount()``
61-
function.
54+
natively in the browser and the module must be able to run in isolation from the main
55+
application.
6256

6357

6458
What props can I pass to Javascript components?
6559
-----------------------------------------------
6660

6761
You can only pass JSON serializable props to components implemented in Javascript. It is
68-
possible to implement a :ref:`Custom Javascript Component <Custom Javascript Components>`
62+
possible to create a :ref:`Custom Javascript Component <Custom Javascript Components>`
6963
which undestands how to deserialise JSON data into native Javascript objects though.

docs/source/javascript-components.rst

+49-15
Original file line numberDiff line numberDiff line change
@@ -30,28 +30,62 @@ Custom Javascript Components
3030
For projects that will be shared with others we recommend bundling your Javascript with
3131
`rollup <https://rollupjs.org/guide/en/>`__ or `webpack <https://webpack.js.org/>`__
3232
into a
33-
`web module <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules>`__
34-
using IDOM's
33+
`web module <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules>`__.
34+
IDOM also provides a
3535
`template repository <https://github.com/idom-team/idom-react-component-cookiecutter>`__
36-
as a blueprint to build a React component. Once you've done this, you can distribute
37-
bundled javascript in your Python package and integrate it into IDOM by defining
38-
:class:`~idom.client.module.Module` objects that load them from source:
36+
that can be used as a blueprint to build a library of React components.
37+
38+
The core benefit of loading Javascript in this way is that users of your code won't need
39+
to have NPM_ installed. Rather, they can use ``pip`` to install your Python package
40+
without any other build steps because the bundled Javascript you distributed with it
41+
will be symlinked into the IDOM client at runtime.
42+
43+
To work as intended, the Javascript bundle must export the following named functions:
44+
45+
.. code-block:: typescript
46+
47+
type createElement = (component: any, props: Object) => any;
48+
type renderElement = (element: any, container: HTMLElement) => void;
49+
type unmountElement = (element: HTMLElement) => void;
50+
51+
These functions can be thought of as being analogous to those from React.
52+
53+
- ``createElement`` ➜ |react.createElement|_
54+
- ``renderElement`` ➜ |reactDOM.render|_
55+
- ``unmountElement`` ➜ |reactDOM.unmountComponentAtNode|_
56+
57+
.. |react.createElement| replace:: ``react.createElement``
58+
.. _react.createElement: https://reactjs.org/docs/react-api.html#createelement
59+
60+
.. |reactDOM.render| replace:: ``reactDOM.render``
61+
.. _reactDOM.render: https://reactjs.org/docs/react-dom.html#render
62+
63+
.. |reactDOM.unmountComponentAtNode| replace:: ``reactDOM.unmountComponentAtNode``
64+
.. _reactDOM.unmountComponentAtNode: https://reactjs.org/docs/react-api.html#createelement
65+
66+
And will be called in the following manner:
67+
68+
.. code-block::
69+
70+
// on every render
71+
renderElement(createElement(type, props), container);
72+
// on unmount
73+
unmountElement(container);
74+
75+
Once you've done this, you can distribute the bundled javascript in your Python package
76+
and integrate it into IDOM by defining :class:`~idom.client.module.Module` objects that
77+
load them from source:
3978

4079
.. code-block::
4180
4281
import idom
4382
my_js_package = idom.Module("my-js-package", source_file="/path/to/my/bundle.js")
4483
45-
The core benefit of loading Javascript in this way is that users of your code won't need
46-
NPM_. Rather, they can use ``pip`` to install your Python package without any other build
47-
steps because the bundled Javascript you distributed with it will be symlinked into the
48-
IDOM client at runtime.
49-
50-
With that said, if you just want to see how this all works it might be easiest to hook
51-
in simple a hand-crafted Javascript component. In the example to follow we'll create a
52-
very basic SVG line chart. The catch though is that we are limited to using Javascript
53-
that can run directly in the browser. This means we can't use fancy syntax like
54-
`JSX <https://reactjs.org/docs/introducing-jsx.html>`__ and instead will use
84+
The simplest way to try this out yourself though, is to hook in simple a hand-crafted
85+
Javascript module that has the requisite interface. In the example to follow we'll
86+
create a very basic SVG line chart. The catch though is that we are limited to using
87+
Javascript that can run directly in the browser. This means we can't use fancy syntax
88+
like `JSX <https://reactjs.org/docs/introducing-jsx.html>`__ and instead will use
5589
`htm <https://github.com/developit/htm>`__ to simulate JSX in plain Javascript.
5690

5791
.. example:: super_simple_chart

setup.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ def run(self):
136136
for args in (f"{npm} install", f"{npm} run build"):
137137
args_list = args.split()
138138
log.info(f"> {list2cmdline(args_list)}")
139-
subprocess.run(args_list, cwd=js_dir)
139+
subprocess.run(args_list, cwd=js_dir, check=True)
140140
except Exception:
141141
log.error("Failed to install Javascript")
142142
log.error(traceback.format_exc())

src/idom/client/app/packages/idom-client-react/src/component.js

+27-25
Original file line numberDiff line numberDiff line change
@@ -64,19 +64,23 @@ function ImportedElement({ model }) {
6464

6565
// this effect must run every time in case the model has changed
6666
react.useEffect(() => {
67-
importSource.then(({ render }) => {
68-
render(
69-
mountPoint.current,
70-
model.tagName,
71-
elementAttributes(model, config.sendEvent),
72-
model.children,
73-
config
67+
importSource.then(({ createElement, renderElement }) => {
68+
renderElement(
69+
createElement(
70+
model.tagName,
71+
elementAttributes(model, config.sendEvent),
72+
model.children
73+
),
74+
mountPoint.current
7475
);
7576
});
7677
});
7778

7879
react.useEffect(
79-
() => () => importSource.then(({ unmount }) => unmount()),
80+
() => () =>
81+
importSource.then(({ unmountElement }) =>
82+
unmountElement(mountPoint.current)
83+
),
8084
[]
8185
);
8286

@@ -149,28 +153,26 @@ function loadFromImportSource(config, importSource) {
149153
.loadImportSource(importSource.source, importSource.sourceType)
150154
.then((module) => {
151155
if (
152-
typeof module.render == "function" &&
153-
typeof module.unmount == "function"
156+
typeof module.createElement == "function" &&
157+
typeof module.renderElement == "function" &&
158+
typeof module.unmountElement == "function"
154159
) {
155160
return {
156-
render: (element, type, props, children, config) => {
157-
module.render(element, module[type], props, children, config);
158-
},
159-
unmount: module.unmount,
161+
createElement: (type, props) =>
162+
module.createElement(module[type], props),
163+
renderElement: module.renderElement,
164+
unmountElement: module.unmountElement,
160165
};
161166
} else {
162167
return {
163-
render: (element, type, props, children) => {
164-
reactDOM.render(
165-
react.createElement(
166-
module[type],
167-
props,
168-
...elementChildren(children)
169-
),
170-
element
171-
);
172-
},
173-
unmount: reactDOM.unmountComponentAtNode,
168+
createElement: (type, props, children) =>
169+
react.createElement(
170+
module[type],
171+
props,
172+
...elementChildren(children)
173+
),
174+
renderElement: reactDOM.render,
175+
unmountElement: reactDOM.unmountComponentAtNode,
174176
};
175177
}
176178
});

tests/test_client/js/set-flag-when-unmount-is-called.js

+10-6
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,22 @@
1-
export function render(element, component, props) {
2-
if (element.firstChild) {
3-
element.removeChild(element.firstChild);
1+
export function createElement(component, props) {
2+
return component(props);
3+
}
4+
5+
export function renderElement(element, container) {
6+
if (container.firstChild) {
7+
container.removeChild(container.firstChild);
48
}
5-
element.appendChild(component(props));
9+
container.appendChild(element);
610
}
711

8-
export function unmount(element) {
12+
export function unmountElement(container) {
913
// We add an element to the document.body to indicate that this function was called.
1014
// Thus allowing Selenium to see communicate to server-side code that this effect
1115
// did indeed occur.
1216
const unmountFlag = document.createElement("h1");
1317
unmountFlag.setAttribute("id", "unmount-flag");
1418
document.body.appendChild(unmountFlag);
15-
element.innerHTML = "";
19+
container.innerHTML = "";
1620
}
1721

1822
export function SomeComponent(props) {

0 commit comments

Comments
 (0)