dugong
dugong

Reputation: 4403

Jest won't transform the module - SyntaxError: Cannot use import statement outside a module

I couldn't get rid of this SyntaxError: Cannot use import statement outside a module error no matter what I have tried and it got so frustrating. Is there anybody out here solved this issue? I have read a million stackoverflow and github issue threads. No clear solutions.

This is a React, Typescript, Webpack project. I am trying to test a module. But Jest won't transform the module to plain javascript somehow.

The error I get is

/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 | 

What I have tried to solve this (and didn't work):

...and more.

I have this setup:

package.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',
  ],
}

variables.ts

import { GlobalVars } from 'variables'

export const Vars = new GlobalVars()

variables.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')
  })
})

Any idea on how to resolve this problem?

Upvotes: 190

Views: 275941

Answers (20)

Marcin Licznerski
Marcin Licznerski

Reputation: 380

I had similar problem after migrating to NX monorepo v18, but with node_modules/@angular/core/fesm2022/testing.mjs file.

Angular library Jest config was not migrated/updated, here is config that worked for me:

Root jest.config.js

/** @type {import('jest').Config} */
const config = {
  testMatch: ['**/+(*.)+(spec|test).+(ts|js)?(x)'],
  transform: {
    '^.+\\.[tj]sx?$': ['ts-jest', { tsconfig: '<rootDir>/tsconfig.spec.json' }],
  },
  resolver: '@nx/jest/plugins/resolver',
  coverageReporters: ['html'],
  testEnvironment: 'jsdom',
  preset: 'ts-jest/presets/js-with-ts',
};

module.exports = config;

Angular Library jest.config.js

/** @type {import('jest').Config} */
const config = {
  displayName: 'tests',
  preset: '../../jest.config.js',
  setupFilesAfterEnv: ['./src/test-setup.ts'],
  coverageDirectory: '../../coverage/libs/tests',
  transform: {
    '^.+\\.(ts|mjs|js|html)$': [
      'jest-preset-angular',
      {
        tsconfig: '<rootDir>/tsconfig.spec.json',
        stringifyContentPathRegex: '\\.(html|svg)$',
      },
    ],
  },
  transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'],
};

module.exports = config;

Also used following commands to clear cache: npx nx reset and npx jest --clearCache.

Upvotes: 0

Jacob S&#225;nchez
Jacob S&#225;nchez

Reputation: 459

If you are using ts-jest

I was using ts-jest and encountered this error. The key is that jest was still trying to use the JavaScript files that had been generated from the TypeScript tests. Once I deleted those, I was good to go.

Here's how I solved it.

  1. Make sure you are using ESM syntax in your code and that your package.json contains this line
{
  ...
 "type": "module"
}
  1. Create a jest config file with this content
// jest.config.ts
import type { JestConfigWithTsJest } from 'ts-jest'

const jestConfig: JestConfigWithTsJest = {
  // [...]
  preset: 'ts-jest/presets/default-esm', // or other ESM presets
  moduleNameMapper: {
    '^(\\.{1,2}/.*)\\.js$': '$1',
  },
  transform: {
    // '^.+\\.[tj]sx?$' to process js/ts with `ts-jest`
    // '^.+\\.m?[tj]sx?$' to process js/ts/mjs/mts with `ts-jest`
    '^.+\\.tsx?$': [
      'ts-jest',
      {
        useESM: true,
      },
    ],
  },
}

export default jestConfig
  1. Delete any (previously) generated JavaScript files in the tests directory

(take care not to delete any of your own js files)

