-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Description
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;