ts-jest: "Cannot use import statement outside a module" when importing a pnpm workspace module (ESM) inside a source

🐛 Bug Report

When I try to run a test for a package inside of a pnpm workspace that uses another package that is compiled to ESM I receive such error:

   Details:

    /home/v1rtl/Coding/tinyhttp/packages/send/dist/index.js:1
    ({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,jest){import { format, parse } from 'es-content-type';
                                                                                      ^^^^^^

    SyntaxError: Cannot use import statement outside a module

    > 1 | export * from '@tinyhttp/send'
        | ^
      2 | export * from './cookie'
      3 | export {
      4 |   setContentType,

      at Runtime.createScriptFromCode (node_modules/.pnpm/jest-runtime@27.0.0-next.8/node_modules/jest-runtime/build/index.js:1472:14)
      at Object.<anonymous> (packages/res/src/index.ts:1:1)

To Reproduce

Steps to reproduce the behavior:

  1. Install pnpm and create a pnpm workspace with such configs:

.npmrc:

link-workspace-packages = true
shamefully-hoist = false
shared-workspace-shrinkwrap = true
access = public

pnpm-workspace.yaml:

packages:
  - 'packages/*'

  1. Install ts-jest and others: pnpm i -WD typescript tslib jest ts-jest @types/jest @jest/globals
  2. Create jest.config.mjs config:
export default {
  testEnvironment: 'node',
  preset: 'ts-jest/presets/default-esm',
  globals: {
    'ts-jest': {
      useESM: true
    }
  },
  transform: {
    '^.+\\.(t|j)sx?$': 'ts-jest'
  },
  testRegex: '(/tests/.*|\\.(test))\\.(ts|tsx|js)$',
  moduleFileExtensions: ['ts', 'js', 'json']
}
  1. Create 2 workspace packages under “packages” folder:
packages /
   a
   b
  1. Set these package.json fields for both projects:
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
  ".": {
    "import": "./dist/index.js"
  },
  "./package.json": "./package.json",
  "./": "./"
}
  1. Import one module from another:
// packages/b/src/index.ts
import { add } from '@workspace/a'

export { add }
  1. Create a test file:
// lib.test.ts
import { add } from './packages/b/src'
import { describe, it } from '@jest/globals'

describe('suite', () => {
  it('case', () => void expect(add(1, 2)).toBe(3))
})

Expected behavior

The test runs as if all files were CommonJS

Link to repo (highly encouraged)

https://github.com/talentlessguy/ts-jest-esm-pnpm-workspaces-repro

Debug log:

https://gist.github.com/talentlessguy/f37f04c3736b2cfaebbe4f5568c46bab

envinfo

System:
    OS: 5.10.30-1-MANJARO
    Node version: 15.14

Npm packages:
    jest: 27.0.0-next.8
    ts-jest: 27.0.0-next.11
    typescript: 4.2.4

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Comments: 21

Most upvoted comments

I landed here looking for a solution to a SyntaxError: Unexpected token error I was facing when importing an ESM-compiled NPM module from a test file in a PNPM monorepo.

I was trying to use the following Jest config in one of my monorepo’s packages (along with allowJs: true in the package’s tsconfig.json):

export default {
  // ...
  preset: 'ts-jest/presets/js-with-ts',
  transformIgnorePatterns: [
    'node_modules/(?!(d3-scale-chromatic)/)',
  ],
}

… but Jest kept on throwing a SyntaxError in the following file:

/my-monorepo-root/node_modules/.pnpm/d3-scale-chromatic@3.0.0/node_modules/d3-scale-chromatic/src/index.js:1

In case anyone lands here like I did, I managed to get it to work by replacing the ignore pattern with:

transformIgnorePatterns: [
  'node_modules/\\.pnpm/(?!(d3-scale-chromatic)@)'
]

I assume this has something to do with how PNPM symlinks modules. 🤷‍♂️

See also this comment for an alternative regex (though allowing any character before the module name seems risky).

I think I got it working

looks like TS+ESM+Jest support is not perfect yet

and it treats types as real imports. that was the issue… ah and also adding the node flag

Should I close this issue and open another one such as “Type imports are treated as module imports”?

to summarize, huge thanks for such instant assistance, I might set up an example repo with using jest, ts-jest and ts with latest node so other ppl don’t face such issues

@ahnpnl now it’s another error 😕

➜ NODE_OPTIONS=--experimental-vm-modules pnpx jest tests/modules/res.test.ts
(node:184000) ExperimentalWarning: VM Modules is an experimental feature. This feature could change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
 FAIL  tests/modules/res.test.ts
  ● Test suite failed to run

    ReferenceError: exports is not defined

      3 | import path from 'path'
      4 | import { Request, Response } from '../../packages/app/src'
    > 5 | import {
        |                   ^
      6 |   formatResponse,
      7 |   getResponseHeader,
      8 |   redirect,

      at tests/modules/res.test.ts:5:23
      at TestScheduler.scheduleTests (node_modules/.pnpm/@jest+core@27.0.0-next.8_ts-node@9.1.1/node_modules/@jest/core/build/TestScheduler.js:337:13)
      at runJest (node_modules/.pnpm/@jest+core@27.0.0-next.8_ts-node@9.1.1/node_modules/@jest/core/build/runJest.js:379:19)

Test Suites: 1 failed, 1 total
Tests:       0 total
Snapshots:   0 total
Time:        2.012 s
Ran all test suites matching /tests\/modules\/res.test.ts/i.

@ahnpnl oh oops forgot to include it.

Still doesn’t work though:

 TS_JEST_LOG=ts-jest.log pnpx jest tests/modules/res.test.ts
 FAIL  tests/modules/res.test.ts
  ● Test suite failed to run

    Jest encountered an unexpected token

    Jest failed to parse a file. This happens e.g. when your code or its dependencies use non-standard JavaScript syntax, or when Jest is not configured to support such syntax.

    Out of the box Jest supports Babel, which will be used to transform your files into valid JS based on your Babel configuration.

    By default "node_modules" folder is ignored by transformers.

    Here's what you can do:
     • If you are trying to use ECMAScript Modules, see https://jestjs.io/docs/ecmascript-modules for how to enable it.
     • To have some of your "node_modules" files transformed, you can specify a custom "transformIgnorePatterns" in your config.
     • If you need a custom transformation specify a "transform" option in your config.
     • If you simply want to mock your non-JS modules (e.g. binary assets) you can stub them out with the "moduleNameMapper" config option.

    You'll find more details and examples of these config options in the docs:
    https://jestjs.io/docs/configuration
    For information about custom transformations, see:
    https://jestjs.io/docs/code-transformation

    Details:

    /home/v1rtl/Coding/tinyhttp/packages/send/dist/index.js:1
    ({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,jest){import { format, parse } from 'es-content-type';
                                                                                      ^^^^^^

    SyntaxError: Cannot use import statement outside a module

    > 1 | export * from '@tinyhttp/send'
        | ^
      2 | export * from './cookie'
      3 | export {
      4 |   setContentType,

      at Runtime.createScriptFromCode (node_modules/.pnpm/jest-runtime@27.0.0-next.8/node_modules/jest-runtime/build/index.js:1472:14)
      at Object.<anonymous> (packages/res/src/index.ts:1:1)

Test Suites: 1 failed, 1 total
Tests:       0 total
Snapshots:   0 total
Time:        2.834 s
Ran all test suites matching /tests\/modules\/res.test.ts/i.