Add support for native ESM named exports#13689
Add support for native ESM named exports#13689sdepold merged 17 commits intosequelize:mainfrom ephys:main
Conversation
|
I think it was a temporary issue with GitHub Actions, @sdepold can you re-run the checks? |
|
Assuming we eventually have full support for TS, will this still be needed? I'm somehow reluctant to merge since we would have to keep both index files in sync, right?! |
|
Both index files would have to be kept in sync yes, Edit: transpiling for both is actually a bad idea, see #13689 (comment) |
|
Opinion? |
|
I think I would rather try to avoid merging this. |
|
Will take a deeper look into this in the evening |
|
I understand that having to maintain two exports file is annoying. More and more libraries are moving to native-esm only, forcing apps to migrate too and while we can import commonjs, every import must be changed from this to this (as well as having to split uses between the type and the value, making it incompatible with TS's
Edit: extra thought regarding this: This method has another advantage over transpiling for both versions: A CommonJS app that uses both ( And regarding maintaining both files: I can add a test that ensures the ESM export has the same keys as the CJS export. |
I'd also opt to avoid merging this, I think its better to wait until we can convert this to TS. If we want to get this working for mjs ASAP I think something like this should work: // lib/sequelize.js
module.exports = Sequelize;
// We're changing the `Sequelize` object here, its just more readable as
// module.exports + that matches what we already have in `lib/sequelize.js`
module.exports.Sequelize = Sequelize;
module.exports.default = Sequelize;
module.exports.__esModule = true; // tell ES modules that this is an esmodule. |
|
But that is not how native ES modules work. Only bundlers respect the There are only 3 ways to support ES Modules: Option 1: Convert the whole codebase to ES Modules and drop CommonJS supportcons:
Option 2: Convert the whole codebase to ES Modules and transpile to CommonJScons:
Option 3: Keep the codebase as CommonJS and include a module that re-exports the CommonJS module using named exportscons:
Closing note: Node does have some static analysis for exposing named exports from CommonJS modules but it should not be relied upon, it's also not available for node 12. |
|
Alternatively, once we finish the migration to TypeScript we should be able to configure the package to allow us to build for both nodejs and cjs, as esbuild supports this. We'd probably want to implement a clone of the index file though, since (I believe we can include both an |
|
Transpiling to both CommonJS and ESM is exactly the option 2 I described and it is going to cause huge issues to CommonJS users:
You can include both |
|
Sorry for the ping but any update on this @Keimeno @sdepold? I think #13689 (comment) explains why this PR is the only way to support ESM named exports and CommonJS at the same time without causing weird bugs |
What kinds of issues? I can't see why a module would be partially CJS and partially MJS at runtime. I think its also better to have a full ESM module for performance & security rather than having a mixed source.
In NodeJS v16.7.0 I get And similarly, in NodeJS v16.7.0 I can't use import Module from 'module';
const require = Module.createRequire('/full/path/to/file'); // __filename also isn't available in .mjs files otherwise that's what we'd use here.
const Sequelize = require('sequelize'); |
|
I would be fine with supporting esm in the short term with this, I'd rather have something than nothing for anyone who wants to use ESM. However I think that in the long term it'd be best to have both esm and cjs compile targets, as the source will be esm and I'd like to keep the transpiled code as close to its native runtime as possible. @ephys this change doesn't account for esbuild and |
wbourne0
left a comment
There was a problem hiding this comment.
Didn't think to request changes in my previous comment, but:
This change doesn't account for dist/, it'll need to be modified to copy the index.mjs file to dist/ (I don't think any esbuild tanspiling is necessary here).
I believe package.json will also need to be updated a bit for this.
You're mistaking the static import with the dynamic import. As specified here, dynamic imports are available to CommonJS. Static imports are not. Plus, as you said yourself, ESM could also access
Most common case: by using any library written in CommonJS that depends Sequelize. They will all break for ESM users. Say your write your app in full native esm. Your app is therefore going to do this:
For type safety, this method checks whether the parameter it received is indeed a sequelize model like so: Using the solution of providing two completely separate transpilation outputs, that is going to return false as there will be two different Extra scenarioAlternative scenario: Many people are currently using the ESM import syntax but then transpiling and running it as CJS. So the following code import SequelizePkg from 'sequelize';
(async () => {
console.log((await import('sequelize')).default === SequelizePkg);
})();could be transpiled to this: const SequelizePkg = require('sequelize');
(async () => {
console.log((await import('sequelize')).default === SequelizePkg);
})();which would output So as long as you support CommonJS, Solution 3 is your bug free version.
What extra security are you speaking of? By the way, please note that the code that's currently being written for the TypeScript migration is not valid ESM.
Will do |
|
I have added a v7 branch now and we could start removing support for node 10 in that branch since we planned to do that anyway. Should we target that branch instead of main maybe? |
|
I think we can land this in v6, node 10 users won't be able to import this file anyway so I can just skip the test for node 10. |
esm is not available in node 10
|
The test has been disabled on node 10, only thing left to resolve is decide whether we handle the typing issues the test unearthed in this PR (#13689 (comment)) |
|
Let's do that in a separate PR if you don't mind and we will merge this PR after that one |
|
Here we go :) #13751 |
|
looks promising |
|
I assume some update is needed now? |
|
Yes, just need to tell the test that Sequelize.db2 is not an export. Should be good now :) |
|
Woop |
|
🥳 |
|
I'll release this as part of our beta releases in a moment :) |
|
🎉 This PR is included in version 6.12.0-beta.3 🎉 The release is available on: Your semantic-release bot 📦🚀 |
|
Nice work on this! @ephys |
|
🎉 This PR is included in version 6.12.0 🎉 The release is available on: Your semantic-release bot 📦🚀 |
* feat: add support for native esm * feat: add missing ESM Named exports * build: move esm entry point to /dist/index.mjs * fix: add missing esm exports * test: add test to ensure esm & cjs export parity * test: include non-enumerables in esm-cjs export partity test * test: ignore "snowflake" property in esm-cjs parity test * test: list all missing exports in one run (esm-named-exports test) * test: skip esm-cjs compat test in node 10 esm is not available in node 10 * test: ensure esm & cjs export the same instances * fix: add missing esm exports * test: mark 'Sequelize.db2' as not a top level export Co-authored-by: Sascha Depold <[email protected]> Co-authored-by: Rik Smale <[email protected]>






Pull Request Checklist
Please make sure to review and check all of these items:
npm run testornpm run test-DIALECTpass with this change (including linting)?Description Of Change
This PR adds support for non-transpiled ESM named imports (Closes #13132)
Instead of converting the codebase to ESM and transpiling to CJS, this PR takes the simpler approach of adding a separate entry point (index.mjs) for ESM that imports the CJS and re-exports it as the default export, and its properties as named exports.
Note: while testing this I noticed that the TypeScript typing declares an export "validator", but the actual export is called "Validator".
exportshas been set in package.json to make CommonJS requireindex.js& ESM importindex.mjs