Jest: Loading .cjs config files on Node 13

Created on 24 Oct 2019  ·  21Comments  ·  Source: facebook/jest

🚀 Feature Proposal

If you have Node 13 and jest.config.js file in a package with type=module then Node will emit a warning:

(node:8906) Warning: require() of ES modules is not supported.
require() of /home/spyke/Projects/undercut/packages/undercut/jest.config.js from /home/spyke/Projects/undercut/node_modules/jest-config/build/readConfigFileAndSetRootDir.js is an ES module file as it is a .js file whose nearest parent package.json contains "type": "module" which defines all .js files in that package scope as ES modules.
Instead rename jest.config.js to end in .cjs, change the requiring code to use import(), or remove "type": "module" from /home/spyke/Projects/undercut/packages/undercut/package.json.

In case of manually specifying the config fole with --config jest.config.cjs it fails with an error:

The --config option requires a JSON string literal, or a file path with a .js or .json extension.
Example usage: jest --config ./jest.config.js

Using ES Modules syntax for config file gives an error too:

SyntaxError: Unexpected token 'export'

The issue is the same as babel/babel#10595. .cjs is mandatory, but being able to load an ESM from .js or .mjs is nice to have too.

Motivation

Support ES Modules workflows in Node13+

Example

Configuring Jest in packages with type=module and config file named jest.config.cjs

Pitch

Feature Request

Most helpful comment

@SimenB Yup, jest 25.

Maybe you missed a spot?
https://github.com/facebook/jest/blob/v25.1.0/packages/jest-cli/src/cli/args.ts#L55

  if (
    argv.config &&
    !isJSONString(argv.config) &&
    !argv.config.match(/\.js(on)?$/)
  ) {
    throw new Error(
      'The --config option requires a JSON string literal, or a file path with a .js or .json extension.\n' +
        'Example usage: jest --config ./jest.config.js',
    );
  }

undercut test log: https://gist.github.com/emma-borhanian/94a233217b2f8551157291aa3adcc956

All 21 comments

Sorry, I don't follow. What's the issue in Jest?

The problem is that after upgrade of Node from 12.15 to 12.16 warning become an error.

I have a type=module, jest.config.js and .babelrc.js in my setup, it worked with warning, now it does not. Just putting a reference here for anyone who'll look for more info just in case.

So in essence this ticket also applies to Node 12, not just 13 as mentioned in title.

Aha! Gotcha, thanks for clarifying. You can rename the file to .cjs if you're on Jest 25 as a fix (or .mjs if you want). I _think_ babel also supports .cjs

It will break differently, unfortunately.

If I rename jest.config.cjs:

@service-web/commonHelpers: $ rc-jest
@service-web/commonHelpers: FAIL src/common/utils.spec.ts
@service-web/commonHelpers:   ● Test suite failed to run
@service-web/commonHelpers:     /xxx/src/commonHelpers/.babelrc.js: Error while loading config - Must use import to load ES Module: /Users/dis/Sites/web/src/commonHelpers/.babelrc.js
@service-web/commonHelpers:     require() of ES modules is not supported.
@service-web/commonHelpers:     require() of /Users/dis/Sites/web/src/commonHelpers/.babelrc.js from /Users/dis/Sites/web/node_modules/@babel/core/lib/config/files/configuration.js is an ES module file as it is a .js file whose nearest parent package.json contains "type": "module" which defines all .js files in that package scope as ES modules.
@service-web/commonHelpers:     Instead rename .babelrc.js to end in .cjs, change the requiring code to use import(), or remove "type": "module" from /Users/dis/Sites/web/src/commonHelpers/package.json.
@service-web/commonHelpers:       at ../../node_modules/@babel/core/lib/config/files/configuration.js:201:26
@service-web/commonHelpers:       at cachedFunction (../../node_modules/@babel/core/lib/config/caching.js:33:19)
@service-web/commonHelpers:       at readConfig (../../node_modules/@babel/core/lib/config/files/configuration.js:173:56)
@service-web/commonHelpers:       at ../../node_modules/@babel/core/lib/config/files/configuration.js:105:24
@service-web/commonHelpers:           at Array.reduce (<anonymous>)

Then if I rename .babelrc.cjs I get:

