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

See original GitHub issue

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;

Issue Analytics

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

github_iconTop GitHub Comments

5reactions
cdauthcommented, Jan 26, 2022

I found a workaround that works in both backend and frontend:

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;
1reaction
akselikapcommented, Sep 1, 2022

This gets even more confusing with RTK Query. I have a similar situation where I use RTK on both backend and frontend. When using ESM, TypeScript can’t locate declaration files for RTK Query so in the end I ended up doing:

import * as rtkQuery from '@reduxjs/toolkit/dist/query/index.js';
const { buildCreateApi, coreModule, fetchBaseQuery } = ((rtkQuery as any).default ?? rtkQuery) as typeof rtkQuery;
import * as rtkQueryReact from '@reduxjs/toolkit/dist/query/react/index.js';
const { reactHooksModule } = ((rtkQueryReact as any).default ?? rtkQueryReact) as typeof rtkQueryReact;
const createApi = buildCreateApi(
    coreModule(),
    reactHooksModule(),
);

I had to build createApi myself because if I didn’t all RTK Query types would fail to infer. Also I still get webpack warnings so I had to ignore those explicitly in my webpack config as well. Hopefully RTK 2.0 comes and saves me from this madness 😄

Read more comments on GitHub >

github_iconTop Results From Across the Web

Why I can not use " import { createSlice, configureStore } from ...
I am learning redux, and try to run a very simple example code in node.js environment. I got the following error when I...
Read more >
@reduxjs/toolkit - npm
An Existing App. Redux Toolkit is available as a package on NPM for use with a module bundler or in a Node application:...
Read more >
Module not found: Can't resolve 'redux' in React | bobbyhadz
To solve the error Module not found: Error: Can't resolve 'redux', ... You should now be able to import and use the redux...
Read more >
export 'import_react3' is not defined in module - You.com
React unable to import component -- module not found ... Affected URL(s):. https://nodejs.org/dist/latest-v14.x/docs/api/esm.html ...
Read more >
scala-js/scala-js/contrib - Gitter
https://nodejs.org/api/modules.html#modules_addenda_the_mjs_extension ... tl;dr: require cannot import an ESModule. import on CommonJS is only specified for ...
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