Skip to content

Commit

Permalink
feat(jest-util): add requireOrImportModule util for importing CJS or …
Browse files Browse the repository at this point in the history
…ESM (#11199)
  • Loading branch information
WeiAnAn committed Mar 16, 2021
1 parent 75e94cc commit 990d9b9
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 50 deletions.
30 changes: 3 additions & 27 deletions packages/jest-config/src/readConfigFileAndSetRootDir.ts
Expand Up @@ -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,
Expand All @@ -35,33 +34,10 @@ export default async function readConfigFileAndSetRootDir(
if (isTS) {
configObject = await loadTSConfigFile(configPath);
} else {
configObject = require(configPath);
configObject = await requireOrImportModule<any>(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'))}`,
Expand Down
27 changes: 4 additions & 23 deletions packages/jest-transform/src/ScriptTransformer.ts
Expand Up @@ -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';
Expand All @@ -23,6 +22,7 @@ import {
createDirectory,
interopRequireDefault,
isPromise,
requireOrImportModule,
tryRealpath,
} from 'jest-util';
import handlePotentialSyntaxError from './enhanceUnexpectedTokenMessage';
Expand Down Expand Up @@ -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.');
Expand Down
1 change: 1 addition & 0 deletions packages/jest-util/src/index.ts
Expand Up @@ -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';
50 changes: 50 additions & 0 deletions 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<T>(
filePath: Config.Path,
): Promise<T> {
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;
}

0 comments on commit 990d9b9

Please sign in to comment.