question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. ItĀ collects links to all the places you might be looking at while hunting down a tough bug.

And, if youā€™re still stuck at the end, weā€™re happy to hop on a call to see how we can help out.

Jest with TypeScript and ES modules can't import "named imports" from cjs modules

See original GitHub issue

šŸ› Bug Report

Using TS, exports from CJS modules can be imported with syntax as if they were named ES module exports, e.g.:

import { sync } from 'glob';

However, with Jest and ES modules, when this style of import is in a test file or in a dependency of a test file, it says SyntaxError: The requested module 'glob' does not provide an export named 'sync'

Going through all one by one and changing them to import glob from 'glob'; and then calling glob.sync() seems to work, however when migrating some legacy stuff from another test runner to Jest this may not be an option, because there are a lot of those such imports in the codebase.

Is there a way around this, so that import { whatever } from 'whatever'; will work for CJS modules?

To Reproduce

Steps to reproduce the behavior:

Running jest with: node --experimental-vm-modules node_modules/jest/bin/jest.js (as described in https://jestjs.io/docs/ecmascript-modules), and using Jest config:

  resetMocks: true,
  testEnvironment: "node",
  testMatch: [
    "**/src/**/*.(spec|test).[tj]s?(x)"
  ],
  preset: 'ts-jest/presets/default-esm',
  transform: {},
  'extensionsToTreatAsEsm': [".ts", ".tsx"],
  globals: {
    'ts-jest': {
      useESM: true,
      tsconfig: {
        allowSyntheticDefaultImports: true,
        declaration: true,
        esModuleInterop: true,
        jsx: "react",
        lib: ["esnext"],
        module: "es2020",
        moduleResolution: "node",
        outDir: "build",
        sourceMap: true,
        strictNullChecks: true,
        target: "ES2020",
      }
    },
  }

Expected behavior

import { sync } from 'glob' and similar imports from CJS modules work.

Link to repl or repo (highly encouraged)

https://github.com/themaskedavenger/tsjestcjerepro

envinfo


  System:
    OS: macOS 10.15.7
    CPU: (8) x64 Intel(R) Core(TM) i7-4870HQ CPU @ 2.50GHz
  Binaries:
    Node: 15.14.0 - /usr/local/bin/node
    Yarn: 1.22.0 - /usr/local/bin/yarn
    npm: 7.7.6 - /usr/local/bin/npm
  npmPackages:
    jest: ^27.0.4 => 27.0.4 

Issue Analytics

  • State:open
  • Created 2 years ago
  • Reactions:16
  • Comments:7

github_iconTop GitHub Comments

7reactions
k2snowman69commented, Jun 18, 2021

Okay so dug into this a bit and hopefully my investigation helps whoever ends up fixing this on jestā€™s side.

The first and important thing to note is that jest does not use nodejs to resolve files. This tripped me up a lot until I figured this out. Both jest and nodejs use cjs-module-lexer which uses basic regex to parse a contents of a file to determine what the exported functions are from a cjs library. The owner of that library had a great explanation that helped guide the rest of this investigation. Now what this means is the same package has the possibility of behaving differently between nodejs and jest and thatā€™s really important because that gap between the two is going to cause lots of confusionā€¦ so using a few examples letā€™s take this package by packageā€¦

glob

Globā€™s export code looks like module.exports = glob which would require an eval on the js code to determine what the exports are. This is why cjs-module-lexer cannot determine the exports, because itā€™s purely basing it off regex for performance reasons required by nodejs. This will fail in both nodejs and jest.

enzyme

Enzymeā€™s export code looks like

module.exports = {
  render: _render2['default'],
  shallow: _shallow2['default'],
  mount: _mount2['default'],
  ShallowWrapper: _ShallowWrapper2['default'],
  ReactWrapper: _ReactWrapper2['default'],
  configure: _configuration.merge,
  EnzymeAdapter: _EnzymeAdapter2['default']
};

however it seems that cjs-module-lexer is only able to extract the first exported function. I commented about this bug in https://github.com/guybedford/cjs-module-lexer/issues/57 and provided a unit test for reproduction. Hopefully we can see it get fixed.

tslib

tslib actually supports cjs, es6 through the module property (non-standard) and ESM through the exports property (node compatible) so this should work.

When running the following code in node in either cjs or esm there are no errors (as expected)

import { __assign } from "tslib";

const d = __assign({ a: "a" });
console.log(d.a);

However when running the following test in Jest ESM:

import { __assign } from "tslib";

test.only("General config is working", async () => {
  const d = __assign({ a: "a" });
  expect(d.a).toBe("a");
});

Youā€™ll get the following error:

 FAIL  src/tslib.test.js
  ā— Test suite failed to run

    SyntaxError: The requested module 'tslib' does not provide an export named '__assign'

      at Runtime.linkAndEvaluateModule (node_modules/jest-runtime/build/index.js:669:5)

Which means that jestā€™s resolver isnā€™t resolving the same file that node js is resolving that eventually gets sent to cjs-module-lexer so that it can correctly determine the exports.

Summary

Hopefully that gives some guidance to someone who investigates this and maybe we can at least fix this for tslib. To fix this for glob however, youā€™ll need to fix it in cjs-module-lexer. Iā€™d still leave this ticket open to hopefully fix it for tslib though.

4reactions
k2snowman69commented, Jun 15, 2021

In hopes that it speeds things along and removes any question as to ownership, I forked the repository provided by @themaskedavenger and removed all typescript related stuff to show itā€™s a jest issue.

https://github.com/k2snowman69/tsjestcjerepro

Hopefully this removes any doubt that this is a ts or a ts-jest issue

Tested with node 14.17.1 and node v16.1.0

Read more comments on GitHub >

github_iconTop Results From Across the Web

Jest with TypeScript and ES modules can't import "named ...
Using TS, exports from CJS modules can be imported with syntax as if they were named ES module exports, e.g.: import { sync...
Read more >
[Bug]: Jest ESM (ts-jest) can't find import from CJS module ...
Each of the imports gives an error like SyntaxError: The requested module '@apollo/client' does not provide an export named 'ApolloProvider'Ā ...
Read more >
ECMAScript Modules - Jest
mock calls that happens in CJS won't work for ESM. To mock modules in ESM, you need to use require or dynamic import()...
Read more >
Node.js now supports named imports from CommonJS ...
This syntax allows you to only import specific named exports from an ES module ā€“ in the example code above we didn't import...
Read more >
Configuring Module Resolution On Typescript and Jest
Importing the repository module requires the service to traverse up their directory and into the the repositories directory leading the following: import *...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found