From 9b4435db8447bf681e7824b31902452f55678adb Mon Sep 17 00:00:00 2001 From: Gil Tayar Date: Sun, 30 May 2021 18:19:20 +0300 Subject: [PATCH] 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. --- docs/index.md | 13 ++--- lib/esm-utils.js | 62 ++++++++++++++++++----- test/integration/fixtures/exit.fixture.js | 2 +- 3 files changed, 52 insertions(+), 25 deletions(-) diff --git a/docs/index.md b/docs/index.md index 953f7bfef9..1cc8047687 100644 --- a/docs/index.md +++ b/docs/index.md @@ -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. @@ -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 @@ -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 diff --git a/lib/esm-utils.js b/lib/esm-utils.js index eebeb9322b..14a9c28fa8 100644 --- a/lib/esm-utils.js +++ b/lib/esm-utils.js @@ -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) { @@ -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); - } -}; +} diff --git a/test/integration/fixtures/exit.fixture.js b/test/integration/fixtures/exit.fixture.js index d369fb311b..0a5e5eb900 100644 --- a/test/integration/fixtures/exit.fixture.js +++ b/test/integration/fixtures/exit.fixture.js @@ -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); });