From 990d9b92eeddf438152012c915a31a8f8bf49988 Mon Sep 17 00:00:00 2001 From: Wei-An Yen Date: Wed, 17 Mar 2021 04:59:14 +0800 Subject: [PATCH] feat(jest-util): add requireOrImportModule util for importing CJS or ESM (#11199) --- .../src/readConfigFileAndSetRootDir.ts | 30 ++--------- .../jest-transform/src/ScriptTransformer.ts | 27 ++-------- packages/jest-util/src/index.ts | 1 + .../jest-util/src/requireOrImportModule.ts | 50 +++++++++++++++++++ 4 files changed, 58 insertions(+), 50 deletions(-) create mode 100644 packages/jest-util/src/requireOrImportModule.ts diff --git a/packages/jest-config/src/readConfigFileAndSetRootDir.ts b/packages/jest-config/src/readConfigFileAndSetRootDir.ts index fe1ba50935ce..508b68e94519 100644 --- a/packages/jest-config/src/readConfigFileAndSetRootDir.ts +++ b/packages/jest-config/src/readConfigFileAndSetRootDir.ts @@ -6,11 +6,10 @@ */ import * as path from 'path'; -import {pathToFileURL} from 'url'; import * as fs from 'graceful-fs'; import type {Register} from 'ts-node'; import type {Config} from '@jest/types'; -import {interopRequireDefault} from 'jest-util'; +import {interopRequireDefault, requireOrImportModule} from 'jest-util'; import { JEST_CONFIG_EXT_JSON, JEST_CONFIG_EXT_TS, @@ -35,33 +34,10 @@ export default async function readConfigFileAndSetRootDir( if (isTS) { configObject = await loadTSConfigFile(configPath); } else { - configObject = require(configPath); + configObject = await requireOrImportModule(configPath); } } catch (error) { - if (error.code === 'ERR_REQUIRE_ESM') { - try { - const configUrl = pathToFileURL(configPath); - - // node `import()` supports URL, but TypeScript doesn't know that - const importedConfig = await import(configUrl.href); - - if (!importedConfig.default) { - throw new Error( - `Jest: Failed to load mjs config file ${configPath} - did you use a default export?`, - ); - } - - configObject = importedConfig.default; - } catch (innerError) { - if (innerError.message === 'Not supported') { - throw new Error( - `Jest: Your version of Node does not support dynamic import - please enable it or use a .cjs file extension for file ${configPath}`, - ); - } - - throw innerError; - } - } else if (isJSON) { + if (isJSON) { throw new Error( `Jest: Failed to parse config file ${configPath}\n` + ` ${jsonlint.errors(fs.readFileSync(configPath, 'utf8'))}`, diff --git a/packages/jest-transform/src/ScriptTransformer.ts b/packages/jest-transform/src/ScriptTransformer.ts index 249f72b43188..3eac1012423b 100644 --- a/packages/jest-transform/src/ScriptTransformer.ts +++ b/packages/jest-transform/src/ScriptTransformer.ts @@ -7,7 +7,6 @@ import {createHash} from 'crypto'; import * as path from 'path'; -import {pathToFileURL} from 'url'; import {transformSync as babelTransform} from '@babel/core'; // @ts-expect-error: should just be `require.resolve`, but the tests mess that up import babelPluginIstanbul from 'babel-plugin-istanbul'; @@ -23,6 +22,7 @@ import { createDirectory, interopRequireDefault, isPromise, + requireOrImportModule, tryRealpath, } from 'jest-util'; import handlePotentialSyntaxError from './enhanceUnexpectedTokenMessage'; @@ -252,28 +252,9 @@ class ScriptTransformer { await Promise.all( this._config.transform.map( async ([, transformPath, transformerConfig]) => { - let transformer: Transformer; - - try { - transformer = interopRequireDefault(require(transformPath)).default; - } catch (error) { - if (error.code === 'ERR_REQUIRE_ESM') { - const configUrl = pathToFileURL(transformPath); - - // node `import()` supports URL, but TypeScript doesn't know that - const importedConfig = await import(configUrl.href); - - if (!importedConfig.default) { - throw new Error( - `Jest: Failed to load ESM transformer at ${transformPath} - did you use a default export?`, - ); - } - - transformer = importedConfig.default; - } else { - throw error; - } - } + let transformer: Transformer = await requireOrImportModule( + transformPath, + ); if (!transformer) { throw new TypeError('Jest: a transform must export something.'); diff --git a/packages/jest-util/src/index.ts b/packages/jest-util/src/index.ts index df75ff0548b9..bbfcbbe85ca0 100644 --- a/packages/jest-util/src/index.ts +++ b/packages/jest-util/src/index.ts @@ -23,3 +23,4 @@ export * as preRunMessage from './preRunMessage'; export {default as pluralize} from './pluralize'; export {default as formatTime} from './formatTime'; export {default as tryRealpath} from './tryRealpath'; +export {default as requireOrImportModule} from './requireOrImportModule'; diff --git a/packages/jest-util/src/requireOrImportModule.ts b/packages/jest-util/src/requireOrImportModule.ts new file mode 100644 index 000000000000..684a114f12b1 --- /dev/null +++ b/packages/jest-util/src/requireOrImportModule.ts @@ -0,0 +1,50 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import {isAbsolute} from 'path'; +import {pathToFileURL} from 'url'; +import type {Config} from '@jest/types'; +import interopRequireDefault from './interopRequireDefault'; + +export default async function requireOrImportModule( + filePath: Config.Path, +): Promise { + let module: T; + if (!isAbsolute(filePath) && filePath[0] === '.') { + throw new Error(`Jest: requireOrImportModule path must be absolute`); + } + try { + module = interopRequireDefault(require(filePath)).default; + } catch (error) { + if (error.code === 'ERR_REQUIRE_ESM') { + try { + const configUrl = pathToFileURL(filePath); + + // node `import()` supports URL, but TypeScript doesn't know that + const importedConfig = await import(configUrl.href); + + if (!importedConfig.default) { + throw new Error( + `Jest: Failed to load ESM at ${filePath} - did you use a default export?`, + ); + } + + module = importedConfig.default; + } catch (innerError) { + if (innerError.message === 'Not supported') { + throw new Error( + `Jest: Your version of Node does not support dynamic import - please enable it or use a .cjs file extension for file ${filePath}`, + ); + } + throw innerError; + } + } else { + throw error; + } + } + return module; +}