From fdd9da919cd6a8bb0cdcf03977a4e8965955de03 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 27 May 2026 01:47:28 +0000 Subject: [PATCH 1/2] fix(recharts): bundle es-toolkit/compat as ESM to stop chart crash (#6561) recharts default-imports CommonJS `es-toolkit/compat/*` shims from its core utils (ChartUtils, DataUtils), so every chart pulls CJS into the bundle. Vite's Rolldown (prod) and Rollup (dev) emit the CJS interop helper into a chunk that can load after React Router's lazy route chunk, so any page with a chart throws a TypeError and reloads forever. Add a Vite plugin that redirects `es-toolkit/compat/` default-imports to the pure-ESM `es-toolkit/compat` barrel, eliminating the CJS interop helper. Also add a prod+dev recharts integration test that renders an area chart and asserts no page error. https://claude.ai/code/session_01B53hepRQu8LCtZxaxAFPSm --- packages/reflex-base/news/6561.bugfix.md | 1 + .../src/reflex_base/compiler/templates.py | 21 +++++ .../tests_playwright/test_recharts.py | 85 +++++++++++++++++++ 3 files changed, 107 insertions(+) create mode 100644 packages/reflex-base/news/6561.bugfix.md create mode 100644 tests/integration/tests_playwright/test_recharts.py diff --git a/packages/reflex-base/news/6561.bugfix.md b/packages/reflex-base/news/6561.bugfix.md new file mode 100644 index 00000000000..52027f3e36b --- /dev/null +++ b/packages/reflex-base/news/6561.bugfix.md @@ -0,0 +1 @@ +Fixed recharts charts crashing at runtime with a `TypeError` and an infinite route-reload loop. recharts default-imports CommonJS `es-toolkit/compat/*` shims, whose interop helper could be bundled into a chunk that loaded after a lazy route chunk; the generated `vite.config.js` now redirects those imports to the pure-ESM `es-toolkit/compat` barrel. diff --git a/packages/reflex-base/src/reflex_base/compiler/templates.py b/packages/reflex-base/src/reflex_base/compiler/templates.py index 838d414b964..606044e784c 100644 --- a/packages/reflex-base/src/reflex_base/compiler/templates.py +++ b/packages/reflex-base/src/reflex_base/compiler/templates.py @@ -575,10 +575,31 @@ def vite_config_template( }}; }} +function esToolkitCompatEsm() {{ + // recharts default-imports CJS shims like `es-toolkit/compat/get`; Rolldown can + // emit their interop helper into a chunk that loads after a lazy route chunk, + // crashing any page with a chart (#6561). Redirect to the pure-ESM compat barrel. + const VIRTUAL = "\0es-toolkit-compat:"; + return {{ + name: "vite-plugin-es-toolkit-compat-esm", + enforce: "pre", + resolveId(source) {{ + const match = /^es-toolkit\/compat\/([A-Za-z0-9_]+)$/.exec(source); + return match ? VIRTUAL + match[1] : null; + }}, + load(id) {{ + if (!id.startsWith(VIRTUAL)) return null; + const name = id.slice(VIRTUAL.length); + return "export {{ " + name + " as default }} from \"es-toolkit/compat\";"; + }}, + }}; +}} + export default defineConfig((config) => ({{ base: "{base}", plugins: [ alwaysUseReactDomServerNode(), + esToolkitCompatEsm(), reactRouter(), safariCacheBustPlugin(), ].concat({"[fullReload()]" if force_full_reload else "[]"}), diff --git a/tests/integration/tests_playwright/test_recharts.py b/tests/integration/tests_playwright/test_recharts.py new file mode 100644 index 00000000000..00e65eb38a9 --- /dev/null +++ b/tests/integration/tests_playwright/test_recharts.py @@ -0,0 +1,85 @@ +"""Integration test that recharts charts render at runtime. + +Regression coverage for https://github.com/reflex-dev/reflex/issues/6561: any page +rendering a ``rx.recharts`` chart crashed with ``TypeError`` and an infinite React +Router route-reload loop. recharts default-imports CommonJS ``es-toolkit/compat/*`` +shims from its core utils; the bundler emitted the CJS interop helper into a chunk +that loaded after the lazy route chunk. The bug only surfaced at runtime in a real +(esp. production / Rolldown) build, which this test exercises via ``app_harness_env``. +""" + +from collections.abc import Generator + +import pytest +from playwright.sync_api import Page, expect + +from reflex.testing import AppHarness + + +def RechartsApp(): + """App with an area chart on the index route (the issue's reproduction).""" + import reflex as rx + + @rx.page("/") + def index(): + return rx.box( + rx.heading("recharts"), + rx.recharts.area_chart( + rx.recharts.area(data_key="value"), + rx.recharts.x_axis(data_key="name"), + data=[ + {"name": "A", "value": 1}, + {"name": "B", "value": 3}, + {"name": "C", "value": 2}, + ], + width=500, + height=300, + ), + id="page", + ) + + app = rx.App() # noqa: F841 + + +@pytest.fixture(scope="module") +def recharts_app( + app_harness_env: type[AppHarness], + tmp_path_factory: pytest.TempPathFactory, +) -> Generator[AppHarness, None, None]: + """Start RechartsApp in both dev and prod mode. + + Args: + app_harness_env: AppHarness (dev) or AppHarnessProd (prod), from conftest. + tmp_path_factory: pytest fixture for creating temporary directories. + + Yields: + Running AppHarness instance. + """ + name = f"recharts_{app_harness_env.__name__.lower()}" + with app_harness_env.create( + root=tmp_path_factory.mktemp(name), + app_name=name, + app_source=RechartsApp, + ) as harness: + assert harness.app_instance is not None, "app is not running" + yield harness + + +def test_recharts_renders_without_error(recharts_app: AppHarness, page: Page): + """The chart's SVG renders and the page raises no JS error or reload loop. + + Args: + recharts_app: AppHarness running the recharts test app. + page: Playwright page. + """ + assert recharts_app.frontend_url is not None + + page_errors: list[str] = [] + page.on("pageerror", lambda exc: page_errors.append(str(exc))) + + page.goto(recharts_app.frontend_url) + + # Under #6561 the route module fails to load and React Router reloads forever, + # so the chart never appears and this assertion times out. + expect(page.locator(".recharts-surface")).to_be_visible() + assert not page_errors, f"Frontend raised unexpected errors: {page_errors}" From eba88dcab17f6000f6b5a86778398dba9221fb37 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 27 May 2026 02:39:37 +0000 Subject: [PATCH 2/2] fix(recharts): also redirect es-toolkit/compat in dev prebundle (#6561) The Vite plugin only covered the production build. In dev, Vite 8 prebundles deps with Rolldown before the build plugins run, so recharts' CommonJS `es-toolkit/compat/*` imports were still bundled with the broken interop helper (TypeError: require_isUnsafeProperty is not a function). Register the same plugin under `optimizeDeps.rolldownOptions.plugins` so the dev dependency optimizer redirects them to the pure-ESM barrel as well. https://claude.ai/code/session_01B53hepRQu8LCtZxaxAFPSm --- .../src/reflex_base/compiler/templates.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/reflex-base/src/reflex_base/compiler/templates.py b/packages/reflex-base/src/reflex_base/compiler/templates.py index 606044e784c..e24758dec26 100644 --- a/packages/reflex-base/src/reflex_base/compiler/templates.py +++ b/packages/reflex-base/src/reflex_base/compiler/templates.py @@ -576,9 +576,10 @@ def vite_config_template( }} function esToolkitCompatEsm() {{ - // recharts default-imports CJS shims like `es-toolkit/compat/get`; Rolldown can - // emit their interop helper into a chunk that loads after a lazy route chunk, - // crashing any page with a chart (#6561). Redirect to the pure-ESM compat barrel. + // recharts default-imports CJS shims like `es-toolkit/compat/get`; the CJS interop + // helper can land in a chunk that runs before it is initialized, crashing any page + // with a chart (#6561). Redirect to the pure-ESM compat barrel. Registered for both + // the prod build (plugins) and the dev prebundle (optimizeDeps.rolldownOptions). const VIRTUAL = "\0es-toolkit-compat:"; return {{ name: "vite-plugin-es-toolkit-compat-esm", @@ -623,6 +624,11 @@ def vite_config_template( }}, }}, }}, + optimizeDeps: {{ + rolldownOptions: {{ + plugins: [esToolkitCompatEsm()], + }}, + }}, experimental: {{ enableNativePlugin: false, hmr: {"true" if experimental_hmr else "false"},