Skip to content

immer is not properly exported to be used in Node.js ESM modules #901

@cdauth

Description

@cdauth

I am trying to use immer in the backend of a Node.js application. I am currently in the process of migrating my backend to ESM modules, since some dependencies (in particular node-fetch) are starting to ship only ESM modules.

Error description

When I try to import immer in an mjs module using import produce from 'immer';, produce will be an object instead of a function, with its default property being the produce function.

I can access the produce function by using import { produce } from 'immer'; or by using import immer from 'immer'; and then using immer.produce(). The problem is that the documentation uses import produce from 'immer';, so there are libraries using this whose code I am not in control of. This means that these libraries break when I migrate to ESM modules.

Reason for the error

immer is bundled in several different formats, among them cjs and esm. The bundles are referenced in package.json in the following way (index.js being a wrapper that includes the cjs bundle):

  "main": "dist/index.js",
  "module": "dist/immer.esm.js",

While the module property is probably supported by webpack and other bundlers, it does not seem to be supported by Node.js. Instead, Node.js uses the exports property to support different main files for different environments (see here. Since that is not defined in this case, Node.js requires the file from the main property, which is a CommonJS bundle.

Even forcing Node.js to use the ESM bundle by doing import produce from 'immer/dist/immer.esm.js'; does not solve the problem. The problem is that Node.js interprets files as CommonJS unless they have a .jsm file extension or "type": "module" is defined in package.json (which then applies to all files in the package) (see here).

Possible solution

Setting "type": "module" is probably not an option, since that will break the CommonJS files.

The only solution that I can think of is to ship the ESM bundle as an .mjs file, either by renaming the current one or by creating a copy. The file can then be referenced in package.json like this:

  "exports": {
    "import": "./dist/immer.esm.mjs",
    "require": "./dist/index.js"
  },

Workaround

If you are using immer yourself, use import { produce } from 'immer'; rather than import produce from 'immer';.

I have not found a workaround for cases where dependencies whose code you don't control are using immer. A last resort is probably yarn patch.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions