-
Notifications
You must be signed in to change notification settings - Fork 30k
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
Adding an option to interpret __esModule as Babel does #40891
Comments
Adds an option `--cjs-import-interop`. When enabled, a CJS module will be translated to ESM slightly differently with respect to default exports. - When the module defines `module.exports.__esModule` as a truthy value, the value of `module.exports.default` is used as the default export. - Otherwise, `module.exports` is used as the default export. (existing behavior) It allows better interoperation between full ES modules and CJS modules transformed from ES modules by Babel or tsc. Consider the following example: ```javascript // Transformed from: // export default "Hello"; Object.defineProperty(module.exports, "__esModule", { value: true }); module.exports.default = "Hello"; ``` When imported from the following module: ```javascript import greeting from "./hello.cjs"; console.log(greeting); ``` With `--cjs-import-interop`, it will print "Hello". Fixes: nodejs#40891
Adds an option "importInterop" to package.json. When enabled, `import` statements in that package will behave slightly different with respect to default exports if the imported module is CJS. - When the imported module defines `module.exports.__esModule` as a truthy value, the value of `module.exports.default` is used as the default export. - Otherwise, `module.exports` is used as the default export. (existing behavior) It allows better interoperation between full ES modules and CJS modules transformed from ES modules by Babel or tsc. Consider the following example: ```javascript // Transformed from: // export default "Hello"; Object.defineProperty(module.exports, "__esModule", { value: true }); module.exports.default = "Hello"; ``` When imported from the following module: ```javascript import greeting from "./hello.cjs"; console.log(greeting); ``` with the following package.json: ```javascript { "type": "module", "importInterop": true } ``` Then it will print "Hello". Fixes: nodejs#40891
Hey, it looks like the module team consensus is to not do this so I'll go ahead and close the issue and Pr. Thanks a lot for the detailed request and implementation attempt. If you would be interested in getting more involved there is a lot of interesting work to do in the modules space in Node. I encourage you to get involved in the discussions :) |
Thank you for the responses too! As a (probably) final comment, let me advertise another solution for someone came across this issue: I made a Babel plugin to mitigate the problem described in this issue: https://github.com/qnighy/node-cjs-interop. Check it out if you're having the default importing issue! |
Is your feature request related to a problem? Please describe.
Node.js provides interop between ES Modules and CommonJS Modules in the following ways:
await import("foo.mjs")
works. Since CJS module body is executed synchronously, it is not possible to import an ESM module during CJS module setup.So basically, the migration should start from the depending side (= application code). In this strategy, each import edge in the module graph will transition in the following manner:
And it is already a widespread practice to write each package in ESM syntax and transform the modules to CJS using Babel or TypeScript (tsc). Node.js and Babel differ in how default imports are treated. If both the importing module and the imported module were in ESM syntax, the behavior will change like:
export default
.export default
.Instead of flipping imports manually, it would be really useful if Node.js can provide a behavior compatible with Babel's behavior.
Describe the solution you'd like
To change the behavior of the CJS compatibility layer. The current behavior is as follows:
module.exports
in CJS is mapped to thedefault
export in ESM.I propose to change the behavior:
module.exports.__esModule
is truthy, the value ofmodule.exports.default
in CJS is mapped to thedefault
export in ESM.module.exports
in CJS is mapped to thedefault
export in ESM.In my understanding, the existing behavior was introduced in #14369. At that time, named exports were not available and the decision was reasonable because we basically want to allow users to access all functionality from CJS modules.
When #35249 was merged to allow named exports/imports, the behavior was not changed as described in the original PR: to maintain compatibility and consistency.
I think it's more consistent to support
__esModule
flag as it provides an accurate embedding of ESM semantics to CJS. However backwards compat is still important; I propose to provide a command-line option to support__esModule
instead.I made a PR which contains the solution described above: #40892
Describe alternatives you've considered
importInterop: "node"
and fix the code appropriately. However, the user will struggle again if the dependent package migrates to full ESM.styled-components/macro
as they expect the specific default import pattern.package.json
rather than a command-line option.package.json
for the imported modulepackage.json
for the importing moduleThe text was updated successfully, but these errors were encountered: