From 7aa6e536fb518ed68feee98f8c263002a3a921a6 Mon Sep 17 00:00:00 2001 From: "Wei-An, Yen" Date: Mon, 15 Mar 2021 00:58:55 +0800 Subject: [PATCH 1/8] feat(jest-util): add importModule for importing CJS or ESM --- packages/jest-util/src/importModule.ts | 27 ++++++++++++++++++++++++++ packages/jest-util/src/index.ts | 1 + 2 files changed, 28 insertions(+) create mode 100644 packages/jest-util/src/importModule.ts diff --git a/packages/jest-util/src/importModule.ts b/packages/jest-util/src/importModule.ts new file mode 100644 index 000000000000..f74d20e37f26 --- /dev/null +++ b/packages/jest-util/src/importModule.ts @@ -0,0 +1,27 @@ +import {pathToFileURL} from 'url'; +import interopRequireDefault from './interopRequireDefault'; + +export default async function importModule(filePath: string): Promise { + let module: T; + try { + module = interopRequireDefault(require(filePath)).default; + } catch (error) { + if (error.code === 'ERR_REQUIRE_ESM') { + 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; + } else { + throw error; + } + } + return module; +} diff --git a/packages/jest-util/src/index.ts b/packages/jest-util/src/index.ts index df75ff0548b9..d7f1b68dc0ae 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 importModule} from './importModule'; From f6cf30847a558d63f68bd822f6d96691fe575c85 Mon Sep 17 00:00:00 2001 From: "Wei-An, Yen" Date: Mon, 15 Mar 2021 01:04:14 +0800 Subject: [PATCH 2/8] refactor(jest-transform): refactor import transformer logic to importModule --- .../jest-transform/src/ScriptTransformer.ts | 25 ++----------------- 1 file changed, 2 insertions(+), 23 deletions(-) diff --git a/packages/jest-transform/src/ScriptTransformer.ts b/packages/jest-transform/src/ScriptTransformer.ts index 249f72b43188..2982322b3e50 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'; @@ -21,6 +20,7 @@ import type {Config} from '@jest/types'; import HasteMap from 'jest-haste-map'; import { createDirectory, + importModule, interopRequireDefault, isPromise, tryRealpath, @@ -252,28 +252,7 @@ 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 importModule(transformPath); if (!transformer) { throw new TypeError('Jest: a transform must export something.'); From b464cff3b8d8a0d6c3f07e353e1e8a1f43bd6e01 Mon Sep 17 00:00:00 2001 From: "Wei-An, Yen" Date: Mon, 15 Mar 2021 23:47:30 +0800 Subject: [PATCH 3/8] rename importModule to requireOrImportModule --- packages/jest-transform/src/ScriptTransformer.ts | 6 ++++-- packages/jest-util/src/index.ts | 2 +- .../src/{importModule.ts => requireOrImportModule.ts} | 4 +++- 3 files changed, 8 insertions(+), 4 deletions(-) rename packages/jest-util/src/{importModule.ts => requireOrImportModule.ts} (88%) diff --git a/packages/jest-transform/src/ScriptTransformer.ts b/packages/jest-transform/src/ScriptTransformer.ts index 2982322b3e50..3eac1012423b 100644 --- a/packages/jest-transform/src/ScriptTransformer.ts +++ b/packages/jest-transform/src/ScriptTransformer.ts @@ -20,9 +20,9 @@ import type {Config} from '@jest/types'; import HasteMap from 'jest-haste-map'; import { createDirectory, - importModule, interopRequireDefault, isPromise, + requireOrImportModule, tryRealpath, } from 'jest-util'; import handlePotentialSyntaxError from './enhanceUnexpectedTokenMessage'; @@ -252,7 +252,9 @@ class ScriptTransformer { await Promise.all( this._config.transform.map( async ([, transformPath, transformerConfig]) => { - let transformer: Transformer = await importModule(transformPath); + 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 d7f1b68dc0ae..bbfcbbe85ca0 100644 --- a/packages/jest-util/src/index.ts +++ b/packages/jest-util/src/index.ts @@ -23,4 +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 importModule} from './importModule'; +export {default as requireOrImportModule} from './requireOrImportModule'; diff --git a/packages/jest-util/src/importModule.ts b/packages/jest-util/src/requireOrImportModule.ts similarity index 88% rename from packages/jest-util/src/importModule.ts rename to packages/jest-util/src/requireOrImportModule.ts index f74d20e37f26..7d749d2eb95c 100644 --- a/packages/jest-util/src/importModule.ts +++ b/packages/jest-util/src/requireOrImportModule.ts @@ -1,7 +1,9 @@ import {pathToFileURL} from 'url'; import interopRequireDefault from './interopRequireDefault'; -export default async function importModule(filePath: string): Promise { +export default async function requireOrImportModule( + filePath: string, +): Promise { let module: T; try { module = interopRequireDefault(require(filePath)).default; From cb22e4fd618ea6e084259f0a43ecd85c8358fbfc Mon Sep 17 00:00:00 2001 From: "Wei-An, Yen" Date: Mon, 15 Mar 2021 23:52:46 +0800 Subject: [PATCH 4/8] change filePath type to Config.Path --- packages/jest-util/src/requireOrImportModule.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/jest-util/src/requireOrImportModule.ts b/packages/jest-util/src/requireOrImportModule.ts index 7d749d2eb95c..4ecd2e0133bb 100644 --- a/packages/jest-util/src/requireOrImportModule.ts +++ b/packages/jest-util/src/requireOrImportModule.ts @@ -1,8 +1,9 @@ import {pathToFileURL} from 'url'; +import type {Config} from '@jest/types'; import interopRequireDefault from './interopRequireDefault'; export default async function requireOrImportModule( - filePath: string, + filePath: Config.Path, ): Promise { let module: T; try { From 4784cc717158e65e8e42fbb297d5227df174bc56 Mon Sep 17 00:00:00 2001 From: "Wei-An, Yen" Date: Tue, 16 Mar 2021 00:51:34 +0800 Subject: [PATCH 5/8] verify requireOrImportModule path must be absolute --- packages/jest-util/src/requireOrImportModule.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/jest-util/src/requireOrImportModule.ts b/packages/jest-util/src/requireOrImportModule.ts index 4ecd2e0133bb..bc8b32a97cc1 100644 --- a/packages/jest-util/src/requireOrImportModule.ts +++ b/packages/jest-util/src/requireOrImportModule.ts @@ -1,3 +1,4 @@ +import {isAbsolute} from 'path'; import {pathToFileURL} from 'url'; import type {Config} from '@jest/types'; import interopRequireDefault from './interopRequireDefault'; @@ -6,6 +7,9 @@ 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) { From 2884d0395741de6e95b3818aab4c489b5fc0e61c Mon Sep 17 00:00:00 2001 From: "Wei-An, Yen" Date: Tue, 16 Mar 2021 00:58:47 +0800 Subject: [PATCH 6/8] add copyright header --- packages/jest-util/src/requireOrImportModule.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/jest-util/src/requireOrImportModule.ts b/packages/jest-util/src/requireOrImportModule.ts index bc8b32a97cc1..c08051369938 100644 --- a/packages/jest-util/src/requireOrImportModule.ts +++ b/packages/jest-util/src/requireOrImportModule.ts @@ -1,3 +1,10 @@ +/** + * 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'; From 9eaf3d7eb4962891e0a3b7b7ae60c308c506d514 Mon Sep 17 00:00:00 2001 From: "Wei-An, Yen" Date: Tue, 16 Mar 2021 01:00:13 +0800 Subject: [PATCH 7/8] refactor(jest-config): refactor using requireOrImportModule for loading config --- .../src/readConfigFileAndSetRootDir.ts | 30 ++----------------- 1 file changed, 3 insertions(+), 27 deletions(-) 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'))}`, From 75479c85d54d1a711823d1fbf23710f32b49f6f1 Mon Sep 17 00:00:00 2001 From: "Wei-An, Yen" Date: Wed, 17 Mar 2021 02:38:11 +0800 Subject: [PATCH 8/8] throw error if not support dynamic import --- .../jest-util/src/requireOrImportModule.ts | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/packages/jest-util/src/requireOrImportModule.ts b/packages/jest-util/src/requireOrImportModule.ts index c08051369938..684a114f12b1 100644 --- a/packages/jest-util/src/requireOrImportModule.ts +++ b/packages/jest-util/src/requireOrImportModule.ts @@ -21,18 +21,27 @@ export default async function requireOrImportModule( module = interopRequireDefault(require(filePath)).default; } catch (error) { if (error.code === 'ERR_REQUIRE_ESM') { - const configUrl = pathToFileURL(filePath); + try { + const configUrl = pathToFileURL(filePath); - // node `import()` supports URL, but TypeScript doesn't know that - const importedConfig = await import(configUrl.href); + // 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?`, - ); - } + if (!importedConfig.default) { + throw new Error( + `Jest: Failed to load ESM at ${filePath} - did you use a default export?`, + ); + } - module = importedConfig.default; + 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; }