From 69a967b1dd9046826cf7c1099d09fd3ceed5b8a1 Mon Sep 17 00:00:00 2001 From: rmorshea Date: Sat, 18 Jun 2022 12:39:17 -0700 Subject: [PATCH 1/4] ability to specify react version in template --- .../_examples/material_ui_button_no_action.py | 22 ++++++++++++------- .../_examples/material_ui_button_on_click.py | 6 ++++- src/idom/web/module.py | 8 +++++-- src/idom/web/templates/react.js | 4 ++-- 4 files changed, 27 insertions(+), 13 deletions(-) diff --git a/docs/source/guides/escape-hatches/_examples/material_ui_button_no_action.py b/docs/source/guides/escape-hatches/_examples/material_ui_button_no_action.py index d8f57de08..55817f9dd 100644 --- a/docs/source/guides/escape-hatches/_examples/material_ui_button_no_action.py +++ b/docs/source/guides/escape-hatches/_examples/material_ui_button_no_action.py @@ -1,11 +1,17 @@ -import idom +from idom import component, run, web -mui = idom.web.module_from_template("react", "@material-ui/core@^5.0", fallback="⌛") -Button = idom.web.export(mui, "Button") - -idom.run( - idom.component( - lambda: Button({"color": "primary", "variant": "contained"}, "Hello World!") - ) +mui = web.module_from_template( + "react@^17.0.0", + "@material-ui/core@4.12.4", + fallback="⌛", ) +Button = web.export(mui, "Button") + + +@component +def HelloWorld(): + return Button({"color": "primary", "variant": "contained"}, "Hello World!") + + +run(HelloWorld) diff --git a/docs/source/guides/escape-hatches/_examples/material_ui_button_on_click.py b/docs/source/guides/escape-hatches/_examples/material_ui_button_on_click.py index 41a3b7c54..e7c327ba0 100644 --- a/docs/source/guides/escape-hatches/_examples/material_ui_button_on_click.py +++ b/docs/source/guides/escape-hatches/_examples/material_ui_button_on_click.py @@ -3,7 +3,11 @@ import idom -mui = idom.web.module_from_template("react", "@material-ui/core@^5.0", fallback="⌛") +mui = idom.web.module_from_template( + "react@^17.0.0", + "@material-ui/core@4.12.4", + fallback="⌛", +) Button = idom.web.export(mui, "Button") diff --git a/src/idom/web/module.py b/src/idom/web/module.py index 48267adff..d98bd18d7 100644 --- a/src/idom/web/module.py +++ b/src/idom/web/module.py @@ -122,6 +122,10 @@ def module_from_template( Using this option has negative performance consequences since all DOM elements must be changed on each render. See :issue:`461` for more info. """ + template_name, _, template_version = template.partition("@") + if template_version: + template_version = "@" + template_version + # We do this since the package may be any valid URL path. Thus we may need to strip # object parameters or query information so we save the resulting template under the # correct file name. @@ -131,7 +135,7 @@ def module_from_template( cdn = cdn.rstrip("/") template_file_name = ( - template + template_name + (".default" if exports_default else "") + module_name_suffix(package_name) ) @@ -140,7 +144,7 @@ def module_from_template( if not template_file.exists(): raise ValueError(f"No template for {template_file_name!r} exists") - variables = {"PACKAGE": package, "CDN": cdn} + variables = {"PACKAGE": package, "CDN": cdn, "VERSION": template_version} content = Template(template_file.read_text()).substitute(variables) return module_from_string( diff --git a/src/idom/web/templates/react.js b/src/idom/web/templates/react.js index f4e2b091f..691fce400 100644 --- a/src/idom/web/templates/react.js +++ b/src/idom/web/templates/react.js @@ -1,7 +1,7 @@ export * from "$CDN/$PACKAGE"; -import * as React from "$CDN/react"; -import * as ReactDOM from "$CDN/react-dom"; +import * as React from "$CDN/react$VERSION"; +import * as ReactDOM from "$CDN/react-dom$VERSION"; export function bind(node, config) { return { From 20c2655739a99acee127a9578bed8b5f977e0b5a Mon Sep 17 00:00:00 2001 From: rmorshea Date: Sat, 18 Jun 2022 21:53:00 -0700 Subject: [PATCH 2/4] fix simple chart example --- .../_examples/super_simple_chart/super-simple-chart.js | 7 +++---- src/idom/web/module.py | 5 ++--- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/docs/source/guides/escape-hatches/_examples/super_simple_chart/super-simple-chart.js b/docs/source/guides/escape-hatches/_examples/super_simple_chart/super-simple-chart.js index 71f117860..486e5c363 100644 --- a/docs/source/guides/escape-hatches/_examples/super_simple_chart/super-simple-chart.js +++ b/docs/source/guides/escape-hatches/_examples/super_simple_chart/super-simple-chart.js @@ -48,10 +48,9 @@ function makePath(props, domain, data, options) { const getSvgX = (x) => ((x - xMin) / (xMax - xMin)) * width; const getSvgY = (y) => height - ((y - yMin) / (yMax - yMin)) * height; - let pathD = "M " + getSvgX(data[0].x) + " " + getSvgY(data[0].y) + " "; - pathD += data.map((point, i) => { - return "L " + getSvgX(point.x) + " " + getSvgY(point.y) + " "; - }); + let pathD = + `M ${getSvgX(data[0].x)} ${getSvgY(data[0].y)} ` + + data.map(({ x, y }, i) => `L ${getSvgX(x)} ${getSvgY(y)}`).join(" "); return html` Date: Sat, 18 Jun 2022 22:12:01 -0700 Subject: [PATCH 3/4] changelog + add dynamic default export --- docs/source/about/changelog.rst | 8 ++++ .../reference/_examples/network_graph.py | 2 - src/idom/web/module.py | 9 +--- src/idom/web/templates/react.default.js | 48 ------------------- src/idom/web/templates/react.js | 13 +++++ .../{test_server => test_backend}/__init__.py | 0 .../test_common.py | 0 .../test_utils.py | 0 8 files changed, 22 insertions(+), 58 deletions(-) delete mode 100644 src/idom/web/templates/react.default.js rename tests/{test_server => test_backend}/__init__.py (100%) rename tests/{test_server => test_backend}/test_common.py (100%) rename tests/{test_server => test_backend}/test_utils.py (100%) diff --git a/docs/source/about/changelog.rst b/docs/source/about/changelog.rst index ae54e44d5..10215f200 100644 --- a/docs/source/about/changelog.rst +++ b/docs/source/about/changelog.rst @@ -36,6 +36,14 @@ Unreleased - ``ServerFixture -> BackendFixture`` - ``DisplayFixture.server -> DisplayFixture.backend`` +- :pull:`765` - ``exports_default`` parameter is not longer required for + ``module_from_template`` when exporting ``default``. + +**Added** + +- :pull:`765` - ability to specify versions with module templates (e.g. + ``module_from_template("react@^17.0.0", ...)``). + v0.38.1 ------- diff --git a/docs/source/reference/_examples/network_graph.py b/docs/source/reference/_examples/network_graph.py index 3dfb9ae87..70187782d 100644 --- a/docs/source/reference/_examples/network_graph.py +++ b/docs/source/reference/_examples/network_graph.py @@ -4,10 +4,8 @@ react_cytoscapejs = idom.web.module_from_template( - # we need to use this template because react-cytoscapejs uses a default export "react", "react-cytoscapejs", - exports_default=True, fallback="⌛", ) Cytoscape = idom.web.export(react_cytoscapejs, "default") diff --git a/src/idom/web/module.py b/src/idom/web/module.py index c1ef8ec19..b6eced2c8 100644 --- a/src/idom/web/module.py +++ b/src/idom/web/module.py @@ -79,7 +79,6 @@ def module_from_template( package: str, cdn: str = "https://esm.sh", fallback: Optional[Any] = None, - exports_default: bool = False, resolve_exports: bool = IDOM_DEBUG_MODE.current, resolve_exports_depth: int = 5, unmount_before_update: bool = False, @@ -110,8 +109,6 @@ def module_from_template( Where the package should be loaded from. The CDN must distribute ESM modules fallback: What to temporarilly display while the module is being loaded. - exports_default: - Whether the module has a default export. resolve_imports: Whether to try and find all the named exports of this module. resolve_exports_depth: @@ -133,11 +130,7 @@ def module_from_template( # downstream code assumes no trailing slash cdn = cdn.rstrip("/") - template_file_name = ( - template_name - + (".default" if exports_default else "") - + module_name_suffix(package_name) - ) + template_file_name = template_name + module_name_suffix(package_name) template_file = Path(__file__).parent / "templates" / template_file_name if not template_file.exists(): diff --git a/src/idom/web/templates/react.default.js b/src/idom/web/templates/react.default.js deleted file mode 100644 index d5d8b5ac9..000000000 --- a/src/idom/web/templates/react.default.js +++ /dev/null @@ -1,48 +0,0 @@ -export { default } from "$CDN/$PACKAGE"; -export * from "$CDN/$PACKAGE"; - -import * as React from "$CDN/react"; -import * as ReactDOM from "$CDN/react-dom"; - -export function bind(node, config) { - return { - create: (component, props, children) => - React.createElement(component, wrapEventHandlers(props), ...children), - render: (element) => ReactDOM.render(element, node), - unmount: () => ReactDOM.unmountComponentAtNode(node), - }; -} - -function wrapEventHandlers(props) { - const newProps = Object.assign({}, props); - for (const [key, value] of Object.entries(props)) { - if (typeof value === "function") { - newProps[key] = makeJsonSafeEventHandler(value); - } - } - return newProps; -} - -function makeJsonSafeEventHandler(oldHandler) { - // Since we can't really know what the event handlers get passed we have to check if - // they are JSON serializable or not. We can allow normal synthetic events to pass - // through since the original handler already knows how to serialize those for us. - return function safeEventHandler() { - oldHandler( - ...Array.from(arguments).filter((value) => { - if (typeof value === "object" && value.nativeEvent) { - // this is probably a standard React synthetic event - return true; - } else { - try { - JSON.stringify(value); - } catch (err) { - console.error("Failed to serialize some event data"); - return false; - } - return true; - } - }) - ); - }; -} diff --git a/src/idom/web/templates/react.js b/src/idom/web/templates/react.js index 691fce400..00924f85a 100644 --- a/src/idom/web/templates/react.js +++ b/src/idom/web/templates/react.js @@ -3,6 +3,19 @@ export * from "$CDN/$PACKAGE"; import * as React from "$CDN/react$VERSION"; import * as ReactDOM from "$CDN/react-dom$VERSION"; +export default ({ children, ...props }) => { + const [{ component }, setComponent] = React.useState({}); + React.useEffect(() => { + import("$CDN/$PACKAGE").then((module) => { + // dynamically load the default export since we don't know if it's exported. + setComponent({ component: module.default }); + }); + }); + return component + ? React.createElement(component, props, ...(children || [])) + : null; +}; + export function bind(node, config) { return { create: (component, props, children) => diff --git a/tests/test_server/__init__.py b/tests/test_backend/__init__.py similarity index 100% rename from tests/test_server/__init__.py rename to tests/test_backend/__init__.py diff --git a/tests/test_server/test_common.py b/tests/test_backend/test_common.py similarity index 100% rename from tests/test_server/test_common.py rename to tests/test_backend/test_common.py diff --git a/tests/test_server/test_utils.py b/tests/test_backend/test_utils.py similarity index 100% rename from tests/test_server/test_utils.py rename to tests/test_backend/test_utils.py From 9a0a5559efe8d0a7e44aa787e4d6f5524678251e Mon Sep 17 00:00:00 2001 From: rmorshea Date: Sat, 18 Jun 2022 22:26:32 -0700 Subject: [PATCH 4/4] test template versioning --- tests/test_web/test_module.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/test_web/test_module.py b/tests/test_web/test_module.py index 48b981ba9..497b89787 100644 --- a/tests/test_web/test_module.py +++ b/tests/test_web/test_module.py @@ -83,7 +83,9 @@ def test_module_from_template_where_template_does_not_exist(): async def test_module_from_template(display: DisplayFixture): - victory = idom.web.module_from_template("react", "victory-bar@35.4.0") + victory = idom.web.module_from_template("react@18.2.0", "victory-bar@35.4.0") + + assert "react@18.2.0" in victory.file.read_text() VictoryBar = idom.web.export(victory, "VictoryBar") await display.show(VictoryBar)