-
-
Notifications
You must be signed in to change notification settings - Fork 3k
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
Node esm support #4038
Node esm support #4038
Conversation
I fixed the bug in Windows (it was actually a bug in the code, but it only manifested itself in Windows). Unfortunately, there is a bug in Windows support for ESM. The third ESM test I wrote fails in Windows in Node v12.6.0 (the version Appveyor runs), but passes in Node v12.11.1 (the latest Node version I downloaded today). I'm not sure how to upgrade the Node version in Appveyor, so I can disable this test for Node versions < 12.11.1? |
44fa39c
to
f9f92f8
Compare
It seems the bug in Node ESM implementation is not just in Windows, but also in Linux, so I disabled ESM support in Mocha for Node <v12.11.0. |
We should make this decision at first place. Why should we try to avoid var mocha = new Mocha();
mocha.addFile();
mocha.loadFiles();
mocha.run(); Would the user be affected in any other breaking way by a PS: Could you please edit your description above and add the |
@juergba today var mocha = new Mocha();
mocha.addFile();
mocha.run();
console.log('Done!) If we broke the interface, it would have to look something like this: var mocha = new Mocha();
mocha.addFile();
mocha.run().then(() => console.log('done')); Because But, because ESM is async, this is a change that has to be done. What I did was this: if you have tests using ESM in Node, you can still have your old sync code. But! If you have tests that are ESM, I am fully aboard with breaking compatibility, and in the long run it had to be done, but I did want to give y'all the option of having it both ways. I can easily patch it to break compatibility and be "correct". |
In addition, if we expose mocha.addFile(...)
mocha.loadFiles().then(() => mocha.run()).then(() => console.log('Done') |
@giltayar thank you. mocha.addFile(...)
mocha.loadFiles().then(() => mocha.run(() => console.log('Done'))) |
exports.singleRun = (mocha, {exit}, fileCollectParams) => {
const files = collectFiles(fileCollectParams);
debug('running tests with files', files);
mocha.files = files;
return mocha.run(exit ? exitMocha : exitMochaLater);
}; Couldn't we implement it here, even as
|
@juergba yes, if we take out Any in any case, As for your idea of I say this: the correct solution is to make |
I prefer the clean way, @demurgos @maxnordlund I need some help with this one and would highly appreciate your input. |
@juergba I will submit to the decision of my elders. 😀 Once you decide, I'll do the switch. It'll probably take no more than an hour or two. Note that there are two decisions to make:
|
I know I'm probably blocking this, but I expect I can get to review it in about 10 days. |
THANK YOU @giltayar !! This is most definitely not just a 'nice to have' feature... can't believe that was added as a tag. @boneskull Any update? Cheers! |
@giltayar IMO, zalgo is bad. A third option would be to leave
So... Promises. Because Mocha doesn't use any Promises internally (prior to this PR), we'll need to ensure Mocha's behavior on unhandled rejection is sane. Mocha has well-defined behavior around how it traps and handles uncaught exceptions. Rejection handling doesn't need to be the same as this, because it might not make sense, but if it's different, that behavior should be defined and documented. If you're interested in this change, feel free to implement, but I can also run with this PR and get it over the finish line if you don't have time. Also what does @juergba think of this "new method" idea? |
Regarding "major" or "minor", I don't really care either way, but I still would rather not introduce a breaking API change lacking any clear necessity. If we can side-step the existing API for programmatic users, then IMO that's the best solution. |
@giltayar using Mocha programmatically: mocha.run(failures => process.exitCode = failures ? 1 : 0)
.on('suite end', suite =>console.dir(suite)); There is no way to register event listeners on the runner instance when using |
@giltayar I did some testing on the copy-esm branch.
mocha.addFile('../test.js');
mocha.loadFilesAsync()
.then(() => mocha.run(failures => process.exitCode = failures ? 1 : 0)
.on('suite end', function(suite) {
console.dir(suite);
})
);
|
I tried out this prerelease and ran into some problems/complexity. StoryI'm interested in switching to mocha from jest because it supports native modules and I'm using an electron/React/sqlite stack for the productivity app I'm building. I'm already using type: "module" because I wanted a consistent import-style across my code and React convention is ESM. Technical background
ProblemNode doesn't throw Solution
Aside: Many modules don't support named imports properly despite their documentationDocumentation suggests things like |
return require(file); | ||
} catch (err) { | ||
if (err.code === 'ERR_REQUIRE_ESM') { | ||
return import(url.pathToFileURL(file)); |
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.
Node doesn't throw ERR_REQUIRE_ESM for .jsx files, instead throws SyntaxError: Cannot use import statement outside a module:
https://github.com/nodejs/node/blob/v13.8.0/lib/internal/modules/cjs/loader.js#L1161
I found a hacky workaround (link also provides context on how I ran into this problem).
Could you check package.json
and import by default if the requiring package has set type: "module"
? That wouldn't solve this problem for everyone but would for me, and seems correct regardless.
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.
@emma-borhanian this issue is outside of this PR's scope, I would like to get it merged as is.
Afterwards the ESM implementation will grow and we are open to suggestions. But IMO checking for the nearest package.json
(for each test file) is too expensive and actually Node's job.
844991d
to
ef8422c
Compare
@giltayar I rebased and resolved conflicts. |
@giltayar I removed
|
@giltayar I pushed some changes due to code review and make-happy coveralls. |
5731b7a
to
68736ef
Compare
@juergba - I saw what you did with removing All in all, I went over the changes you did, and they make sense. Thanks for the comprehensive review and changes! |
@giltayar re Can I squash my commits into yours? You remain the owner this way, otherwise I will merge two separate commits. Edit: do you agree with |
@juergba - you can squash, thanks. And, yes, semver-minor makes sense. |
68736ef
to
e1728f8
Compare
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.
@giltayar thank you!
Lgtm, only PR's description could need some attention.
Description of the Change
This PR adds support for Node.js's native ESM (ES modules) support.
Specifically, up till now, Mocha could only load test files that were CommonJS,
and this PR enables it to load test files that are ES modules,
and thus enable the test files to import stuff from other ES modules
(not to mention other goodies coming up the line, like enabling top-level await, which is an ESM only JS feature).
Note that this support is for Node.js only, and has no connection to browser ESM support.
Also, prior to Node v12, there was no or quirky support for ESM, so this support is only for Node v12 and above.
Alternate Designs
This is not a trivial change. The main problem is that loading an ESM is an async operation, and yet
the call path to
Mocha.prototype.loadFiles
is a sync one. Changing the call path would necessitate asemver-major change, which I'm not sure I have the ability to push, so I decided on a design
that keeps the signatures of all the functions as they are, unless there is an ESM test file to load, by which
time they morph to async functions. So their return type in the signature looks
something like
Runnner|Promise<Runner>
, whereRunner
is for cases where there are no ESM test files,and
Promise<Runner>
is for the case there areThis is not a good solution, but it enables us to push this change without it being semver-major, and once
there is a decision to change to semver-major, the signature change and code change that does this is trivial.
If y'all decide that this PR can change the major version of Mocha, I can fix it so that the signatures
change and the weirdness goes away.
Another not insignificant change was the error path. In some cases, errors found by Mocha were synchronous, i.e.
they occur during the
yargs.parse
phase, which throws the error. But, because the call path toloadFiles
couldbe asynchronous, I changed the command handler (in
lib/cli/run.js
) to be async (i.e to return aPromise
).This means that errors (such as bad tap versions) are no more synchronous, and will always arrive at the
fail
yargs handler (inlib/cli.js
). To make them look like the old errors, I changed the codethere. No tests fail now, so it seems OK, but please check this in the review.
Why should this be in core?
I'm assuming this is obvious: it has to change
Mocha.prototype.loadFiles
to be async. There is theesm
module that simulates ESM in userland, but native support has to be async. And native support is crucial if the developer's code is running using Node's native ESM support.Benefits
Developers starting to write Node.js code in ES modules will benefit by being able to write their tests using
ESM too, and not have to dynamically import their ES modules using dynamic import (aka
await import
), which is a hassle and a kludge.Possible Drawbacks
This may impact code using Mocha's API, as we are changing the signature (even if not in the short run,
definitely in the long run). Also, error handling has subtly changed, and this may impact error handling reporting
in ways I cannot know.
Applicable issues
This is currently an enhancement (minor release), that should be turned in the future into a semver-major,
as described in the "Alternate Designs" section.
Limitations