diff --git a/CHANGELOG.md b/CHANGELOG.md index eff713ac0b7a..e8c895c4beb3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ### Features +- `[jest-config]` Support ESM config files with `.js` extension ([#9573](https://github.com/facebook/jest/9573)). - `[jest-runtime]` Override `module.createRequire` to return a Jest-compatible `require` function ([#9469](https://github.com/facebook/jest/pull/9469)) - `[*]` Support array of paths for `moduleNameMapper` aliases ([#9465](https://github.com/facebook/jest/pull/9465)) diff --git a/babel.config.js b/babel.config.js index d96e19ebfa93..343d46ea18ae 100644 --- a/babel.config.js +++ b/babel.config.js @@ -35,7 +35,7 @@ module.exports = { }, ], ], - test: 'packages/jest-config/src/importMjs.ts', + test: 'packages/jest-config/src/importEsm.ts', }, ], plugins: [ diff --git a/e2e/__tests__/esmConfigFile.test.ts b/e2e/__tests__/esmConfigFile.test.ts index dde3552cbd8b..893e258534cc 100644 --- a/e2e/__tests__/esmConfigFile.test.ts +++ b/e2e/__tests__/esmConfigFile.test.ts @@ -35,4 +35,17 @@ onNodeVersions('^13.2.0', () => { name: 'Config from mjs file', }); }); + + test('reads config from js file when package.json#type=module', () => { + const {json, exitCode} = runWithJson('esm-config/js', ['--show-config'], { + skipPkgJsonCheck: true, + }); + + expect(exitCode).toBe(0); + expect(json.configs).toHaveLength(1); + expect(json.configs[0].displayName).toEqual({ + color: 'white', + name: 'Config from js file', + }); + }); }); diff --git a/e2e/esm-config/js/__tests__/test.js b/e2e/esm-config/js/__tests__/test.js new file mode 100644 index 000000000000..2b4a7ced6f45 --- /dev/null +++ b/e2e/esm-config/js/__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/js/jest.config.js b/e2e/esm-config/js/jest.config.js new file mode 100644 index 000000000000..74dd839b0229 --- /dev/null +++ b/e2e/esm-config/js/jest.config.js @@ -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 js file', + testEnvironment: 'node', +}; diff --git a/e2e/esm-config/js/package.json b/e2e/esm-config/js/package.json new file mode 100644 index 000000000000..3dbc1ca591c0 --- /dev/null +++ b/e2e/esm-config/js/package.json @@ -0,0 +1,3 @@ +{ + "type": "module" +} diff --git a/packages/jest-config/src/__tests__/Defaults.test.ts b/packages/jest-config/src/__tests__/Defaults.test.ts index c985aca3090c..739ca8ee771e 100644 --- a/packages/jest-config/src/__tests__/Defaults.test.ts +++ b/packages/jest-config/src/__tests__/Defaults.test.ts @@ -7,7 +7,7 @@ import {defaults} from '../index'; -jest.mock('../importMjs', () => (s: string) => import(s)); +jest.mock('../importEsm', () => (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 a8bc5ef553d7..d3583b82ed6c 100644 --- a/packages/jest-config/src/__tests__/readConfig.test.ts +++ b/packages/jest-config/src/__tests__/readConfig.test.ts @@ -7,7 +7,7 @@ import {readConfig} from '../index'; -jest.mock('../importMjs', () => (s: string) => import(s)); +jest.mock('../importEsm', () => (s: string) => import(s)); test('readConfig() throws when an object is passed without a file path', async () => { await expect( diff --git a/packages/jest-config/src/__tests__/readConfigs.test.ts b/packages/jest-config/src/__tests__/readConfigs.test.ts index d15082bab409..29b3497b4885 100644 --- a/packages/jest-config/src/__tests__/readConfigs.test.ts +++ b/packages/jest-config/src/__tests__/readConfigs.test.ts @@ -7,7 +7,7 @@ import {readConfigs} from '../index'; -jest.mock('../importMjs', () => (s: string) => import(s)); +jest.mock('../importEsm', () => (s: string) => import(s)); test('readConfigs() throws when called without project paths', async () => { await expect( diff --git a/packages/jest-config/src/importMjs.ts b/packages/jest-config/src/importEsm.ts similarity index 100% rename from packages/jest-config/src/importMjs.ts rename to packages/jest-config/src/importEsm.ts diff --git a/packages/jest-config/src/readConfigFileAndSetRootDir.ts b/packages/jest-config/src/readConfigFileAndSetRootDir.ts index 96576e9c28e9..7215a86cbd15 100644 --- a/packages/jest-config/src/readConfigFileAndSetRootDir.ts +++ b/packages/jest-config/src/readConfigFileAndSetRootDir.ts @@ -10,55 +10,49 @@ import * as fs from 'fs'; import {Config} from '@jest/types'; // @ts-ignore: vendored import jsonlint from './vendor/jsonlint'; -import { - JEST_CONFIG_EXT_JSON, - JEST_CONFIG_EXT_MJS, - PACKAGE_JSON, -} from './constants'; -import importMjs from './importMjs'; +import {JEST_CONFIG_EXT_JSON, PACKAGE_JSON} from './constants'; +import importEsm from './importEsm'; // 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. +// 2. For any other file, we just require it. If we receive an 'ERR_REQUIRE_ESM' +// from node, perform a dynamic import instead. 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; - if (isMjs) { - try { - const importedConfig = await importMjs(configPath); + try { + configObject = require(configPath); + } catch (error) { + if (error.code === 'ERR_REQUIRE_ESM') { + try { + const importedConfig = await importEsm(configPath); - if (!importedConfig.default) { - throw new Error( - `Jest: Failed to load mjs config file ${configPath} - did you use a default export?`, - ); - } + 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}`, - ); - } + 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 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; + throw innerError; } + } else if (isJSON) { + throw new Error( + `Jest: Failed to parse config file ${configPath}\n` + + ` ${jsonlint.errors(fs.readFileSync(configPath, 'utf8'))}`, + ); + } else { + throw error; } } diff --git a/scripts/build.js b/scripts/build.js index 09a5c348d598..ff06ba44adf0 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -153,8 +153,8 @@ function buildFile(file, silent) { 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', + // we want to lazyload all non-local modules plus `importEsm` - the latter to avoid syntax errors. Set to just `true` when we drop support for node 8 + !string.startsWith('./') || string === './importEsm', }), ]; }