Jest 不会转换模块-SyntaxError: 不能在模块外使用 import 语句

无论我怎么尝试,我都无法摆脱这个 SyntaxError: Cannot use import statement outside a module错误,这让我很沮丧。这里有人解决了这个问题吗?我读过无数的 stackoverflow 和 github 问题线程。没有明确的解决方案。

这是一个反应,打字,Webpack 项目。我正在测试一个模块。但是 Jest 不会改变 模块转换为普通的 javascript

我得到的错误是

/Users/me/dev/Project/project/node_modules/variables/src/variables.js:12
import './main.js';
^^^^^^


SyntaxError: Cannot use import statement outside a module


17 |
18 | */
> 19 | import { GlobalVars } from 'variables'
| ^
20 |
21 | export const Vars = new GlobalVars()
22 |

我曾试图解决这个问题(但没有成功) :

  • babel.config: env.test.preset: ['@babel/plugin-transform-modules-commonjs']中使用 env设置

  • 将 Jest 配置中的 transform设置修改为 '^.+\\.jsx?$': 'babel-jest', '^.+\\.tsx?$': 'ts-jest',以及围绕这一点的所有其他可能性。

  • 在 Jest 配置中,testPathIgnorePatternstransformIgnorePatterns

  • 使用 .babel.config.js而不是 .babelrc.js

甚至更多。

我有这个装置:

包裹 Json

  "jest": {
"preset": "ts-jest",
"testEnvironment": "node"
}

. babelrc.js

module.exports = {
presets: [
['@babel/preset-env', { targets: { node: 'current' } }],
'@babel/preset-react',
'@babel/preset-typescript',
],
plugins: [
'@babel/plugin-transform-runtime',
'@babel/proposal-class-properties',
'@babel/transform-regenerator',
'@babel/plugin-transform-template-literals',
'react-hot-loader/babel',
],
}

变量

import { GlobalVars } from 'variables'


export const Vars = new GlobalVars()

Variable.spec.ts

import { Vars } from './variables.ts'


describe('Test The Package', () => {
it('Should accept new variables', () => {
Vars.newVariable = 'new variable'
expect(Vars.newVariable).toEqual('new variable')
})
})


有什么办法解决这个问题吗?

136077 次浏览

Even though I have tried them separately, I haven't tried them together (transform and transformIgnorePatterns). So this jest configuration solved my issue:

  "jest": {
"preset": "ts-jest",
"testEnvironment": "node",
"transform": {
"node_modules/variables/.+\\.(j|t)sx?$": "ts-jest"
},
"transformIgnorePatterns": [
"node_modules/(?!variables/.*)"
]
},

My mistakes were:

  1. Not using transform and transformIgnorePatterns together.
  2. And defining babel-jest as the transformer instead of ts-jest (I guess that is a problem when the preset of jest is defined as ts-jest. Because if I change it to be babel-jest it throws the same error again.):
--- "node_modules/variables/.+\\.(j|t)sx?$": "babel-jest"
+++ "node_modules/variables/.+\\.(j|t)sx?$": "ts-jest"

Since Jest is not working with esmodules well, you need to add these configurations in jest.config.js to tell Jest to use commonJS builds instead

moduleNameMapper: {
'^variables$': 'variables/dist/cjs',
'^[NAME OF MODULE YOU WANT TO IMPORT]$': '[NAME OF MODULE YOU WANT TO IMPORT]/dist/cjs'
}

Another way to solve it is and possible other subsequent issues regarding babel and typescript is to use ts-jest, quoted from Jest's getting started

However, there are some caveats to using TypeScript with Babel. Because TypeScript support in Babel is purely transpilation, Jest will not type-check your tests as they are run. If you want that, you can use ts-jest instead, or just run the TypeScript compiler tsc separately (or as part of your build process).

I got stuck in the same situation. Due to I had a private untranspiled package which is based on TypeScript, and all my source files and test files were all applied with ECMA ( import syntax ), I encountered the following error as well.

SyntaxError: Cannot use import statement outside a module

The solutions I have tried.

  • The above answers, such as use transform, transformIgnorePatterns, moduleNameMapper in jest.config.ts.
  • Followed JEST official document, Configuration Jest#transformignorepatterns-arraystring, I used exactly the same method and even referred to the use case from React Native Guide.
  • Removed all node_modules, including cache, and reinstalled them.
  • Used react-scripts and downgrade jest and babel-jest to 26 from 27. There was another issue that occurred.

After all, I found ECMAScript Modules from the JEST official document, the four steps perfectly solved my problem. I pasted their instructions here, however, you should take a look at the document itself.

  1. Ensure you either disable code transforms by passing transform: {} or otherwise configure your transformer to emit ESM rather than the default CommonJS (CJS).
  2. Execute node with --experimental-vm-modules, e.g. node --experimental-vm-modules node_modules/.bin/jest or NODE_OPTIONS=--experimental-vm-modules npx jest etc.. On Windows, you can use cross-env to be able to set environment variables.
  3. Beyond that, we attempt to follow node's logic for activating "ESM mode" (such as looking at type in package.json or mjs files), see their docs for details.
  4. If you want to treat other file extensions (such as ts) as ESM, please use the extensionsToTreatAsEsm option.

