| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158 |
- const path = require('node:path');
- const url = require('node:url');
- const debug = require('debug')('mocha:esm-utils');
- const forward = x => x;
- const formattedImport = async (file, esmDecorator = forward) => {
- if (path.isAbsolute(file)) {
- try {
- return await exports.doImport(esmDecorator(url.pathToFileURL(file)));
- } catch (err) {
- // This is a hack created because ESM in Node.js (at least in Node v15.5.1) does not emit
- // the location of the syntax error in the error thrown.
- // This is problematic because the user can't see what file has the problem,
- // so we add the file location to the error.
- // TODO: remove once Node.js fixes the problem.
- if (
- err instanceof SyntaxError &&
- err.message &&
- err.stack &&
- !err.stack.includes(file)
- ) {
- const newErrorWithFilename = new SyntaxError(err.message);
- newErrorWithFilename.stack = err.stack.replace(
- /^SyntaxError/,
- `SyntaxError[ @${file} ]`
- );
- throw newErrorWithFilename;
- }
- throw err;
- }
- }
- return exports.doImport(esmDecorator(file));
- };
- exports.doImport = async file => import(file);
- // When require(esm) is not available, we need to use `import()` to load ESM modules.
- // In this case, CJS modules are loaded using `import()` as well. When Node.js' builtin
- // TypeScript support is enabled, `.ts` files are also loaded using `import()`, and
- // compilers based on `require.extensions` are omitted.
- const tryImportAndRequire = async (file, esmDecorator) => {
- if (path.extname(file) === '.mjs') {
- return formattedImport(file, esmDecorator);
- }
- try {
- return dealWithExports(await formattedImport(file, esmDecorator));
- } catch (err) {
- if (
- err.code === 'ERR_MODULE_NOT_FOUND' ||
- err.code === 'ERR_UNKNOWN_FILE_EXTENSION' ||
- err.code === 'ERR_UNSUPPORTED_DIR_IMPORT'
- ) {
- try {
- // Importing a file usually works, but the resolution of `import` is the ESM
- // resolution algorithm, and not the CJS resolution algorithm. We may have
- // failed because we tried the ESM resolution, so we try to `require` it.
- return require(file);
- } catch (requireErr) {
- if (
- requireErr.code === 'ERR_REQUIRE_ESM' ||
- (requireErr instanceof SyntaxError &&
- requireErr
- .toString()
- .includes('Cannot use import statement outside a module'))
- ) {
- // ERR_REQUIRE_ESM happens when the test file is a JS file, but via type:module is actually ESM,
- // AND has an import to a file that doesn't exist.
- // This throws an `ERR_MODULE_NOT_FOUND` error above,
- // and when we try to `require` it here, it throws an `ERR_REQUIRE_ESM`.
- // What we want to do is throw the original error (the `ERR_MODULE_NOT_FOUND`),
- // and not the `ERR_REQUIRE_ESM` error, which is a red herring.
- //
- // SyntaxError happens when in an edge case: when we're using an ESM loader that loads
- // a `test.ts` file (i.e. unrecognized extension), and that file includes an unknown
- // import (which throws an ERR_MODULE_NOT_FOUND). `require`-ing it will throw the
- // syntax error, because we cannot require a file that has `import`-s.
- throw err;
- } else {
- throw requireErr;
- }
- }
- } else {
- throw err;
- }
- }
- };
- // Utilize Node.js' require(esm) feature to load ESM modules
- // and CJS modules. This keeps the require() features like `require.extensions`
- // and `require.cache` effective, while allowing us to load ESM modules
- // and CJS modules in the same way.
- const requireModule = async (file, esmDecorator) => {
- if (path.extname(file) === '.mjs') {
- return formattedImport(file, esmDecorator);
- }
- try {
- return require(file);
- } catch (requireErr) {
- debug('requireModule caught err: %O', requireErr.message);
- try {
- return dealWithExports(await formattedImport(file, esmDecorator));
- } catch (importErr) {
- // If a --require module throws in a Node.js version that doesn't yet support .ts files,
- // the fallback import() will throw an uninformative error about the file extension.
- // What we actually care about is the original require() error.
- // See: https://github.com/mochajs/mocha/issues/5393
- if (
- /\.(cts|mts|ts)$/.test(file) &&
- importErr.code === 'ERR_UNKNOWN_FILE_EXTENSION'
- ) {
- throw requireErr;
- }
- // Similarly, for an exports/imports mismatch such as a missing 'default',
- // the require() error will be more informative for users.
- // See: https://github.com/mochajs/mocha/issues/5411
- if (importErr.code === 'ERR_INTERNAL_ASSERTION') {
- throw requireErr;
- }
- throw importErr;
- }
- }
- };
- // We only assign this `requireOrImport` function once based on Node version
- // We check for file extensions in `requireModule` and `tryImportAndRequire`
- debug('assigning requireOrImport, require_module === %O', process.features.require_module);
- if (process.features.require_module) {
- exports.requireOrImport = requireModule;
- } else {
- exports.requireOrImport = tryImportAndRequire;
- }
- function dealWithExports(module) {
- if (module.default) {
- return module.default;
- } else {
- return {...module, default: undefined};
- }
- }
- exports.loadFilesAsync = async (
- files,
- preLoadFunc,
- postLoadFunc,
- esmDecorator
- ) => {
- for (const file of files) {
- preLoadFunc(file);
- const result = await exports.requireOrImport(
- path.resolve(file),
- esmDecorator
- );
- postLoadFunc(file, result);
- }
- };
|