From 522cf410a31870db3b94a680bfea90d726b450c3 Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Thu, 27 Aug 2026 20:10:25 +0000 Subject: [PATCH 1/2] Filter the react-dom/server resolveId hook and fix the config import extension The generated vite.config.js registered `alwaysUseReactDomServerNode` as an enforce: "pre" plugin with an unfiltered resolveId hook, so rolldown called into JS for every import in the module graph (15865 calls on the docs build) just to redirect a single specifier. Declaring the documented hook filter lets rolldown skip the call entirely unless the specifier contains "react-dom/server". Also give the local safari cachebust plugin import a file extension: Vite's native config loader cannot resolve extensionless relative imports and warns about it today. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01PXbBjFDmFjqYQPHdPjk4zp --- .../src/reflex_base/compiler/templates.py | 29 +++++++++------ tests/units/utils/test_utils.py | 37 +++++++++++++++++++ 2 files changed, 54 insertions(+), 12 deletions(-) diff --git a/packages/reflex-base/src/reflex_base/compiler/templates.py b/packages/reflex-base/src/reflex_base/compiler/templates.py index 173c903ef54..84c5af0ed9f 100644 --- a/packages/reflex-base/src/reflex_base/compiler/templates.py +++ b/packages/reflex-base/src/reflex_base/compiler/templates.py @@ -655,7 +655,7 @@ def vite_config_template( return rf"""import {{ fileURLToPath, URL }} from "url"; import {{ reactRouter }} from "@react-router/dev/vite"; import {{ defineConfig }} from "vite"; -import safariCacheBustPlugin from "./vite-plugin-safari-cachebust"; +import safariCacheBustPlugin from "./vite-plugin-safari-cachebust.js"; // Ensure that bun always uses the react-dom/server.node functions. function alwaysUseReactDomServerNode() {{ @@ -663,17 +663,22 @@ def vite_config_template( name: "vite-plugin-always-use-react-dom-server-node", enforce: "pre", - resolveId(source, importer) {{ - if ( - typeof importer === "string" && - importer.endsWith("/entry.server.node.tsx") && - source.includes("react-dom/server") - ) {{ - return this.resolve("react-dom/server.node", importer, {{ - skipSelf: true, - }}); - }} - return null; + resolveId: {{ + // Without this filter, the hook runs for every import in the graph + // (tens of thousands of calls); only "react-dom/server" specifiers + // imported by the node server entry are of interest here. + filter: {{ id: /react-dom\/server/ }}, + handler(source, importer) {{ + if ( + typeof importer === "string" && + importer.endsWith("/entry.server.node.tsx") + ) {{ + return this.resolve("react-dom/server.node", importer, {{ + skipSelf: true, + }}); + }} + return null; + }}, }}, }}; }} diff --git a/tests/units/utils/test_utils.py b/tests/units/utils/test_utils.py index 213580abdf7..fcc7a790f4f 100644 --- a/tests/units/utils/test_utils.py +++ b/tests/units/utils/test_utils.py @@ -885,6 +885,43 @@ def test_vite_config_template_pins_preview_host() -> None: assert 'host: "127.0.0.1",' in config +def test_vite_config_template_imports_plugin_with_extension() -> None: + """Local plugin imports carry a file extension. + + Vite's native config loader (planned to become the default) cannot resolve + extensionless relative imports and warns about them today. + """ + from reflex.compiler import templates as compiler_templates + + config = compiler_templates.vite_config_template( + base="/", + hmr=True, + force_full_reload=False, + experimental_hmr=False, + sourcemap=False, + ) + assert 'from "./vite-plugin-safari-cachebust.js"' in config + + +def test_vite_config_template_filters_react_dom_server_resolve_hook() -> None: + """The react-dom/server resolveId hook declares a hook filter. + + Without a filter, rolldown calls the hook for every import in the module + graph, which dominates build time on large apps. + """ + from reflex.compiler import templates as compiler_templates + + config = compiler_templates.vite_config_template( + base="/", + hmr=True, + force_full_reload=False, + experimental_hmr=False, + sourcemap=False, + ) + assert "filter: { id: /react-dom\\/server/ }," in config + assert "handler(source, importer) {" in config + + @pytest.mark.parametrize("minify", [True, False]) def test_compile_vite_config_reads_minify_env( minify: bool, monkeypatch: pytest.MonkeyPatch From 67ffa406714223217025845d466fa1e1bf9f503c Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Thu, 27 Aug 2026 20:34:11 +0000 Subject: [PATCH 2/2] Drop the expository comment from the generated config, add news fragments Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01PXbBjFDmFjqYQPHdPjk4zp --- news/6959.performance.md | 1 + packages/reflex-base/news/6959.performance.md | 1 + packages/reflex-base/src/reflex_base/compiler/templates.py | 3 --- 3 files changed, 2 insertions(+), 3 deletions(-) create mode 100644 news/6959.performance.md create mode 100644 packages/reflex-base/news/6959.performance.md diff --git a/news/6959.performance.md b/news/6959.performance.md new file mode 100644 index 00000000000..31fa3094c44 --- /dev/null +++ b/news/6959.performance.md @@ -0,0 +1 @@ +The generated `vite.config.js` now declares a hook filter on the plugin that redirects `react-dom/server` to `react-dom/server.node`, so the bundler no longer calls into it for every import in the module graph — on the Reflex docs site that was ~15,800 calls per build to rewrite a single specifier. The local plugin import in the config also carries a file extension now, silencing Vite's warning about features its native config loader cannot resolve. diff --git a/packages/reflex-base/news/6959.performance.md b/packages/reflex-base/news/6959.performance.md new file mode 100644 index 00000000000..d5ad6b2fbe9 --- /dev/null +++ b/packages/reflex-base/news/6959.performance.md @@ -0,0 +1 @@ +`vite_config_template` declares a `resolveId` hook filter (`{ id: /react-dom\/server/ }`) on `vite-plugin-always-use-react-dom-server-node`. The plugin runs with `enforce: "pre"`, so without a filter rolldown invoked its JS handler for every import specifier in the graph (~15,800 calls on the Reflex docs build) to redirect the one specifier imported by `entry.server.node.tsx`; the filter is evaluated natively and skips the call otherwise. The template also imports `./vite-plugin-safari-cachebust.js` with its extension, which Vite's `configLoader: "native"` requires and warns about today. diff --git a/packages/reflex-base/src/reflex_base/compiler/templates.py b/packages/reflex-base/src/reflex_base/compiler/templates.py index 84c5af0ed9f..ff6260673c7 100644 --- a/packages/reflex-base/src/reflex_base/compiler/templates.py +++ b/packages/reflex-base/src/reflex_base/compiler/templates.py @@ -664,9 +664,6 @@ def vite_config_template( enforce: "pre", resolveId: {{ - // Without this filter, the hook runs for every import in the graph - // (tens of thousands of calls); only "react-dom/server" specifiers - // imported by the node server entry are of interest here. filter: {{ id: /react-dom\/server/ }}, handler(source, importer) {{ if (