Skip to content

Cannot import redux-toolkit from a Node.js ESM module #1960

@cdauth

Description

@cdauth

I have a bit of an unusual setup where I use redux-toolkit also 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 redux-toolkit in an mjs module using import { createSlice } from '@reduxjs/toolkit';, I am receiving the following error:

SyntaxError: Named export 'createSlice' not found. The requested module '@reduxjs/toolkit' is a CommonJS module, which may not support all module.exports as named exports.
CommonJS modules can always be imported via the default export, for example using:

import pkg from '@reduxjs/toolkit';
const { createSlice } = pkg;

The workaround suggested in the error message does work for Node.js mjs files. The problem is that the code where I use redux-toolkit is shared by the backend (running on Node.js) and the frontend (compiled using webpack). With the workaround in place, webpack can now not compile the file anymore and gives the following error:

export 'default' (imported as 'toolkit') was not found in '@reduxjs/toolkit' (possible exports: MiddlewareArray, __DO_NOT_USE__ActionTypes, applyMiddleware, bindActionCreators, combineReducers, compose, configureStore, createAction, createAsyncThunk, createDraftSafeSelector, createEntityAdapter, createImmutableStateInvariantMiddleware, createNextState, createReducer, createSelector, createSerializableStateInvariantMiddleware, createSlice, createStore, current, findNonSerializableValue, freeze, getDefaultMiddleware, getType, isAllOf, isAnyOf, isAsyncThunkAction, isDraft, isFulfilled, isImmutableDefault, isPending, isPlain, isPlainObject, isRejected, isRejectedWithValue, miniSerializeError, nanoid, original, unwrapResult)

Reason for the error

redux-toolkit 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/redux-toolkit.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 { createSlice } from '@reduxjs/toolkit/dist/redux-toolkit.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).

Node.js does support importing CommonJS packages in most cases, but in the case of redux-toolkit for some reason it doesn’t work. I am not sure why, but none of my other dependencies had this problem.

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/redux-toolkit.esm.mjs",
    "require": "./dist/index.js"
  },

This solution does not solve the problem completely, as redux-toolkit uses immer, which has a similar problem. I have reported that as immerjs/immer#901.

Workaround

Use the import like this:

import * as toolkitRaw from '@reduxjs/toolkit';
const { createSlice } = toolkitRaw.default ?? toolkitRaw;

or in Typescript:

import * as toolkitRaw from '@reduxjs/toolkit';
const { createSlice } = ((toolkitRaw as any).default ?? toolkitRaw) as typeof toolkitRaw;

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions