-
Notifications
You must be signed in to change notification settings - Fork 44
import cjs strange behaviour (interop with babel) #480
Comments
Yeah, this is really confusing.
The issue at the core of this is: The module syntax in babel is just sugar for CommonJS. Which means that the code often doesn't work like a "real" JavaScript module. Node, just like browsers, implements JavaScript modules as they appear in the JavaScript spec. And loading CommonJS (the code babel generates) from ESM (JavaScript modules) is currently only really possible as the default export. If the file you are loading is CommonJS, it only has a default export (the value of See: https://babeljs.io/docs/en/babel-plugin-transform-modules-commonjs#nointerop (should not be set) |
Example: // foo.mjs
export function bar() {}
// foo.cjs, compiled by babel from foo.mjs
exports.bar = function bar() {}
Object.defineProperty(exports, "__esModule", { value: true });
// consumer.mjs
import { bar } from './foo.mjs';
bar();
// consumer.cjs, compiled by babel from consumer.mjs
var _foo = _interopRequireDefault(require("./foo.cjs"));
_foo.bar(); Note: The example above assumes that there's a babel plugin to rewrite mjs imports to the appropriate cjs path. |
The solution to this is in the package that's transpiled: their entry points should not be transpiled, they should This is a common problem in the TS and Babel ecosystems where the packages are published in such a way as to expose this interop implementation detail. |
@jkrems Thank you for detailed response! But I still don't get it. So, for now, Babel and Node.js has no interop, thus library authors can't start to update their libraries (they can update only if they support node 13+) |
In modules, there is no assignable module content object. The closest thing is the namespace object ( When those names are determined, no code has executed yet - so it's impossible to tell what properties the CJS file will set on
I'm not sure I follow - the example above should work..? Can you clarify which aspect of it doesn't work for you? If you can set up a repo or gist with a repro, I'm happy to take a look as well. :) When porting libraries from babel/CommonJS to modules, you'll likely have to start from deeper dependencies and work your way outwards. Or, as @ljharb hinted at, you can update the libraries to make sure they only expose the default export to outside users instead of leaking the babel module interop wrappers. |
@shrpne, if none of the solutions above work, you may have success with the following.
|
The above being the desugared form of: import cjsExportsObject from 'cjs-package-specifier'; |
Small nit: Please don't call the exports object a "namespace" in the context of ESM. There's already a namespace in ESM and it's something else. :) import { default as exportsValue } from 'cjs-package-specifier';
import exportsValue from 'cjs-package-specifier';
import * as namespace from 'cjs-package-specifier';
const exportsValue = namespace.default; |
I'm wondering if there's anything we should add to the docs regarding this. I don't want to give advice specific to Babel or using Babel-transpiled packages, though; is there something about |
I think we approach the limits of what belongs in the node reference docs vs. what could be a dedicated guide. I think both Typescript and Babel are important enough in the wider ecosystem to warrant dedicated how-tos. |
Not universal - it applies to Babel’s interop, and typescript’s (when their module system isn’t broken, by enabling esModuleInterop and synthetic imports, which tsc init enables by default). |
I guess these are the same issues: And they are somehow should be resolved by Babel to make it interoperable with Node's approach |
We do have a place on the repo for "guides".
Maybe a "all you need to know as a module author" guide would be appropriate
…On Fri, Jan 31, 2020, 12:57 PM shrpne ***@***.***> wrote:
I guess these are the same issues:
babel/babel#7294 <babel/babel#7294>
babel/babel#7998 <babel/babel#7998>
And they are somehow should be resolved by Babel to make it interoperable
with Node's approach
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
<#480?email_source=notifications&email_token=AADZYV4A5PMNVIZMBONJ5C3RARRBTA5CNFSM4KMXK3YKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEKPO4SQ#issuecomment-580841034>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AADZYV273KZ7HRVQZAWNSALRARRBTANCNFSM4KMXK3YA>
.
|
A couple links about ES module namespace objects from the ES2020 spec: |
I have tried to update my library that depends on other cjs libraries.
But I encountered a problem, that
import * as foo from 'foo'
treats it as{default: {bar, baz}}
when foo/index.js contains
As I know such module representation does not match current implementations, e.g. Babel, which doesn't wrap module content into {default: ...}.
So if I write
Node will complain "bar is not a function", because foo.default.bar should be used
And if I write
Babel will complain "Cannot read property 'bar' of undefined", because
default
export is undefined, sofoo
is undefined tooI guess TS it works like Babel here.
I don't understand why module-as-default representation of a module was implemented, I think it is very weird.
So how I should write my library so it can be used with native Node and can be transpiled too?
The text was updated successfully, but these errors were encountered: