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.
This commit is contained in:
MHSanaei
2026-05-25 17:33:20 +02:00
parent 75b0a21987
commit 2d55b3b663

View File

@@ -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') {