$ rm tests/*.js
  1. Run jest again
bun run jest

Source: https://kulshekhar.github.io/ts-jest/docs/guides/esm-support

Upvotes: 0

half of a glazier
half of a glazier

Reputation: 2076

Update the test property in the package.json file (the default is react-scripts test):

"scripts": {
    "test": "jest"
}

Upvotes: 0

Doni Putra
Doni Putra

Reputation: 81

I encountered the same issue when I was trying to test my app and some functions were using the file-type library.

I did jest configuration as below in my package.json

"jest": {
    "moduleFileExtensions": [
      "js",
      "json",
      "jsx",
      "node",
      "mjs"
    ],
    "transformIgnorePatterns": []
  },

I think the important thing is to only use "transformIgnorePatterns": [].

Setting transformIgnorePatterns to an empty array means Jest will transform every imported module using the specified transformer, in this case, babel-jest. By default, Jest does not transform anything in node_modules, which can lead to issues when a module (or its dependencies) uses ES6 imports.

If you only encounter this issue with a subset of modules, you can further refine transformIgnorePatterns to exclude only those specific modules from being ignored, but if the empty array works well and does not introduce other complications, you can stick with that.

Upvotes: 6

I.G. Pascual
I.G. Pascual

Reputation: 5965

I encountered the same issue when installing NestJS following this documentation: https://docs.nestjs.com/first-steps.

The fix was adding the missing "preset": "ts-jest", in the package.json file under "jest" configuration:

"jest": {
  ...
  "preset": "ts-jest",
  ...
}

Upvotes: 0

Sam Spade
Sam Spade

Reputation: 1486

If you use direct paths:

Right now ts-jest version 28 & 29 is broken. I don't know all of the versions it affects, but it at least affects 28.1.3 - 29.1.0. It is documented here: https://github.com/nodkz/mongodb-memory-server/issues/679

The bug is that specifying moduleDirectories: ["node_modules", "src"] breaks jest in unexpected ways, including being essentially super mad at you for imports and exports. The fix is to change the line to moduleDirectories: ["node_modules", "<rootdir>/src/"] or remove it entirely. Note: these solutions do not permit the original functionality

The reason one might include this line is to allow direct paths so instead of import { minute } from "../../../utils"; you can write import { minute } from "utils"; In my opinion this is very convenient. The above fixes will allow you to compile, but you will probably still get errors like cannot find module "utils" in /some/directory/path

The only fix I was able to find to get back my desired behaviour is using the moduleNameMapper option like so:

"moduleNameMapper": {
    '^utils(.*)$': '<rootDir>/src/utils$1',
},

credit to answer https://stackoverflow.com/a/67667249

This means that you now have to explicitly specify which files you will reference with absolute imports. While not ideal, I could not get anything else to work

For reference, here is my complete jest.config.js file:

/* eslint-disable import/no-commonjs */
/** @type {import('@ts-jest/dist/types').InitialOptionsTsJest} */
module.exports = {
    preset: "ts-jest/presets/js-with-ts",
    testEnvironment: "node",
    roots: ["<rootDir>/src"],
    "transformIgnorePatterns": [
        "node_modules/(?!variables/.*)"
    ],
    testPathIgnorePatterns: ["<rootDir>/dist/", "node_modules"],
    moduleDirectories: ["node_modules", "<rootdir>/src/"],
    setupFiles: ['dotenv/config'],
    collectCoverage: true,
    coverageDirectory: "coverage",
    extensionsToTreatAsEsm: ['.ts'],
    "moduleNameMapper": {
        "/src/(.*)": "<rootDir>/src/$1",
        '^utils(.*)$': '<rootDir>/src/utils$1',
    },
    "transform": {
        "node_modules/variables/.+\\.(j|t)sx?$": "ts-jest"
    },
};

For your tsconfig.json, you may want to consider the following settings:

        "allowSyntheticDefaultImports": true,
        "esModuleInterop": true,
        "moduleResolution": "Node",
        "module": "ESNext",
        "target": "ES2020",
``

Upvotes: 0

Ogada Stanley Chinedu
Ogada Stanley Chinedu

Reputation: 455

This worked for me.

npm i ts-jest -D

Create jest.config.js in your root dir

Then add this to it:

module.exports = {
  "roots": [
    "<rootDir>/src"
  ],
  "testMatch": [
    "**/__tests__/**/*.+(ts|tsx|js)",
    "**/?(*.)+(spec|test).+(ts|tsx|js)"
  ],
  "transform": {
    "^.+\\.(ts|tsx)$": "ts-jest"
  },
}

Upvotes: 9

svenema
svenema

Reputation: 2496

Use Vitest.

I have tried most of the suggestions made here. I found Jest to be quite painful to install correctly, whereas Vitest works out of the box without any config. This is my personal experience. It took me days to get Jest to sort-of work. I had Vitest working immediately after installation.

I don't want to hate on Jest, I actually think it's a wonderful and intuitive testing tool. But ultimately Vitest "Just Works" (tm), and you can use the same simple Jest style API we all have come to love.

Steps (I use pnpm, but of course you could do the same with yarn or npm):

pnpm remove jest ts-jest @types/jest
pnpm add -D vite vitest

Delete jest.config.js, and create vite.config.ts:

/// <reference types="vitest" />

import { defineConfig } from 'vite'

export default defineConfig({
  test: {
    /* for example, use global to avoid globals imports (describe, test, expect): */
    // globals: true,
  },
})

Add to your tests:

import { assert, expect, test } from 'vitest'

Update your package.json:

"test": "vitest",

Upvotes: 35

Stanley Ulili
Stanley Ulili

Reputation: 1354

I tried a lot of answers here, but nothing worked for me. I was about to give up on jest but found a solution. I will document the steps as a checklist.

First, make sure jest is installed:

npm install --save-dev jest

Second, enable ESM in your package.json file:

{
  ...
 "type": "module"
}

Third, update the test property in the package.json file:

"scripts": {
    "test": "node --experimental-vm-modules ./node_modules/.bin/jest"
}

Following that, create the jest.config.js and add the following contents:

export default { transform: {} };

With that, run the jest again:

npm test

Upvotes: 1

Henry Ruhs
Henry Ruhs

Reputation: 1630

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.

Upvotes: 6

Jplus2
Jplus2

Reputation: 2531

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.

Upvotes: 3

tjvg1991
tjvg1991

Reputation: 402

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"
  }
}

Upvotes: 2

Alex
Alex

Reputation: 489

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

Upvotes: 38

Andrew
Andrew

Reputation: 88

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.

Upvotes: 1

snowyBunny
snowyBunny

Reputation: 445

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'
}

Upvotes: 10

E. Hein
E. Hein

Reputation: 266

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)

Upvotes: 1

Ville
Ville

Reputation: 494

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.

Upvotes: 5

SiegeSailor
SiegeSailor

Reputation: 73

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.

Upvotes: 2

dugong
dugong

Reputation: 4403

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"

Upvotes: 119

Abdullah Khaled
Abdullah Khaled

Reputation: 338

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).

Upvotes: 2

Related Questions