-
Notifications
You must be signed in to change notification settings - Fork 30.1k
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
Improve documentation on Dual Module Packages #34515
Comments
Note: Even if this is "not a bug", I believe the documentation could use an update to include a working example of a dual module package. Ideally, that example would be one that can be extended to be used in the real world and can work when compiling from TypeScript (i.e., an example that says to just rename all your files and all of your imports isn't particularly useful). It is also worth noting that the library should be native browser compatible as well, which means following the "MJS wrapper around CJS" pattern won't work. |
For reference, the {
"name": "library-package",
"version": "1.0.0",
"main": "./index-cjs.js",
"exports": {
"import": "./index-esm.js",
"require": "./index-cjs.js"
},
"type": "module"
} Setting $ mkdir ./cjs ./esm
$ echo '{"type":"commonjs"}' > cjs/package.json
$ echo '{"type":"module"}' > esm/package.json
$ git mv index-cjs.js cjs/index.js
$ git mv index-esm.js esm/index.js And then have your package exports point to those subfolders: {
"name": "library-package",
"version": "1.0.0",
"main": "./cjs/index.js",
"exports": {
"import": "./esm/index.js",
"require": "./cjs/index.js"
},
"type": "module"
} |
Are there any side effects to putting a mostly-empty |
Correct, npm uses only the "root" |
I have updated the title of this to be a documentation improvement that illustrates the above thing with multiple output folders and package.json files. |
@MicahZoltu that's cool and here's another working example (tested with node 12):
Note the .mjs extension - a crucial point to make it work. |
@kapouer #34515 (comment) worked for me back when I tested it after learning. All of my projects now have two The reason I strongly prefer this way over |
While the multiple-output-folder approach works, I do consider the OP to be a bug: the The same sort of "missed opportunity" is true for a Taking these "hints" into account does "complicate" the interpretation process a bit -- but only a bit, and it seems worth it, to avoid the need for people to use separate output folders all the time. (or variant extensions, which has drawbacks for build tools like TypeScript) |
This was discussed recently in #38740. The gist of it is the assumption is not correct: you could pass anything to the
It would actually complicate it by a lot 😅 also it brings other questions such as "what happens if those hints lead to a file being both ESM and CJS?", it would have to introduce all sorts of exception which could become tricky to document. See the linked discussion for more details. That being said I agree we could/should use those hints when printing the error message to help users debug their |
For WASM and JSON: AFAIK there are only two valid values for the My proposal merely changes the implicit module-type, for the specific file referenced under
What use case is there for someone to ever put a CJS module under the
I read through the full thread. The two main counters to the proposal seems to be:
As mentioned above, I don't currently see any use-case for marking a file under both |
I was referencing something like this: {
"exports": {
"import": "./esm-data.json",
"require": "./cjs-data.json"
}
} It would be wrong to assume those JSON file are either ESM or CJS IMHO. But I guess you were discussing
I don't know any valid use case, but using the // a.cjs
module.exports = 'I was imported';
// b.cjs
module.exports = 'I was required';
// index.cjs
console.log(require('this-package')); // I was required
import('this-package').then(console.log); // { default: 'I was imported' } {
"name": "this-package",
"exports": {
"import": "./a.cjs",
"require": "./b.cjs"
}
} I don't know why anyone would need that tbh, but that's the current state of things.
Currently, when parsing a |
Just wanted to link to a real-world use-case where NodeJS's non-utilization of the Yes, it's possible to solve this using the approaches described above (eg. creating a For me, the main reasons I don't like those two workarounds:
My guess is that several of the package maintainers have avoided use of those workarounds as well for the same (or related) reasons. Because of this, it leaves the end-user in the annoying situation of having to pester the maintainers of many different packages to change their output structure (with the drawbacks mentioned above), fork the packages themself, or manually modify their local copies using something like patch-package. That last option is what I'm going with for now; but it's less than ideal due to the need to maintain those patch files when updating the packages, as well as the fact that npm's modification of |
Indeed, apparently even the Microsoft TypeScript developers assumed as I (and others) have that the module-type would be inferred from
(An example of that typescript output can be seen here, from this source file and this tsconfig.json file.) For reference, here is the
Can anyone see what the problem is? The problem, it seems, is that the TypeScript team, like hundreds of other package maintainers, have assumed that if their
...that NodeJS would recognize the But instead, NodeJS looks at the This is just one example out of many, of widely used Javascript packages which have misunderstood (or neglected to implement, due to inconvenience) the changes necessary for NodeJS to recognize a I've hit so many build-blockers due to this issue, due to so many modules being affected by this flaw in NodeJS (well, I consider it a flaw anyway), that I've been working on this issue for over half of a day, and still have not completed all the fixes. It seems sometimes like I'm one of very few developers who are actually attempting to use NodeJS in esm-import mode. Many libraries have esm outputs, but like half or more of them have "broken" esm builds, with the majority of these "broken" merely because NodeJS is ignoring all of the |
The pain continues. I keep hitting the issue over and over whenever I try to add "ESM-output" packages to my NodeJS program. (I'm now paranoid whenever I try to add a package) Switching to ESM-import mode in NodeJS has produced hours of time-loss because of this single issue (there is no easy way to solve this as a consumer of a library -- requiring forking of libraries and/or persistently pestering the maintainers of everything I try to import). And that it is not being seen as a serious issue suggests to me that I am one of very few people actually using NodeJS in ESM-import mode. If the inference of ESM-mode based on hints from Someone will surely say, "It's good that you're being forced to solve the issue upstream, so we can get it fixed for everyone." But that doesn't sound so good of a solution when you're (seemingly) the only one using NodeJS in ESM mode, and most maintainers are not interested enough in the issue to fix it themselves. (Issues like this are why adoption of ESM-mode is moving so slowly imo; there is not a smooth path of transition to it, because of this and other issues. But this is the main one, by far, in my experience.) Basically, until there is something in the build/publish process itself that is preventing package developers from continuing to make this mistake, the mistake will keep on happening, and ESM-output builds will keep being broken. Until such a warning/error is implemented, consumers should be able to have some way of saying, "Yes, this module really is an ESM module -- interpret it as ESM and let me continue developing." Without that, usage of NodeJS in ESM-import mode is far more pain than it is worth. |
Something like this for NodeJS would be amazing: https://typestrong.org/ts-node/docs/module-type-overrides This would (mostly) solve my pain-point, because it would allow me to just import directly from the |
I think this kind of solution would create a non-negligible performance decrease if node has to check the whole file system up to the root to see if a BTW if you don't care about transitive deps and just want your own dependencies to behave, you should look into |
I've created tsconfig-to-dual-package for making TypeScript library dual package based on @aduh95 comment: #34515 (comment)
Edit: Ah, I missed it. |
v14.4.0
What steps will reproduce the bug?
Then remove
"type": "module"
fromlibrary-package/package.json
and repeat the process:How often does it reproduce? Is there a required condition?
Always.
What is the expected behavior?
The ability to ship an NPM package that can be used by either CJS users (any version of NodeJS) or ESM users (running NodeJS 14+)
What do you see instead?
I can either define
type: module
and it will work as an ESM, or I can not define it or set it to CommonJS and it will work as a CJS module, but I cannot define the package.json such that the package works in both environments.Additional information
The error indicates that NodeJS is correctly following the
exports
path and locating the right module in each situation, however it proceeds to load the module using a loader picked by the presence of thetype
property inpackage.json
. My expectation is that if it loads via theexports: { import: ... }
entrypoint then that entire call stack from there down should be ESM, and if it loads viaexports: { require: ... }
then the entire call stack from there down should be CJS.Error when
type: module
is not set:Error when
type: module
is set:The documentation makes it sound like NodeJS 14 supports packages that can be used as either ESM or CJS, but thus far I have not figured out how to actually accomplish this.
I believe this specific problem could be worked around by renaming files to
.cjs
and.mjs
in thelibrary-package
. However, in the real world this solution is far less tenable because I'm compiling from TypeScript which does not do import rewrites during emit (and they maintain a very strong position that they have no intent to ever change this policy). This means that I cannot have TypeScript emit files where the import statements have different extensions depending on the module type being targeted. So in order to have everything bemjs
in the ESM build output andcjs
in the CJS build output I would need to run my code through a transformer that rewrites all import statements (both static and dynamic) and also rename all of the files to have extensions by output folder.The text was updated successfully, but these errors were encountered: