Skip to content
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

WIP: use dynamic import to load es module tests. #3253

Closed
wants to merge 8 commits into from

Conversation

harrysarson
Copy link
Contributor

Description of the Change

Uses the new (and as yet highly experimental) support in node for dynamically loading modules using const obj = await import('./file-name.mjs');.

I have added a new command line flag --es-modules which causes mocha to use import() instead of require() to load test files.

This allows tests to import obj from './somefile.mjs' without having to transpile tests before running them.

Obviously this only works in the very latest node version. This commit does not break mocha in node <= 8 unless the user passes the es-modules flag.

Currently running a script in node with --experimental-modules fails if the file does not have an extension which means that node --experimental-modules bin/_mocha files. From my understanding (see nodejs/node#18728) node v9.7 will be able to run scripts without the .js extension.

Alternate Designs

Current design works fine as long as users are happy to use a transpiler.

Why should this be in core?

Cannot be implemented outside of core.

Benefits

Tests can be written as es modules.

Possible Drawbacks

Current support for modules in node is experimental. It is likely that mocha will want to wait untill node starts properly supporting modules before merging this pr.

Applicable issues

#3006

Probably semver minor but could be major.

When the --es-modules flag is provided import() will be used to load
all test files.
node 9.6 will not allow script files without .js extensions when --experimental-modules
flag is passed. I think node 9.7 will allow this so this commit can be reverted then.
Node versions less than 9.6 do not support dynamic es imports. Currently
using instanbul to calculate codecoverage calls some script without a .js file extension
which causes the tests to fail. Hopefully in v9.7 this will be
fixed.
Adds an integration test that uses cjs modules but is called with
the --es-modules flag.
Dynamic imports are async and therefore errors need to be explicitly handled.
@boneskull
Copy link
Contributor

This seems to break AppVeyor's build, but why?

@boneskull boneskull added type: feature enhancement proposal semver-minor implementation requires increase of "minor" version number; "features" labels Mar 1, 2018
Copy link
Contributor

@boneskull boneskull left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. I'm not entirely sure what you intended we do with this PR.

.travis.yml Outdated
@@ -14,17 +14,7 @@ matrix:
fast_finish: true
include:
- node_js: '9'
env: TARGET=test.node COVERAGE=true
- node_js: '8'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can't just remove all of these from the build matrix...

@@ -10,7 +10,7 @@
const spawn = require('child_process').spawn;
const path = require('path');
const getOptions = require('./options');
const args = [path.join(__dirname, '_mocha')];
const args = [path.join(__dirname, '_mocha.js')];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is this necessary?

* Load registered files via es6 dynamic imports.
*
* Note: `eval` is used in this function or else mocha will not
* compile on older node versions.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand where this is supposed to work.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new import() operator allows ECMAScript Modules to be loaded dynamically. The hope was that tests written as modules (i.e. tests including import obj from './script.js' statements) can be loaded into Mocha using dynamic imports without having to first use a transpiler to convert ECMAScript Modules into common js modules.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right, but I'm unclear on why we're removing old versions of Node.js from the matrix.

Current support for modules in node is experimental. It is likely that mocha will want to wait untill node starts properly supporting modules before merging this pr.

if we're going to break backwards compatibility, then it'd have to wait until the oldest version of Node.js that supports modules is no longer maintained. that'll be a few years. so, that's why I'm trying to understand what I'm supposed to do with this...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I didn't mean to add that change to the pull request, I will revert it.

The idea was that mocha could add a new CLI flag which would cause mocha to use the new import() instead of require() to load test files.

This new CLI flag could only be used on the most recent versions of node but mocha would work normally if this flag was not set.

The tests fail because I added a test for the new flag and travis/appveyor runs this test on old versions of node, I didn't know how to work around that.

file = path.resolve(file);
suite.emit('pre-require', global, file, self);
return eval('import(file)')
.then(function (module) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably shouldn't use module

@demurgos
Copy link
Contributor

Why was this PR closed?

I am currently using Mocha together with @std/esm to test my .mjs files but this is suboptimal (edge cases around interop with cjs or code coverage). Now that dynamic imports are available it would be nice to be able to use ESM directly with Mocha.

@boneskull
Copy link
Contributor

Until modules are unflagged and/or no longer considered "experimental", I'm concerned about adopting something too soon. Specifically, when there's agreement in core around interop between require() and import, filename extensions, etc.--then it's probably safe to move. Too early, and it'll be expensive to fix any mistakes we made by jumping on it too soon.

@demurgos
Copy link
Contributor

Thanks for the reply: it's a fair reason.

I was worried that there was an issue with the PR preventing it from working. Still, it's good to know that it's a Node stability issue instead and that you are able to add support for it when possible.

@harrysarson
Copy link
Contributor Author

I was a bit optimistic with this PR, definately need to wait until modules are finalised in node.

The other problem, which is why my work was so hacky, is that the dynamic import() loads modules asynchronously whereas mocha currently loads tests synchronously (using require()) so it is not as simple as just switching one for the other.

@demurgos
Copy link
Contributor

demurgos commented Jun 25, 2018

Hi,
The modules discussion is still ongoing, but it seems that there's agreement that the imports will be async (ESM to ESM, ESM to CJS).

A possible way to prepare the support for ESM would be to force the current imports to be async by default: replace the resolution code from require("test.spec.js") to Promise.resolve(require("test.spec.js")).
Replacing the inner require by import would then be trivial.

Looking at the PR, it seems that most of the code is already async. I am not familiar enough with mocha's codebase so maybe my suggestion is not needed. I just hope that Mocha will be able to support ESM when available.

@harrysarson
Copy link
Contributor Author

@demurgos from memory it should be fairly straightforward to make that change

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
semver-minor implementation requires increase of "minor" version number; "features" type: feature enhancement proposal
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants