Skip to content

Commit

Permalink
Import-first loading of test files (#4635)
Browse files Browse the repository at this point in the history
First try ESM, and only if that fails, load test file using require.
This enables an ESM loader to have first dibs at transforming the file.
  • Loading branch information
giltayar committed May 30, 2021
1 parent 1c4e623 commit 9b4435d
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 25 deletions.
13 changes: 3 additions & 10 deletions docs/index.md
Expand Up @@ -1065,7 +1065,6 @@ Require a module before loading the user interface or test files. This is useful

- Test harnesses
- Assertion libraries that augment built-ins or global scope (such as [should.js][npm-should.js])
- Instant ECMAScript modules via [esm][npm-esm]
- Compilers such as Babel via [@babel/register][npm-babel-register] or TypeScript via [ts-node][npm-ts-node] (using `--require ts-node/register`). See [Babel][example-babel] or [TypeScript][example-typescript] working examples.

Modules required in this manner are expected to do work synchronously; Mocha won't wait for async tasks in a required module to finish.
Expand Down Expand Up @@ -2034,20 +2033,15 @@ this means either ending the file with a `.mjs` extension, or, if you want to us
adding `"type": "module"` to your `package.json`.
More information can be found in the [Node.js documentation](https://nodejs.org/api/esm.html).

> Mocha supports ES modules only from Node.js v12.11.0 and above. To enable this in versions smaller than 13.2.0, you need to add `--experimental-modules` when running
> Mocha. From version 13.2.0 of Node.js, you can use ES modules without any flags.
> (Mocha _will_ load ESM even in Node v10, but this is not officially supported. Use at your own risk.)
### Current Limitations

Node.JS native ESM support still has status: **Stability: 1 - Experimental**

- [Watch mode](#-watch-w) does not support ES Module test files
- [Custom reporters](#third-party-reporters) and [custom interfaces](#interfaces)
can only be CommonJS files
- [Configuration file](#configuring-mocha-nodejs) can only be a CommonJS file (`.mocharc.js` or `.mocharc.cjs`)
- When using module-level mocks via libs like `proxyquire`, `rewiremock` or `rewire`, hold off on using ES modules for your test files
- Node.JS native ESM support does not work with [esm][npm-esm] module
- When using module-level mocks via libs like `proxyquire`, `rewiremock` or `rewire`,
hold off on using ES modules for your test files. You can switch to using `testdouble`,
which does support ESM.

## Running Mocha in the Browser

Expand Down Expand Up @@ -2426,7 +2420,6 @@ or the [source](https://github.com/mochajs/mocha/blob/master/lib/mocha.js).
[npm]: https://npmjs.org/
[npm-babel-register]: https://npm.im/@babel/register
[npm-chai-as-promised]: https://www.npmjs.com/package/chai-as-promised
[npm-esm]: https://npm.im/esm
[npm-glob]: https://www.npmjs.com/package/glob
[npm-growl]: https://npm.im/growl
[npm-mocha-lcov-reporter]: https://npm.im/mocha-lcov-reporter
Expand Down
62 changes: 48 additions & 14 deletions lib/esm-utils.js
Expand Up @@ -30,14 +30,56 @@ const formattedImport = async file => {
return import(file);
};

exports.requireOrImport = async file => {
const hasStableEsmImplementation = (() => {
const [major, minor] = process.version.split('.');
// ESM is stable from v12.22.0 onward
// https://nodejs.org/api/esm.html#esm_modules_ecmascript_modules
return parseInt(major.slice(1), 10) > 12 || parseInt(minor, 10) >= 22;
})();

exports.requireOrImport = hasStableEsmImplementation
? async file => {
if (path.extname(file) === '.mjs') {
return formattedImport(file);
}
try {
return dealWithExports(await formattedImport(file));
} catch (err) {
if (
err.code === 'ERR_MODULE_NOT_FOUND' ||
err.code === 'ERR_UNKNOWN_FILE_EXTENSION'
) {
return require(file);
} else {
throw err;
}
}
}
: implementationOfRequireOrImportForUnstableEsm;

function dealWithExports(module) {
if (module.default) {
return module.default;
} else {
return {...module, default: undefined};
}
}

exports.loadFilesAsync = async (files, preLoadFunc, postLoadFunc) => {
for (const file of files) {
preLoadFunc(file);
const result = await exports.requireOrImport(path.resolve(file));
postLoadFunc(file, result);
}
};

/* istanbul ignore next */
async function implementationOfRequireOrImportForUnstableEsm(file) {
if (path.extname(file) === '.mjs') {
return formattedImport(file);
}
// This is currently the only known way of figuring out whether a file is CJS or ESM.
// If Node.js or the community establish a better procedure for that, we can fix this code.
// Another option here would be to always use `import()`, as this also supports CJS, but I would be
// wary of using it for _all_ existing test files, till ESM is fully stable.
// This is currently the only known way of figuring out whether a file is CJS or ESM in
// Node.js that doesn't necessitate calling `import` first.
try {
return require(file);
} catch (err) {
Expand All @@ -47,12 +89,4 @@ exports.requireOrImport = async file => {
throw err;
}
}
};

exports.loadFilesAsync = async (files, preLoadFunc, postLoadFunc) => {
for (const file of files) {
preLoadFunc(file);
const result = await exports.requireOrImport(path.resolve(file));
postLoadFunc(file, result);
}
};
}
2 changes: 1 addition & 1 deletion test/integration/fixtures/exit.fixture.js
Expand Up @@ -4,5 +4,5 @@ var net = require('net');

it('should hang when --no-exit used', function (done) {
var server = net.createServer();
server.listen(55555, done);
server.listen(55554, done);
});

0 comments on commit 9b4435d

Please sign in to comment.