You don't necessarily need to change the transformer from babel-jest to ts-jest.

Instead:

  1. Rename your .babelrc to babel.config.json https://babeljs.io/docs/en/configuration#whats-your-use-case

  2. Add transformIgnorePatterns:

    "transformIgnorePatterns": [
"node_modules/(?!variables/.*)"
]

This solved similar problem to me without need to add additional transform patterns. .babelrc is local to your project so it won't be applied to node_modules in Jest.

tl;dr: tsconfig.spec.json > { "compilerOptions": { "allowJs": true } }

Did everything else mentioned here:

  • transformIgnorePatterns
  • add an additional pattern to the transform section
  • ... much more that I reverted afterwards

Then I debugged into the transformer. First think I realized: the preconfigured workspace does not log anything, not even into ts-jest.log, not with --verbose or --debug and not even the warning, that would have been:

Got a .js file to compile while allowJs option is not set to true (file: \{\{path}}). To fix this:

  • if you want TypeScript to process JS files, set allowJs to true in your TypeScript config (usually tsconfig.json)
  • if you do not want TypeScript to process your .js files, in your Jest config change the transform key which value is ts-jest so that it does not match .js files anymore

(see: ts-jest-transformer.ts)

A simple solution would be to use a different preset in your jest.config.js file.

Instead of using the default "preset": "ts-jest", try use presets like "preset": "ts-jest/presets/js-with-ts". Based on the documentation, it will:

ts-jest/presets/js-with-ts

TypeScript and JavaScript files (.ts, .tsx, .js, .jsx) will be transformed by ts-jest to CommonJS syntax. You'll need to set allowJs to true in your tsconfig.json file.

My issue was different in a way that jest would stumle on .js files from one of the dependencies in node_modules with SyntaxError: Cannot use import statement outside a module.

I had to make sure, that ts-jest wouldn't ignore (when transforming) .js files in troublesome dependency.

After reading carefully about presets, I realized, that it leaves them 'as-is' with preset: 'ts-jest'. I changed it to preset: 'ts-jest/presets/js-with-ts' and set "allowJs": true in tsconfig.json.

To not mess up my project's tsconfig.json, I have a separate one for jest.

In the end, my jest.config.js looks mainly like this:

module.exports = {
preset: 'ts-jest/presets/js-with-ts',
testEnvironment: "node",
globals: {
'ts-jest': {
tsconfig: '<rootDir>/test/tsconfig.json',
},
},
transformIgnorePatterns: [
"node_modules/(?!troublesome-dependency/.*)",
],
}

P.S. I didn't need a transform field, since the preset is already on it.

P.P.S. I didn't need to introduce any babel configuration

I solved my problem, mocking the file ex: jest.mock('rehype-raw/index.js', () => jest.fn());

this in the top of your test file

Solved mine by adding "modules": "commonjs" inside .babelrc -> presets -> @babel/preset-env

.babelrc

{
"presets": [
[
"@babel/preset-env",
{
"modules": "commonjs"
}
],
"@babel/preset-react"
]
}

package.json

"jest": {
"verbose": true,
"moduleDirectories": ["node_modules", "src"],
"testEnvironment": "node",
"transformIgnorePatterns": [
"node_modules/(?!variables/.*)"
],
"transform": {
"^.+\\.jsx?$": "babel-jest"
}
}

For people suffering from this due to antd framework.

Scenario

You override antd DatePicker to use day js instead of default moment js.

You followed the instructions here

Now if you run your test, it will have that dreaded error

SyntaxError: Cannot use import statement outside a module

The reason for that is because of this

import generatePicker from "antd/es/date-picker/generatePicker";
import "antd/es/date-picker/style/index";

Specifically it is caused because of importing from antd/es/

The fix is to import from antd/lib/ instead.

import generatePicker from "antd/lib/date-picker/generatePicker";
import "antd/lib/date-picker/style/index";

This fixes the test error, however, another problem that would arise is that this code have a bug. The DatePicker component now will not respect locales.

So the final solution is to add

  moduleNameMapper: {
... other mappings here ...
    

// Add this to fix the problem!
"^antd/es/(.*)$": "antd/lib/$1",
}

in your jest.config.js file

This will let you use antd/es/ in your code but will replace it with antd/lib/ on your tests.

Reference here

Hope this helps anyone, this took me a while to figure out.

By accident I found a hack to let pre-processor process the whole node_modules what is usually forbidden:

By default "node_modules" folder is ignored by transformers.

transformIgnorePatterns:
[
'node_modules'
]

Using this pattern seems to bypass the Jest internal check.

transformIgnorePatterns:
[
'//node_modules'
]

It this a hack? Yes! Is this slow? Yes! But properly it can help someone who needs a quick fix.

I just created a .babelsrc file and added:

{
"presets": [["@babel/preset-env", {"targets": {"node": "current"}}]]
}

And of course, you'll have to install this:

npm install --save-dev babel-jest @babel/core @babel/preset-env

And it should work when you launch npm run test