@service-web/commonHelpers: FAIL src/common/utils.spec.ts
@service-web/commonHelpers:   ● Test suite failed to run
@service-web/commonHelpers:     /xxx/src/commonHelpers/src/common/utils.spec.ts:1
@service-web/commonHelpers:     ({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,global,jest){import { getDispositionFileName } from './utils';
@service-web/commonHelpers:                                                                                              ^^^^^^
@service-web/commonHelpers:     SyntaxError: Cannot use import statement outside a module
@service-web/commonHelpers:       at ScriptTransformer._transformAndBuildScript (../../node_modules/@jest/transform/build/ScriptTransformer.js:537:17)
@service-web/commonHelpers:       at ScriptTransformer.transform (../../node_modules/@jest/transform/build/ScriptTransformer.js:579:25)

... the problem is that our previous release (for which we're doing patches now) was on Jest 24... And we cannot do major version upgrade that easily due to policies etc etc.

That second one looks weird... Babel supports .cjs from version 7.7.0 (https://github.com/babel/babel/releases/tag/v7.7.0), is that on an older version?

That won't help if you're stuck on Jest 24 though, as Jest won't recognize the config file... If your config file is just JSON you use that, or do some fancy hacks like putting your config in a preset in another module and load that in Jest (which should circumvent the package.json check for type). Completely untested, but maybe moving the jest config file to a directory with its own dummy package.json works?

@kirill-konshin This is an issue with your setup, probably some glob/regexs in config files are broken because of changed extensions. I'm using Node 13.8.0 and having no errors within undercut repo. As you can see both Babel and Jest configs are .cjs files. You can try by yourself:

yarn
yarn test

@SimenB you're right, our babel in that outdated branch is 7.4.3.
@the-spyke we have quite an old setup at the moment.

So far we just downgraded to Node 12.15... I wonder how come such breaking change made its way to minor release.

Thank you for support!

@kirill-konshin I'm not sure if experimental features are a part of stable API. Also, it isn't really a change, because importing a file with require from ESM context shouldn't have been working in the first place failing require is undefined error.

@the-spyke somehow it worked on Node 12.15. It produced warnings, but code worked fine.

@kirill-konshin Yeah, because it was experimental and did some things not as it should. That's why there was a warning: when implementation of ESM finishes require, __dirname, etc. will be undefined.

The most frustrating about this experimental feature is the transition from "no warning" to "warning" and then to "error" all within minor release...

@the-spyke

I'm using Node 13.8.0 and having no errors within undercut repo. As you can see both Babel and Jest configs are .cjs files. You can try by yourself:

If I run yarn jest --config jest.config.cjs instead of just yarn jest in your repo it fails with:

The --config option requires a JSON string literal, or a file path with a .js or .json extension.

This directly contradicts the jest documentation:

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.

@emma-borhanian with jest 25? cjs and mjs support landed in 25 (in theory)

@SimenB Yup, jest 25.

Maybe you missed a spot?
https://github.com/facebook/jest/blob/v25.1.0/packages/jest-cli/src/cli/args.ts#L55

  if (
    argv.config &&
    !isJSONString(argv.config) &&
    !argv.config.match(/\.js(on)?$/)
  ) {
    throw new Error(
      'The --config option requires a JSON string literal, or a file path with a .js or .json extension.\n' +
        'Example usage: jest --config ./jest.config.js',
    );
  }

undercut test log: https://gist.github.com/emma-borhanian/94a233217b2f8551157291aa3adcc956

@emma-borhanian Indeed, I somehow forget to patch --config option in CLI in #9291. But as you can see, Jest 25 loads the .cjs config by itself without using --config option. I'll submit a fix for the --config soon, sorry for inconvenience.

Aha, good catch @emma-borhanian!

Hi guys, this is really nice feature for future versions of Node. But I still have one problem with this. My script for the test is defined as:

"test": "jest --runInBand --forceExit --detectOpenHandles test/integration"

Running on Node v13.9.0

Then I have _jest.config.cjs_ file with the setup:

module.exports = {
    coveragePathIgnorePatterns: ["/node_modules/"],
    transform: {
        "^.+\\.mjs$": "babel-jest",
    },
};

And babel.config.json as:

{
  "presets": ["@babel/preset-env"]
}

Package versions are updated:
_"babel-jest": "^25.1.0"_
_"jest-cli": "^25.1.0"_
_"@babel/core": "^7.8.4"_
_"@babel/preset-env": "^7.8.4"_

But I am still getting the error below when I run my tests...

_/Volumes/FjoogaDEV/projects/fjooga-api/test/unit/Organisation.test.js:1
({"Object.":function(module,exports,require,__dirname,__filename,global,jest){import { Container } from "typedi";
^^^^^^

SyntaxError: Cannot use import statement outside a module_

What am I doing wrong? Thank you in advance guys.

@bouchja1 Your test file has .js extension, but Jest transformer is set to .mjs extension.

@bouchja1 Your test file has .js extension, but Jest transformer is set to .mjs extension.

That could be a problem. I have renamed it to *.mjs (and added

    moduleFileExtensions: [
       ....
        "mjs",
    ],

to _jest.config.cjs_ but it says "No tests found, exiting with code 0" now... This is that test and I think it is written correctly.

import { Container } from "typedi";
import testApp from "../testApp";

describe("Test Organisations Service", () => {
    let organisationsService;

    beforeAll(async () => {
        await testApp();
        organisationsService = Container.get("organisationService");
    });

    test("Should something", async () => {
        expect(true).toBe(true);
    });
});

EDIT: I am able to run the test after adding this to jest.config.cjs...

testMatch: [
    "**/*.test.mjs"
  ] 

@bouchja1 As you've renamed tests to .mjs I bet testMatch expression stopped working.

In general, you shouldn't rename files into .mjs if they are already ESMs and using Babel. The idea is that you set type="module" in your package.json and the entire package becomes ESM. .cjs needed for files loaded by tools like Jest that use require() internally. In this case you run a Node application (Jest) inside a ESM package folder, where Node thinks that every .js file is a ESM. And you can't require a ESM by design. To fix this you need to manually specify some files that aren't ESMs. Like renaming jest.config.js into jest.config.cjs.

Was this page helpful?
0 / 5 - 0 ratings