-
-
Notifications
You must be signed in to change notification settings - Fork 6.8k
fix(optimizer): non object module.exports for Node builtin modules in CJS external facade #20048
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
import * as m from ${JSON.stringify(nonFacadePrefix + args.path)}; | ||
if (typeof m.default === 'function' || (typeof m.default === 'object' && m.default !== null)) { | ||
module.exports = Object.assign(m.default, m); | ||
} else { | ||
module.exports = { ...m }; | ||
}`, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The current behavior is aligned with requireReturnsDefault: false
of the commonjs plugin. I'm a bit worried about breaking users that rely on the current behavior.
I think we can have a special handling for node builtin modules here. I guess that would probably cover most cases.
This code would work for that.
import * as m from ${JSON.stringify(nonFacadePrefix + args.path)};
if (typeof m.default === 'function') {
module.exports = m.default;
} else {
module.exports = { ...m };
}
I confirmed this covers Node's builtin modules with the following script.
import module from 'node:module'
const require = module.createRequire(import.meta.url)
for (const mod of module.builtinModules) {
const esmExport = await import(mod)
const moduleExports = require(mod)
if (typeof esmExport.default === 'function') {
console.assert(esmExport.default === moduleExports, mod)
} else {
const esmList = Object.keys(esmExport).filter((key) => key !== 'default')
const cjsList = Object.keys(moduleExports)
const diffOnlyEsm = esmList.filter((key) => !cjsList.includes(key))
const diffOnlyCjs = cjsList.filter((key) => !esmList.includes(key))
console.assert(
diffOnlyEsm.length === 0 && diffOnlyCjs.length === 0,
mod,
diffOnlyEsm
.map((key) => `+ ${key}`)
.concat(diffOnlyCjs.map((key) => `- ${key}`)),
)
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've updated the PR to use the simpler change you suggested.
By the way, converting an external
Would that help in your case? If not, I’d be curious to hear why |
When I've tried using TypeError: (0 , vite_ssr_import_0.createRequire) is not a function We may be doing something else wrong but I think keeping |
contents: `\ | ||
import * as m from ${JSON.stringify(nonFacadePrefix + args.path)}; | ||
if (typeof m.default === 'function') { | ||
module.exports = m.default; | ||
} else { | ||
module.exports = { ...m }; | ||
}`, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the change! I realize my earlier comment might have been misleading, sorry about that. What I had in mind was to apply the change only for Node builtins. So the change I was thinking of looks like this:
contents: `\ | |
import * as m from ${JSON.stringify(nonFacadePrefix + args.path)}; | |
if (typeof m.default === 'function') { | |
module.exports = m.default; | |
} else { | |
module.exports = { ...m }; | |
}`, | |
contents: isNodeBuiltin(args.path) ? `\ | |
import * as m from ${JSON.stringify(nonFacadePrefix + args.path)}; | |
if (typeof m.default === 'function') { | |
module.exports = m.default; | |
} else { | |
module.exports = { ...m }; | |
}` : `\ | |
import * as m from ${JSON.stringify( | |
nonFacadePrefix + args.path, | |
)};` + `module.exports = { ...m };`, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, OK, thanks. I was curious if we could always just use the default export for Node builtins. This modified version of your script confirms this would work.
import module from "node:module";
const require = module.createRequire(import.meta.url);
for (const mod of module.builtinModules) {
const esmExport = await import(mod);
const moduleExports = require(mod);
const esmList = Object.keys(esmExport.default);
const cjsList = Object.keys(moduleExports);
const diffOnlyEsm = esmList.filter((key) => !cjsList.includes(key));
const diffOnlyCjs = cjsList.filter((key) => !esmList.includes(key));
console.assert(
diffOnlyEsm.length === 0 && diffOnlyCjs.length === 0,
mod,
diffOnlyEsm
.map((key) => `+ ${key}`)
.concat(diffOnlyCjs.map((key) => `- ${key}`))
);
}
Shall we go with that as it's simpler? Then the code could be:
contents: `\
import * as m from ${JSON.stringify(nonFacadePrefix + args.path)};
module.exports = ${isNodeBuiltin(args.path) ? 'm.default' : '{ ...m }'};
`,
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, that's true. I think we can go with your one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've made the changes - 0c0e387. Hopefully that's enough to solve the issues here.
It looks like the getAugmentedNamespace
function used in @rollup/plugin-commonjs
is quite a bit cleverer in how it handles these things. Out of interest, will Rolldown in Vite means that all this behaviour becomes more aligned in dev and build?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems the Windows CI is failing because of "node:dummy-builtin"
in dependencies
of dep-cjs-with-external-deps
.
https://github.com/vitejs/vite/pull/20048/files#diff-f7e4e2e9348777fb2e4dc2fa297148f6b58221e69a79143a09025358dfb8c204R8
When I removed it, the ERR_PNPM_SYMLINK_FAILED Maximum call stack size exceeded
error didn't happen.
Would you consider moving the test to playground/ssr-webworker
? That way, we can avoid having node:*
in the dependencies.
Out of interest, will Rolldown in Vite means that all this behaviour becomes more aligned in dev and build?
It will generally be more aligned. However, in the short term, this specific part won't be aligned by that (full-bundled mode would help in the long term).
Regarding that matter, you might be interested in this issue: rolldown/rolldown#4575
To summarize: Vite / Rollup have this require
->import
conversion mechanism, while esbuild / Rolldown does not have it, as such a conversion isn't semantically correct.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was the colon in the package name that was causing the issue on Windows. I've imported the dummy package as stream
so that it will be treated like a Node builtin and added a comment.
Ah, I think that's probably because |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks!
/ecosystem-ci run |
commit: |
📝 Ran ecosystem CI on
✅ ladle, astro, marko, quasar, rakkas, sveltekit, storybook, vite-plugin-pwa, unocss, react-router, vite-environment-examples, vite-plugin-svelte, vite-plugin-react, vite-plugin-vue, vite-plugin-cloudflare, vitepress, vite-setup-catalogue, vuepress |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See #20115 (comment) about the failures in CI.
Description
Fixes #20047
This aims to improve the interop when using the CJS external facade. If the package is a Node builtin then the default export is used rather than the named exports.