Search code examples
jestjsbabeljsbabel-jest

Jest and Babel transpilation - SyntaxError: Cannot use import statement outside a module


I struggle to use JEST for some cases where running the tests I get

Test suite failed to run

...node_modules\p-retry\index.js:1
    ({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,jest){import retry from 'retry';
                                                                                      ^^^^^^

    SyntaxError: Cannot use import statement outside a module

    > 1 | import pRetry from 'p-retry';
        | ^
      2 |
      3 | export function Retry(tries: number) {

      at Runtime.createScriptFromCode (node_modules/jest-runtime/build/index.js:1728:14)
      at Object.<anonymous> (src/common/Retry.ts:1:1)

Meanwhile my webpack build works nice with typescript and babel. I tried a lot of stuff (see below to get it working but no success so far - and haven't really been able to understand whats going on. From my pov - though the transpilation stuff is kind of a black area so far for me I tried to enable Jest to use ESModules andprovide code as such as well as tried providing commonJS module code.

So I am looking for alternative options and ways to investigate further. Particularly one thing strikes me as strange: the Retry.ts file from the error is one of my files which imports the pRetry (a node_module written in ESModule style) which in its code does the import retry from 'retry' (another node-module written in commonJS style)from the very first line of the error.

So what seems to happen to me is that the pRetry is not transformed from it's ESModule Code (the source of pRetry starts with import retry from 'retry';) and just wrapped in some commonJS code instead if I interpret the syntax correctly.

So my next steps would likely be investigate what babel-jest really generates and check what's up there and try to deduct furhter. Does anybody know how to achieve this (especially understand what babel-jest generates) or has another idea?

Things I tried - all failed (sometimes slightly different errors)

  1. using plugins: ["@babel/plugin-transform-runtime"] in babel.config.js
  2. changing target and module in tsconfig.json to es5
  3. introducing below in jest.config.ts transformIgnorePatterns: ["node_modules/?!(p-retry)"]
  4. using the following in jest.config.ts

preset: "ts-jest",
transform: { '^.+\.(ts|tsx)?$': 'ts-jest', "^.+\.(js|jsx)$": "babel-jest"}

or alternatively with ts-jest for both or babel-jest for both

  1. migrating from .babelrc file to babel.config.js as suggested by one post

  2. AllowJS : true in tscfonfig.json and transformIgnorePatterns in jest in combination

  3. adding ["@babel/plugin-transform-runtime",{"regenerator": true}] to babel.config

  4. Using

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

in jest.config

  1. using "transform-es2015-modules-commonjs" in babel.config
  2. using @babel/plugin-transform-modules-commonjs in babel.config
  3. Applying the following steps as suggest by https://stackoverflow.com/questions/35756479/does-jest-support-es6-import-export#:~:text=Jest%20will%20enable%20compilation%20from,json%20.&text=If%20you%20don't%20want%20to%20pollute%20your%20project%20with%20
  • Make sure you don't transform away import statements by setting transform: {} in config file
  • Run node@^12.16.0 || >=13.2.0 with --experimental-vm-modules flag
  • Run your test with jest-environment-node or jest-environment-jsdom-sixteen.
  1. playing with testenvironment like jest-environment-node, node or jsdom in jest.config.ts

jest-config.ts:

const tsconfig = require("./tsconfig.json");
const moduleNameMapper = require("tsconfig-paths-jest")(tsconfig)

export default {
   collectCoverage: true,
   coverageDirectory: "analysis/coverage",
   coveragePathIgnorePatterns: ["/node_modules/"],
   collectCoverageFrom: ["src/**/*.{js,jsx,ts}"],
   coverageReporters: ["json", "lcov", "text", "clover"],
   coverageThreshold: {
      global: {
         branches: 0,
         functions: 0,
         lines: 0,
         statements: 0
      },
   },
   clearMocks: true,
   coverageProvider: "babel",
   moduleNameMapper,
   roots: ["<rootDir>/src/", "<rootDir>/test/"],
   testEnvironment: 'jest-environment-node', 
   testPathIgnorePatterns: [
      "\\\\node_modules\\\\"
   ],
   "transform": {
      "^.+\\.(js|ts|jsx)$": "babel-jest"
   }
};

babel.config.js:

    module.exports = {
   presets: ['@babel/preset-typescript',
      ['@babel/preset-env', {
         targets: { node: "current" }
      }],
      '@babel/preset-flow',

   ],
   plugins: [["@babel/plugin-transform-modules-commonjs"], ["@babel/plugin-proposal-decorators", { "legacy": true }], ["@babel/plugin-proposal-class-properties"]]
}

Extract from package.json

"@babel/core": "^7.16.12",
"@babel/plugin-proposal-decorators": "^7.16.5",
"@babel/plugin-transform-modules-commonjs": "^7.16.8",
"@babel/plugin-transform-runtime": "^7.16.10",
"@babel/preset-env": "^7.14.4",
"@babel/preset-flow": "^7.16.7",
"@babel/preset-typescript": "^7.13.0",
"@babel/runtime": "^7.16.7",
"babel-jest": "^27.4.6",
"babel-plugin-transform-regenerator": "^6.26.0",         
"jest": "^27.0.4",
"jest-config": "^27.4.5",
"jest-esm-transformer": "^1.0.0",
"ts-jest": "^27.1.3",
"tsconfig-paths-jest": "^0.0.1",
"core-js": "^3.20.0",

Solution

  • Turns out I was close.

    With a change of babel.config.ts by adding esmodules: false it is done :-)

    module.exports = {
       presets: ['@babel/preset-typescript',
          ['@babel/preset-env', {
             targets: { esmodules: false, node: "current" }
          }],
          '@babel/preset-flow',
        
       ],
       plugins: [["@babel/plugin-transform-modules-commonjs"], ["@babel/plugin-proposal-decorators", { "legacy": true }], ["@babel/plugin-proposal-class-properties"]]
    }