redux-toolkit: Cannot import redux-toolkit from a Node.js ESM module
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;
About this issue
- Original URL
- State: closed
- Created 2 years ago
- Reactions: 25
- Comments: 33 (2 by maintainers)
Commits related to this issue
- Generate .mjs file to allow importing from Node.js in ESM mode (#1960) — committed to chesterlaykin/fork-redux-toolkit by deleted user a year ago
- v1.0.4 [1]upgraded to svelte 4 [2]updated other deps [3]compatible with redux/toolkit cjs([issue](https://github.com/reduxjs/redux-toolkit/issues/1960)) — committed to ColaFanta/svelte-redux-adapter by ColaFanta 10 months ago
- Fix adapter and uuid imports for sdk There's a confusion in ESM and CJS, let's try to fix them by using namespace imports for uuid and `default` wrapper removal for `adapter` (as suggested here: http... — committed to whereby/jslib-media by nandito 4 months ago
- Fix adapter and uuid imports for sdk There's a confusion in ESM and CJS, let's try to fix them by using namespace imports for uuid and `default` wrapper removal for `adapter` (as suggested here: http... — committed to whereby/jslib-media by nandito 4 months ago
- Fix adapter and uuid imports for sdk There's a confusion in ESM and CJS, let's try to fix them by using namespace imports for uuid and `default` wrapper removal for `adapter` (as suggested here: http... — committed to whereby/jslib-media by nandito 4 months ago
- Fix adapter and uuid imports for sdk There's a confusion in ESM and CJS, let's try to fix them by using namespace imports for uuid and `default` wrapper removal for `adapter` (as suggested here: http... — committed to whereby/jslib-media by nandito 4 months ago
- Fix adapter and uuid imports for sdk There's a confusion in ESM and CJS, let's try to fix them by using namespace imports for uuid and `default` wrapper removal for `adapter` (as suggested here: http... — committed to whereby/jslib-media by nandito 4 months ago
- Fix adapter imports for sdk There's a confusion in ESM and CJS, let's try to fix them by using `default` wrapper removal for `adapter` (as suggested here: https://github.com/reduxjs/redux-toolkit/iss... — committed to whereby/jslib-media by nandito 4 months ago
- Fix adapter imports for sdk There's a confusion in ESM and CJS, let's try to fix them by using `default` wrapper removal for `adapter` (as suggested here: https://github.com/reduxjs/redux-toolkit/iss... — committed to whereby/jslib-media by nandito 4 months ago
Hiya, folks.
For the record, I am finally trying to get started working on proper ESM support for RTK.
Aaaaand I hate everything about this 😃
I found a workaround that works in both backend and frontend:
or in Typescript:
FYI folks, I’ve published https://github.com/reduxjs/redux-toolkit/releases/tag/v2.0.0-alpha.4 , which does further improvements to the ESM/CJS package formatting.
I’ve also built up a whole suite of example apps using different build tools (CRA4/5, Next, Vite) and other test projects (Node in ESM and CJS mode, and the
arethetypeswrong
tool) to verify that the package works as expected in a variety of different environments.I feel fairly good about the ESM/CJS packaging at this point. Please try out that alpha and give us feedback!
This should now be available in https://github.com/reduxjs/redux-toolkit/releases/tag/v2.0.0-alpha.1 .
Please try that out and let us know if it works!
@SebastianGaud : like I said, I’m working on this right now 😃
@toychicken Yes, because ESM syntax has been pretty standard for writing client-side code for several years now. The Node situation is unfortunate, but not a reason to skip showing ESM imports in examples.
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:
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 😄
@azzazkhan did you see Marks last message and try the 2.0 betas?
Our team uses gen-esm-wrapper to create another
.mjs
file (koa uses this approach too) and then define aexports
field in thepackage.json
file. This patch makes us safely use@reduxjs/redux-toolkit
in our ESM jest tests:If that can be helpful, I’ve managed to get Redux Toolkit imported properly when running in Mocha through a little patching with
patch-package
, to:package.json
file to declareexports
as described earlier in the threadthunkMiddleware
which gets accessed usingthunkMiddleware.default
when running inside Node.Note that the project it runs in has
"type": "module"
in itspackage.json
(as well as the"postinstall": "patch-package"
script required bypatch-package
, of course)In case it helps anyone, this is the
patches/@reduxjs+toolkit+1.8.4.patch
patch file that got generated bypatch-package
:If you’re looking to patch the library further (for ex. to remove the
enableES5()
call), seemspatch-package
can only handle one patch per library, so you’ll need to make sure to generate your own patch after editing further. To make sure the changes inpackage.json
get in the patch, there’s a little twist and you’ll need to add the--exclude
flag for generating the patch :npx patch-package --exclude
.Just FYI - all the examples on the Redux Toolkit site uses ESM imports - might be why some are confused here! e.g. https://redux-toolkit.js.org/api/createReducer