Explorar o código

fix(vite): bypass es-toolkit CJS shim for recharts deep imports

The Nodes page (and any other recharts-using route) crashed in dev and
prod with TypeError: require_isUnsafeProperty is not a function.

Root cause: es-toolkit's package.json exports './compat/*' only via a
default condition pointing at the CJS shims under compat/<name>.js.
Those shims use a require_X.Y access pattern that Vite's optimizer
(Rolldown in Vite 8) and the production Rolldown build both mishandle,
losing the named-export accessor and calling the namespace object as
a function. recharts imports a dozen of these subpaths with default-
import syntax, so every chart path tripped the bug.

The matching ESM build at dist/compat/<category>/<name>.mjs is fine,
but it only carries a named export. Recharts uses default imports.

Plug a small Rollup-compatible plugin (enforce: 'pre') in front of
the resolver: any 'es-toolkit/compat/<name>' request becomes a virtual
module that imports the named symbol from the right .mjs file and
re-exports it as both default and named. The plugin is registered as
a top-level plugin (for the prod build) and via the new Vite 8
optimizeDeps.rolldownOptions.plugins (for the dev pre-bundler), so
both pipelines pick it up consistently.
MHSanaei hai 16 horas
pai
achega
2d55b3b663
Modificáronse 1 ficheiros con 45 adicións e 1 borrados
  1. 45 1
      frontend/vite.config.js

+ 45 - 1
frontend/vite.config.js

@@ -77,6 +77,45 @@ function injectBasePathPlugin() {
   };
 }
 
+// es-toolkit's `./compat/*` exports map only declares a CJS condition, so deep
+// imports like `es-toolkit/compat/get` resolve to a CJS shim. That shim uses a
+// `require_X.Y` pattern that Vite's optimizer and Rolldown both mishandle
+// (TypeError: require_isUnsafeProperty is not a function). The ESM build at
+// `dist/compat/<category>/<name>.mjs` is fine but only carries a named export,
+// while consumers like recharts use default imports — so emit a virtual module
+// that re-exports the named symbol as default.
+const ES_TOOLKIT_COMPAT_DIRS = ['array', 'function', 'math', 'object', 'predicate', 'string', 'util'];
+const ES_TOOLKIT_SHIM_PREFIX = '\0es-toolkit-compat:';
+
+function findEsToolkitCompatMjs(name) {
+  for (const sub of ES_TOOLKIT_COMPAT_DIRS) {
+    const candidate = path.resolve(__dirname, 'node_modules/es-toolkit/dist/compat', sub, `${name}.mjs`);
+    if (fs.existsSync(candidate)) return candidate;
+  }
+  return null;
+}
+
+function esToolkitCompatEsmResolver() {
+  return {
+    name: 'es-toolkit-compat-esm',
+    enforce: 'pre',
+    resolveId(id) {
+      const m = id.match(/^es-toolkit\/compat\/(.+)$/);
+      if (!m) return null;
+      if (!findEsToolkitCompatMjs(m[1])) return null;
+      return ES_TOOLKIT_SHIM_PREFIX + m[1];
+    },
+    load(id) {
+      if (!id.startsWith(ES_TOOLKIT_SHIM_PREFIX)) return null;
+      const name = id.slice(ES_TOOLKIT_SHIM_PREFIX.length);
+      const target = findEsToolkitCompatMjs(name);
+      if (!target) return null;
+      const url = target.replace(/\\/g, '/');
+      return `import { ${name} } from ${JSON.stringify(url)};\nexport { ${name} };\nexport default ${name};\n`;
+    },
+  };
+}
+
 function bypassMigratedRoute(req) {
   if (req.method !== 'GET') return undefined;
   const url = req.url.split('?')[0];
@@ -140,12 +179,17 @@ function makeBackendProxy(target) {
 }
 
 export default defineConfig({
-  plugins: [react(), injectBasePathPlugin()],
+  plugins: [esToolkitCompatEsmResolver(), react(), injectBasePathPlugin()],
   resolve: {
     alias: {
       '@': path.resolve(__dirname, 'src'),
     },
   },
+  optimizeDeps: {
+    rolldownOptions: {
+      plugins: [esToolkitCompatEsmResolver()],
+    },
+  },
   experimental: {
     renderBuiltUrl(filename, { hostType }) {
       if (hostType === 'js') {