Skip to content

Commit 9b4435d

Browse files
authored
Import-first loading of test files (#4635)
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.
1 parent 1c4e623 commit 9b4435d

File tree

3 files changed

+52
-25
lines changed

3 files changed

+52
-25
lines changed

docs/index.md

+3-10
Original file line numberDiff line numberDiff line change
@@ -1065,7 +1065,6 @@ Require a module before loading the user interface or test files. This is useful
10651065

10661066
- Test harnesses
10671067
- Assertion libraries that augment built-ins or global scope (such as [should.js][npm-should.js])
1068-
- Instant ECMAScript modules via [esm][npm-esm]
10691068
- 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.
10701069

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

2037-
> 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
2038-
> Mocha. From version 13.2.0 of Node.js, you can use ES modules without any flags.
2039-
> (Mocha _will_ load ESM even in Node v10, but this is not officially supported. Use at your own risk.)
2040-
20412036
### Current Limitations
20422037

2043-
Node.JS native ESM support still has status: **Stability: 1 - Experimental**
2044-
20452038
- [Watch mode](#-watch-w) does not support ES Module test files
20462039
- [Custom reporters](#third-party-reporters) and [custom interfaces](#interfaces)
20472040
can only be CommonJS files
20482041
- [Configuration file](#configuring-mocha-nodejs) can only be a CommonJS file (`.mocharc.js` or `.mocharc.cjs`)
2049-
- When using module-level mocks via libs like `proxyquire`, `rewiremock` or `rewire`, hold off on using ES modules for your test files
2050-
- Node.JS native ESM support does not work with [esm][npm-esm] module
2042+
- When using module-level mocks via libs like `proxyquire`, `rewiremock` or `rewire`,
2043+
hold off on using ES modules for your test files. You can switch to using `testdouble`,
2044+
which does support ESM.
20512045

20522046
## Running Mocha in the Browser
20532047

@@ -2426,7 +2420,6 @@ or the [source](https://github.com/mochajs/mocha/blob/master/lib/mocha.js).
24262420
[npm]: https://npmjs.org/
24272421
[npm-babel-register]: https://npm.im/@babel/register
24282422
[npm-chai-as-promised]: https://www.npmjs.com/package/chai-as-promised
2429-
[npm-esm]: https://npm.im/esm
24302423
[npm-glob]: https://www.npmjs.com/package/glob
24312424
[npm-growl]: https://npm.im/growl
24322425
[npm-mocha-lcov-reporter]: https://npm.im/mocha-lcov-reporter

lib/esm-utils.js

+48-14
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,56 @@ const formattedImport = async file => {
3030
return import(file);
3131
};
3232

33-
exports.requireOrImport = async file => {
33+
const hasStableEsmImplementation = (() => {
34+
const [major, minor] = process.version.split('.');
35+
// ESM is stable from v12.22.0 onward
36+
// https://nodejs.org/api/esm.html#esm_modules_ecmascript_modules
37+
return parseInt(major.slice(1), 10) > 12 || parseInt(minor, 10) >= 22;
38+
})();
39+
40+
exports.requireOrImport = hasStableEsmImplementation
41+
? async file => {
42+
if (path.extname(file) === '.mjs') {
43+
return formattedImport(file);
44+
}
45+
try {
46+
return dealWithExports(await formattedImport(file));
47+
} catch (err) {
48+
if (
49+
err.code === 'ERR_MODULE_NOT_FOUND' ||
50+
err.code === 'ERR_UNKNOWN_FILE_EXTENSION'
51+
) {
52+
return require(file);
53+
} else {
54+
throw err;
55+
}
56+
}
57+
}
58+
: implementationOfRequireOrImportForUnstableEsm;
59+
60+
function dealWithExports(module) {
61+
if (module.default) {
62+
return module.default;
63+
} else {
64+
return {...module, default: undefined};
65+
}
66+
}
67+
68+
exports.loadFilesAsync = async (files, preLoadFunc, postLoadFunc) => {
69+
for (const file of files) {
70+
preLoadFunc(file);
71+
const result = await exports.requireOrImport(path.resolve(file));
72+
postLoadFunc(file, result);
73+
}
74+
};
75+
76+
/* istanbul ignore next */
77+
async function implementationOfRequireOrImportForUnstableEsm(file) {
3478
if (path.extname(file) === '.mjs') {
3579
return formattedImport(file);
3680
}
37-
// This is currently the only known way of figuring out whether a file is CJS or ESM.
38-
// If Node.js or the community establish a better procedure for that, we can fix this code.
39-
// Another option here would be to always use `import()`, as this also supports CJS, but I would be
40-
// wary of using it for _all_ existing test files, till ESM is fully stable.
81+
// This is currently the only known way of figuring out whether a file is CJS or ESM in
82+
// Node.js that doesn't necessitate calling `import` first.
4183
try {
4284
return require(file);
4385
} catch (err) {
@@ -47,12 +89,4 @@ exports.requireOrImport = async file => {
4789
throw err;
4890
}
4991
}
50-
};
51-
52-
exports.loadFilesAsync = async (files, preLoadFunc, postLoadFunc) => {
53-
for (const file of files) {
54-
preLoadFunc(file);
55-
const result = await exports.requireOrImport(path.resolve(file));
56-
postLoadFunc(file, result);
57-
}
58-
};
92+
}

test/integration/fixtures/exit.fixture.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@ var net = require('net');
44

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

0 commit comments

Comments
 (0)