diff --git a/CHANGELOG.md b/CHANGELOG.md index b2a023fa86dd..2d617f54617b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ - `[jest-config]` [**BREAKING**] Set default display name color based on runner ([#8689](https://github.com/facebook/jest/pull/8689)) - `[jest-config]` Merge preset globals with project globals ([#9027](https://github.com/facebook/jest/pull/9027)) - `[jest-config]` Support `.cjs` config files ([#9291](https://github.com/facebook/jest/pull/9291)) +- `[jest-config]` [**BREAKING**] Support `.mjs` config files ([#9431](https://github.com/facebook/jest/pull/9431)) - `[jest-core]` Support reporters as default exports ([#9161](https://github.com/facebook/jest/pull/9161)) - `[jest-core]` Support `--findRelatedTests` paths case insensitivity on Windows ([#8900](https://github.com/facebook/jest/issues/8900)) - `[jest-diff]` Add options for colors and symbols ([#8841](https://github.com/facebook/jest/pull/8841)) diff --git a/babel.config.js b/babel.config.js index 1269fe3b0744..82b13d097f49 100644 --- a/babel.config.js +++ b/babel.config.js @@ -19,6 +19,22 @@ module.exports = { presets: ['@babel/preset-typescript'], test: /\.tsx?$/, }, + // we want this file to keep `import()`, so exclude the transform for it + { + plugins: ['@babel/plugin-syntax-dynamic-import'], + presets: [ + '@babel/preset-typescript', + [ + '@babel/preset-env', + { + exclude: ['@babel/plugin-proposal-dynamic-import'], + shippedProposals: true, + targets: {node: 8}, + }, + ], + ], + test: 'packages/jest-config/src/importMjs.ts', + }, ], plugins: [ ['@babel/plugin-transform-modules-commonjs', {allowTopLevelThis: true}], diff --git a/docs/Configuration.md b/docs/Configuration.md index 50e03f28b87a..9f0d18868b53 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -3,7 +3,7 @@ id: configuration title: Configuring Jest --- -Jest's configuration can be defined in the `package.json` file of your project, or through a `jest.config.js` file or through the `--config ` option. If you'd like to use your `package.json` to store Jest's config, the "jest" key should be used on the top level so Jest will know how to find your settings: +Jest's configuration can be defined in the `package.json` file of your project, or through a `jest.config.js` file or through the `--config ` option. If you'd like to use your `package.json` to store Jest's config, the `"jest"` key should be used on the top level so Jest will know how to find your settings: ```json { diff --git a/e2e/__tests__/cjsConfigFile.test.ts b/e2e/__tests__/cjsConfigFile.test.ts deleted file mode 100644 index 9c6b1345ef4e..000000000000 --- a/e2e/__tests__/cjsConfigFile.test.ts +++ /dev/null @@ -1,21 +0,0 @@ -/** - * 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 {json as runWithJson} from '../runJest'; - -test('reads config from cjs file', () => { - const {json, exitCode} = runWithJson('cjs-config', ['--show-config'], { - skipPkgJsonCheck: true, - }); - - expect(exitCode).toBe(0); - expect(json.configs).toHaveLength(1); - expect(json.configs[0].displayName).toEqual({ - color: 'white', - name: 'Config from cjs file', - }); -}); diff --git a/e2e/__tests__/esmConfigFile.test.ts b/e2e/__tests__/esmConfigFile.test.ts new file mode 100644 index 000000000000..dde3552cbd8b --- /dev/null +++ b/e2e/__tests__/esmConfigFile.test.ts @@ -0,0 +1,38 @@ +/** + * 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 {onNodeVersions} from '@jest/test-utils'; +import {json as runWithJson} from '../runJest'; + +test('reads config from cjs file', () => { + const {json, exitCode} = runWithJson('esm-config/cjs', ['--show-config'], { + skipPkgJsonCheck: true, + }); + + expect(exitCode).toBe(0); + expect(json.configs).toHaveLength(1); + expect(json.configs[0].displayName).toEqual({ + color: 'white', + name: 'Config from cjs file', + }); +}); + +// not unflagged for other versions yet. Update this range if that changes +onNodeVersions('^13.2.0', () => { + test('reads config from mjs file', () => { + const {json, exitCode} = runWithJson('esm-config/mjs', ['--show-config'], { + skipPkgJsonCheck: true, + }); + + expect(exitCode).toBe(0); + expect(json.configs).toHaveLength(1); + expect(json.configs[0].displayName).toEqual({ + color: 'white', + name: 'Config from mjs file', + }); + }); +}); diff --git a/e2e/cjs-config/__tests__/test.js b/e2e/esm-config/cjs/__tests__/test.js similarity index 100% rename from e2e/cjs-config/__tests__/test.js rename to e2e/esm-config/cjs/__tests__/test.js diff --git a/e2e/cjs-config/jest.config.cjs b/e2e/esm-config/cjs/jest.config.cjs similarity index 100% rename from e2e/cjs-config/jest.config.cjs rename to e2e/esm-config/cjs/jest.config.cjs diff --git a/e2e/cjs-config/package.json b/e2e/esm-config/cjs/package.json similarity index 100% rename from e2e/cjs-config/package.json rename to e2e/esm-config/cjs/package.json diff --git a/e2e/esm-config/mjs/__tests__/test.js b/e2e/esm-config/mjs/__tests__/test.js new file mode 100644 index 000000000000..2b4a7ced6f45 --- /dev/null +++ b/e2e/esm-config/mjs/__tests__/test.js @@ -0,0 +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. + */ + +test('dummy test', () => { + expect(1).toBe(1); +}); diff --git a/e2e/esm-config/mjs/jest.config.mjs b/e2e/esm-config/mjs/jest.config.mjs new file mode 100644 index 000000000000..3cec62f2d4ac --- /dev/null +++ b/e2e/esm-config/mjs/jest.config.mjs @@ -0,0 +1,11 @@ +/** + * 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. + */ + +export default { + displayName: 'Config from mjs file', + testEnvironment: 'node', +}; diff --git a/e2e/esm-config/mjs/package.json b/e2e/esm-config/mjs/package.json new file mode 100644 index 000000000000..586d4ca6b75c --- /dev/null +++ b/e2e/esm-config/mjs/package.json @@ -0,0 +1,3 @@ +{ + "jest": {} +} diff --git a/packages/jest-cli/src/init/__tests__/__snapshots__/init.test.js.snap b/packages/jest-cli/src/init/__tests__/__snapshots__/init.test.js.snap index 1ca7d79a90dc..bb0ef6886d5f 100644 --- a/packages/jest-cli/src/init/__tests__/__snapshots__/init.test.js.snap +++ b/packages/jest-cli/src/init/__tests__/__snapshots__/init.test.js.snap @@ -36,6 +36,15 @@ Object { } `; +exports[`init has-jest-config-file-mjs ask the user whether to override config or not user answered with "Yes" 1`] = ` +Object { + "initial": true, + "message": "It seems that you already have a jest configuration, do you want to override it?", + "name": "continue", + "type": "confirm", +} +`; + exports[`init project with package.json and no jest config all questions answered with answer: "No" should return the default configuration (an empty config) 1`] = ` "// For a detailed explanation regarding each configuration property, visit: // https://jestjs.io/docs/en/configuration.html diff --git a/packages/jest-cli/src/init/__tests__/fixtures/has_jest_config_file_mjs/jest.config.mjs b/packages/jest-cli/src/init/__tests__/fixtures/has_jest_config_file_mjs/jest.config.mjs new file mode 100644 index 000000000000..d291155b7a10 --- /dev/null +++ b/packages/jest-cli/src/init/__tests__/fixtures/has_jest_config_file_mjs/jest.config.mjs @@ -0,0 +1,8 @@ +/** + * 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. + */ + +export default {}; diff --git a/packages/jest-cli/src/init/__tests__/fixtures/has_jest_config_file_mjs/package.json b/packages/jest-cli/src/init/__tests__/fixtures/has_jest_config_file_mjs/package.json new file mode 100644 index 000000000000..0967ef424bce --- /dev/null +++ b/packages/jest-cli/src/init/__tests__/fixtures/has_jest_config_file_mjs/package.json @@ -0,0 +1 @@ +{} diff --git a/packages/jest-cli/src/init/__tests__/fixtures/type_module/package.json b/packages/jest-cli/src/init/__tests__/fixtures/type_module/package.json new file mode 100644 index 000000000000..a6893e57d0df --- /dev/null +++ b/packages/jest-cli/src/init/__tests__/fixtures/type_module/package.json @@ -0,0 +1,7 @@ +{ + "name": "type_module", + "scripts": { + "test": "different-test-runner" + }, + "type": "module" +} diff --git a/packages/jest-cli/src/init/__tests__/init.test.js b/packages/jest-cli/src/init/__tests__/init.test.js index 6a6768f83779..afc2acdca315 100644 --- a/packages/jest-cli/src/init/__tests__/init.test.js +++ b/packages/jest-cli/src/init/__tests__/init.test.js @@ -9,8 +9,11 @@ import * as fs from 'fs'; import * as path from 'path'; import prompts from 'prompts'; +import {constants} from 'jest-config'; import init from '../'; -import {JEST_CONFIG_EXT_ORDER} from '../constants'; +import {onNodeVersions} from '@jest/test-utils'; + +const {JEST_CONFIG_EXT_ORDER} = constants; jest.mock('prompts'); jest.mock('../../../../jest-config/build/getCacheDirectory', () => () => @@ -52,6 +55,22 @@ describe('init', () => { expect(evaluatedConfig).toEqual({}); }); + + onNodeVersions('^13.2.0', () => { + it('should generate empty config with mjs extension', async () => { + prompts.mockReturnValueOnce({}); + + await init(resolveFromFixture('type_module')); + + const writtenJestConfigFilename = fs.writeFileSync.mock.calls[0][0]; + const writtenJestConfig = fs.writeFileSync.mock.calls[0][1]; + + expect(writtenJestConfigFilename.endsWith('.mjs')).toBe(true); + + expect(typeof writtenJestConfig).toBe('string'); + expect(writtenJestConfig.split('\n')[3]).toBe('export default {'); + }); + }); }); describe('some questions answered with answer: "Yes"', () => { diff --git a/packages/jest-cli/src/init/constants.ts b/packages/jest-cli/src/init/constants.ts deleted file mode 100644 index b7c3ed5e25ff..000000000000 --- a/packages/jest-cli/src/init/constants.ts +++ /dev/null @@ -1,17 +0,0 @@ -/** - * 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. - */ - -export const PACKAGE_JSON = 'package.json'; -export const JEST_CONFIG_BASE_NAME = 'jest.config'; -export const JEST_CONFIG_EXT_CJS = '.cjs'; -export const JEST_CONFIG_EXT_JS = '.js'; -export const JEST_CONFIG_EXT_JSON = '.json'; -export const JEST_CONFIG_EXT_ORDER = Object.freeze([ - JEST_CONFIG_EXT_JS, - JEST_CONFIG_EXT_CJS, - JEST_CONFIG_EXT_JSON, -]); diff --git a/packages/jest-cli/src/init/generate_config_file.ts b/packages/jest-cli/src/init/generate_config_file.ts index f53393d3fbc4..93389b9d6e3c 100644 --- a/packages/jest-cli/src/init/generate_config_file.ts +++ b/packages/jest-cli/src/init/generate_config_file.ts @@ -31,7 +31,10 @@ const stringifyOption = ( ); }; -const generateConfigFile = (results: Record): string => { +const generateConfigFile = ( + results: Record, + generateEsm = false, +): string => { const {coverage, clearMocks, environment} = results; const overrides: Record = {}; @@ -75,7 +78,7 @@ const generateConfigFile = (results: Record): string => { return ( '// For a detailed explanation regarding each configuration property, visit:\n' + '// https://jestjs.io/docs/en/configuration.html\n\n' + - 'module.exports = {\n' + + (generateEsm ? 'export default {\n' : 'module.exports = {\n') + properties.join('\n') + '};\n' ); diff --git a/packages/jest-cli/src/init/index.ts b/packages/jest-cli/src/init/index.ts index ffc8e1a2e30a..faafb4792bf8 100644 --- a/packages/jest-cli/src/init/index.ts +++ b/packages/jest-cli/src/init/index.ts @@ -10,18 +10,20 @@ import * as path from 'path'; import chalk = require('chalk'); import prompts = require('prompts'); import {sync as realpath} from 'realpath-native'; +import {constants} from 'jest-config'; import defaultQuestions, {testScriptQuestion} from './questions'; import {MalformedPackageJsonError, NotFoundPackageJsonError} from './errors'; -import { +import generateConfigFile from './generate_config_file'; +import modifyPackageJson from './modify_package_json'; +import {ProjectPackageJson} from './types'; + +const { JEST_CONFIG_BASE_NAME, - JEST_CONFIG_EXT_CJS, + JEST_CONFIG_EXT_MJS, JEST_CONFIG_EXT_JS, JEST_CONFIG_EXT_ORDER, PACKAGE_JSON, -} from './constants'; -import generateConfigFile from './generate_config_file'; -import modifyPackageJson from './modify_package_json'; -import {ProjectPackageJson} from './types'; +} = constants; type PromptsResults = { clearMocks: boolean; @@ -65,7 +67,7 @@ export default async (rootDir: string = realpath(process.cwd())) => { rootDir, getConfigFilename( projectPackageJson.type === 'module' - ? JEST_CONFIG_EXT_CJS + ? JEST_CONFIG_EXT_MJS : JEST_CONFIG_EXT_JS, ), ); @@ -131,7 +133,11 @@ export default async (rootDir: string = realpath(process.cwd())) => { console.log(`✏️ Modified ${chalk.cyan(projectPackageJsonPath)}`); } - const generatedConfig = generateConfigFile(results); + const generatedConfig = generateConfigFile( + results, + projectPackageJson.type === 'module' || + jestConfigPath.endsWith(JEST_CONFIG_EXT_MJS), + ); fs.writeFileSync(jestConfigPath, generatedConfig); diff --git a/packages/jest-config/src/__tests__/Defaults.test.ts b/packages/jest-config/src/__tests__/Defaults.test.ts index 31eda54e2efe..c985aca3090c 100644 --- a/packages/jest-config/src/__tests__/Defaults.test.ts +++ b/packages/jest-config/src/__tests__/Defaults.test.ts @@ -7,6 +7,8 @@ import {defaults} from '../index'; +jest.mock('../importMjs', () => (s: string) => import(s)); + test('get configuration defaults', () => { expect(defaults).toBeDefined(); }); diff --git a/packages/jest-config/src/__tests__/readConfig.test.ts b/packages/jest-config/src/__tests__/readConfig.test.ts index 63d81ce7e751..a8bc5ef553d7 100644 --- a/packages/jest-config/src/__tests__/readConfig.test.ts +++ b/packages/jest-config/src/__tests__/readConfig.test.ts @@ -7,16 +7,18 @@ import {readConfig} from '../index'; -test('readConfig() throws when an object is passed without a file path', () => { - expect(() => { +jest.mock('../importMjs', () => (s: string) => import(s)); + +test('readConfig() throws when an object is passed without a file path', async () => { + await expect( readConfig( // @ts-ignore null /* argv */, {} /* packageRootOrConfig */, false /* skipArgvConfigOption */, null /* parentConfigPath */, - ); - }).toThrowError( + ), + ).rejects.toThrowError( 'Jest: Cannot use configuration as an object without a file path', ); }); diff --git a/packages/jest-config/src/__tests__/readConfigs.test.ts b/packages/jest-config/src/__tests__/readConfigs.test.ts index 298f0f29625a..d15082bab409 100644 --- a/packages/jest-config/src/__tests__/readConfigs.test.ts +++ b/packages/jest-config/src/__tests__/readConfigs.test.ts @@ -7,9 +7,11 @@ import {readConfigs} from '../index'; -test('readConfigs() throws when called without project paths', () => { - expect(() => { +jest.mock('../importMjs', () => (s: string) => import(s)); + +test('readConfigs() throws when called without project paths', async () => { + await expect( // @ts-ignore - readConfigs(null /* argv */, [] /* projectPaths */); - }).toThrowError('jest: No configuration found for any project.'); + readConfigs(null /* argv */, [] /* projectPaths */), + ).rejects.toThrowError('jest: No configuration found for any project.'); }); diff --git a/packages/jest-config/src/constants.ts b/packages/jest-config/src/constants.ts index 08c0128a534a..3ae6c9701ece 100644 --- a/packages/jest-config/src/constants.ts +++ b/packages/jest-config/src/constants.ts @@ -13,10 +13,12 @@ export const DEFAULT_REPORTER_LABEL = 'default'; export const PACKAGE_JSON = 'package.json'; export const JEST_CONFIG_BASE_NAME = 'jest.config'; export const JEST_CONFIG_EXT_CJS = '.cjs'; +export const JEST_CONFIG_EXT_MJS = '.mjs'; export const JEST_CONFIG_EXT_JS = '.js'; export const JEST_CONFIG_EXT_JSON = '.json'; export const JEST_CONFIG_EXT_ORDER = Object.freeze([ JEST_CONFIG_EXT_JS, + JEST_CONFIG_EXT_MJS, JEST_CONFIG_EXT_CJS, JEST_CONFIG_EXT_JSON, ]); diff --git a/packages/jest-config/src/importMjs.ts b/packages/jest-config/src/importMjs.ts new file mode 100644 index 000000000000..d04ddf5525d3 --- /dev/null +++ b/packages/jest-config/src/importMjs.ts @@ -0,0 +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. + */ + +// this is in a separate file so that node 8 don't explode with a syntax error. +// Remove this file when we drop support for Node 8 +export default (specifier: string) => import(specifier); diff --git a/packages/jest-config/src/index.ts b/packages/jest-config/src/index.ts index faf1a3deb1a0..495879ce59ed 100644 --- a/packages/jest-config/src/index.ts +++ b/packages/jest-config/src/index.ts @@ -20,7 +20,8 @@ export {default as deprecationEntries} from './Deprecated'; export {replaceRootDirInPath} from './utils'; export {default as defaults} from './Defaults'; export {default as descriptions} from './Descriptions'; -import {JEST_CONFIG_EXT_ORDER} from './constants'; +import * as constants from './constants'; +export {constants}; type ReadConfig = { configPath: Config.Path | null | undefined; @@ -29,7 +30,7 @@ type ReadConfig = { projectConfig: Config.ProjectConfig; }; -export function readConfig( +export async function readConfig( argv: Config.Argv, packageRootOrConfig: Config.Path | Config.InitialOptions, // Whether it needs to look into `--config` arg passed to CLI. @@ -39,7 +40,7 @@ export function readConfig( skipArgvConfigOption?: boolean, parentConfigPath?: Config.Path | null, projectIndex: number = Infinity, -): ReadConfig { +): Promise { let rawOptions; let configPath = null; @@ -74,11 +75,11 @@ export function readConfig( // or a path to directory containing `package.json` or `jest.config.js` } else if (!skipArgvConfigOption && typeof argv.config == 'string') { configPath = resolveConfigPath(argv.config, process.cwd()); - rawOptions = readConfigFileAndSetRootDir(configPath); + rawOptions = await readConfigFileAndSetRootDir(configPath); } else { // Otherwise just try to find config in the current rootDir. configPath = resolveConfigPath(packageRootOrConfig, process.cwd()); - rawOptions = readConfigFileAndSetRootDir(configPath); + rawOptions = await readConfigFileAndSetRootDir(configPath); } const {options, hasDeprecationWarnings} = normalize( @@ -262,14 +263,14 @@ This usually means that your ${chalk.bold( // // If no projects are specified, process.cwd() will be used as the default // (and only) project. -export function readConfigs( +export async function readConfigs( argv: Config.Argv, projectPaths: Array, -): { +): Promise<{ globalConfig: Config.GlobalConfig; configs: Array; hasDeprecationWarnings: boolean; -} { +}> { let globalConfig; let hasDeprecationWarnings; let configs: Array = []; @@ -277,7 +278,7 @@ export function readConfigs( let configPath: Config.Path | null | undefined; if (projectPaths.length === 1) { - const parsedConfig = readConfig(argv, projects[0]); + const parsedConfig = await readConfig(argv, projects[0]); configPath = parsedConfig.configPath; hasDeprecationWarnings = parsedConfig.hasDeprecationWarnings; @@ -298,33 +299,37 @@ export function readConfigs( ? projects[0] === realpath(process.cwd()) : projects[0] === process.cwd(); - const parsedConfigs = projects - .filter(root => { - // Ignore globbed files that cannot be `require`d. - if ( - typeof root === 'string' && - fs.existsSync(root) && - !fs.lstatSync(root).isDirectory() && - !JEST_CONFIG_EXT_ORDER.some(ext => root.endsWith(ext)) - ) { - return false; - } + const parsedConfigs = await Promise.all( + projects + .filter(root => { + // Ignore globbed files that cannot be `require`d. + if ( + typeof root === 'string' && + fs.existsSync(root) && + !fs.lstatSync(root).isDirectory() && + !constants.JEST_CONFIG_EXT_ORDER.some(ext => root.endsWith(ext)) + ) { + return false; + } - return true; - }) - .map((root, projectIndex) => { - const projectIsTheOnlyProject = - projectIndex === 0 && projects.length === 1; - const skipArgvConfigOption = !(projectIsTheOnlyProject && projectIsCwd); + return true; + }) + .map((root, projectIndex) => { + const projectIsTheOnlyProject = + projectIndex === 0 && projects.length === 1; + const skipArgvConfigOption = !( + projectIsTheOnlyProject && projectIsCwd + ); - return readConfig( - argv, - root, - skipArgvConfigOption, - configPath, - projectIndex, - ); - }); + return readConfig( + argv, + root, + skipArgvConfigOption, + configPath, + projectIndex, + ); + }), + ); ensureNoDuplicateConfigs(parsedConfigs, projects); configs = parsedConfigs.map(({projectConfig}) => projectConfig); diff --git a/packages/jest-config/src/readConfigFileAndSetRootDir.ts b/packages/jest-config/src/readConfigFileAndSetRootDir.ts index 1eb08ca7a75e..96576e9c28e9 100644 --- a/packages/jest-config/src/readConfigFileAndSetRootDir.ts +++ b/packages/jest-config/src/readConfigFileAndSetRootDir.ts @@ -10,26 +10,56 @@ import * as fs from 'fs'; import {Config} from '@jest/types'; // @ts-ignore: vendored import jsonlint from './vendor/jsonlint'; -import {PACKAGE_JSON} from './constants'; +import { + JEST_CONFIG_EXT_JSON, + JEST_CONFIG_EXT_MJS, + PACKAGE_JSON, +} from './constants'; +import importMjs from './importMjs'; // Read the configuration and set its `rootDir` // 1. If it's a `package.json` file, we look into its "jest" property // 2. For any other file, we just require it. -export default (configPath: Config.Path): Config.InitialOptions => { - const isJSON = configPath.endsWith('.json'); +export default async function readConfigFileAndSetRootDir( + configPath: Config.Path, +): Promise { + const isJSON = configPath.endsWith(JEST_CONFIG_EXT_JSON); + const isMjs = configPath.endsWith(JEST_CONFIG_EXT_MJS); let configObject; - try { - configObject = require(configPath); - } catch (error) { - if (isJSON) { - throw new Error( - `Jest: Failed to parse config file ${configPath}\n` + - ` ${jsonlint.errors(fs.readFileSync(configPath, 'utf8'))}`, - ); - } else { + if (isMjs) { + try { + const importedConfig = await importMjs(configPath); + + if (!importedConfig.default) { + throw new Error( + `Jest: Failed to load mjs config file ${configPath} - did you use a default export?`, + ); + } + + configObject = importedConfig.default; + } catch (error) { + if (error.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 error; } + } else { + try { + configObject = require(configPath); + } catch (error) { + if (isJSON) { + throw new Error( + `Jest: Failed to parse config file ${configPath}\n` + + ` ${jsonlint.errors(fs.readFileSync(configPath, 'utf8'))}`, + ); + } else { + throw error; + } + } } if (configPath.endsWith(PACKAGE_JSON)) { @@ -53,4 +83,4 @@ export default (configPath: Config.Path): Config.InitialOptions => { } return configObject; -}; +} diff --git a/packages/jest-core/src/cli/index.ts b/packages/jest-core/src/cli/index.ts index da079919a161..eea54b856180 100644 --- a/packages/jest-core/src/cli/index.ts +++ b/packages/jest-core/src/cli/index.ts @@ -49,7 +49,7 @@ export const runCLI = async ( const outputStream = argv.json || argv.useStderr ? process.stderr : process.stdout; - const {globalConfig, configs, hasDeprecationWarnings} = readConfigs( + const {globalConfig, configs, hasDeprecationWarnings} = await readConfigs( argv, projects, ); diff --git a/packages/jest-runtime/src/cli/index.ts b/packages/jest-runtime/src/cli/index.ts index 22ed107ffb99..3d1a9ddd2b03 100644 --- a/packages/jest-runtime/src/cli/index.ts +++ b/packages/jest-runtime/src/cli/index.ts @@ -20,7 +20,7 @@ import {VERSION} from '../version'; import {Context} from '../types'; import * as args from './args'; -export function run(cliArgv?: Config.Argv, cliInfo?: Array) { +export async function run(cliArgv?: Config.Argv, cliInfo?: Array) { const realFs = require('fs'); const fs = require('graceful-fs'); fs.gracefulify(realFs); @@ -62,7 +62,7 @@ export function run(cliArgv?: Config.Argv, cliInfo?: Array) { const info = cliInfo ? ', ' + cliInfo.join(', ') : ''; console.log(`Using Jest Runtime v${VERSION}${info}`); } - const options = readConfig(argv, root); + const options = await readConfig(argv, root); const globalConfig = options.globalConfig; // Always disable automocking in scripts. const config: Config.ProjectConfig = { @@ -73,26 +73,26 @@ export function run(cliArgv?: Config.Argv, cliInfo?: Array) { // Break circular dependency const Runtime: any = require('..'); - (Runtime.createContext(config, { - maxWorkers: Math.max(cpus().length - 1, 1), - watchman: globalConfig.watchman, - }) as Promise) - .then(hasteMap => { - const Environment: typeof JestEnvironment = require(config.testEnvironment); - const environment = new Environment(config); - setGlobal( - environment.global, - 'console', - new CustomConsole(process.stdout, process.stderr), - ); - setGlobal(environment.global, 'jestProjectConfig', config); - setGlobal(environment.global, 'jestGlobalConfig', globalConfig); - - const runtime = new Runtime(config, environment, hasteMap.resolver); - runtime.requireModule(filePath); - }) - .catch(e => { - console.error(chalk.red(e.stack || e)); - process.on('exit', () => (process.exitCode = 1)); + try { + const hasteMap: Context = await Runtime.createContext(config, { + maxWorkers: Math.max(cpus().length - 1, 1), + watchman: globalConfig.watchman, }); + + const Environment: typeof JestEnvironment = require(config.testEnvironment); + const environment = new Environment(config); + setGlobal( + environment.global, + 'console', + new CustomConsole(process.stdout, process.stderr), + ); + setGlobal(environment.global, 'jestProjectConfig', config); + setGlobal(environment.global, 'jestGlobalConfig', globalConfig); + + const runtime = new Runtime(config, environment, hasteMap.resolver); + runtime.requireModule(filePath); + } catch (e) { + console.error(chalk.red(e.stack || e)); + process.on('exit', () => (process.exitCode = 1)); + } } diff --git a/scripts/build.js b/scripts/build.js index 2930ee8804e2..3dee896a034f 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -149,7 +149,14 @@ function buildFile(file, silent) { Array.isArray(plugin) && plugin[0] === '@babel/plugin-transform-modules-commonjs' ) { - return [plugin[0], Object.assign({}, plugin[1], {lazy: true})]; + return [ + plugin[0], + Object.assign({}, plugin[1], { + lazy: string => + // we want to lazyload all non-local modules plus `importMjs` - the latter to avoid syntax errors. Set to just `true` when we drop support for node 8 + !string.startsWith('./') || string === './importMjs', + }), + ]; } return plugin;