Skip to content

Commit

Permalink
Handle ERR_REQUIRE_ESM when reading configuration (#9573)
Browse files Browse the repository at this point in the history
  • Loading branch information
azz committed Feb 16, 2020
1 parent 9d1236b commit 83aad69
Show file tree
Hide file tree
Showing 12 changed files with 74 additions and 42 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -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))
- `[jest-reporters]` Adds ability to pass options to the istanbul-reporter through `coverageReporters` ([#9572](https://github.com/facebook/jest/pull/9572))
Expand Down
2 changes: 1 addition & 1 deletion babel.config.js
Expand Up @@ -35,7 +35,7 @@ module.exports = {
},
],
],
test: 'packages/jest-config/src/importMjs.ts',
test: 'packages/jest-config/src/importEsm.ts',
},
],
plugins: [
Expand Down
13 changes: 13 additions & 0 deletions e2e/__tests__/esmConfigFile.test.ts
Expand Up @@ -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',
});
});
});
10 changes: 10 additions & 0 deletions 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);
});
11 changes: 11 additions & 0 deletions 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',
};
3 changes: 3 additions & 0 deletions e2e/esm-config/js/package.json
@@ -0,0 +1,3 @@
{
"type": "module"
}
2 changes: 1 addition & 1 deletion packages/jest-config/src/__tests__/Defaults.test.ts
Expand Up @@ -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();
Expand Down
2 changes: 1 addition & 1 deletion packages/jest-config/src/__tests__/readConfig.test.ts
Expand Up @@ -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(
Expand Down
2 changes: 1 addition & 1 deletion packages/jest-config/src/__tests__/readConfigs.test.ts
Expand Up @@ -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(
Expand Down
File renamed without changes.
66 changes: 30 additions & 36 deletions packages/jest-config/src/readConfigFileAndSetRootDir.ts
Expand Up @@ -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<Config.InitialOptions> {
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;
}
}

Expand Down
4 changes: 2 additions & 2 deletions scripts/build.js
Expand Up @@ -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',
}),
];
}
Expand Down

0 comments on commit 83aad69

Please sign in to comment.