Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
cdauth opened this issue Jan 25, 2022 · 12 comments · Fixed by #921
Closed

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

cdauth opened this issue Jan 25, 2022 · 12 comments · Fixed by #921
Labels

Comments

@cdauth
Copy link
Contributor

cdauth commented Jan 25, 2022

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.

@mweststrate
Copy link
Collaborator

mweststrate commented Jan 25, 2022 via email

@cdauth
Copy link
Contributor Author

cdauth commented Jan 25, 2022

I could do it, but I'm not sure which solution you prefer. I see that the ESM export used to have the .mjs extension, but this was changed by #332. So I'm wondering if renaming the file to .mjs will break things for some users again? Should the build process copy the existing file and create a duplicate with the .mjs extension?

@cdauth
Copy link
Contributor Author

cdauth commented Jan 25, 2022

After gaining some more experience with this, I think the way to go would be to add an .mjs build in addition to the existing ones for now, without referencing it in package.json. This would avoid any breaking changes.

For the next major release, the .mjs file could be declared in the exports property, or even become the main distribution file.

If you agree with this approach, I could work on a pull request.

@mweststrate
Copy link
Collaborator

@cdauth sorry had to read a little bit up on that, looks like it is going to be messy indeed, but your solution sounds reasonable, so feel free to give it a shot :). I think introducing an exports field should be ok? with a copy of the module file with separate extension is probably the safest bet at the moment indeed, while crying a little inside.

@github-actions
Copy link
Contributor

🎉 This issue has been resolved in version 9.0.13 🎉

The release is available on:

Your semantic-release bot 📦🚀

@timur-svoboda
Copy link

I write an Expo application. I use @reduxjs/toolkit@1.9.5, which depends on immer@9.0.21. I have received the following warning:

warning: The package C:\Mine\projects\farm\farm-code\node_modules\immer contains an invalid package.json configuration. Consider raising this issue with the package maintainer(s).
Reason: The resolution for "C:\Mine\projects\farm\farm-code\node_modules\immer" defined in "exports" is C:\Mine\projects\farm\farm-code\node_modules\immer\dist\immer.esm.mjs, however this file does not exist. Falling back to file-based resolution.

Maybe the warning is related to this issue. Let me know if you need additional information.

@markerikson
Copy link
Contributor

@timur-svoboda : yeah, Immer 10 should fix that, but RTK 1.x needs Immer 9.

Please try out our RTK 2.0 betas, which already are updated to use Immer 10 instead:

https://github.com/reduxjs/redux-toolkit/releases/tag/v2.0.0-beta.1

@anhtuank7c
Copy link

anhtuank7c commented Sep 3, 2023

I'm having an issue with RTK 1.9.5 or even upgraded to RTK 2.0.0-beta 1 doesn't fix the issue.
I write application in Bare React Native with @callstack/repack

Simulator Screenshot - iPhone 8 Plus - 2023-09-03 at 15 06 59

@mweststrate
Copy link
Collaborator

mweststrate commented Sep 3, 2023 via email

@techgerm
Copy link

I'm building an Expo app in an Nx repo and I also get the following warning:

warning: The package /Users/german/repos/copies/flash-copy/node_modules/immer contains an invalid package.json configuration. Consider raising this issue with the package maintainer(s).
Reason: The resolution for "/Users/german/repos/copies/flash-copy/node_modules/immer" defined in "exports" is /Users/german/repos/copies/flash-copy/node_modules/immer/dist/immer.esm.mjs, however this file does not exist. Falling back to file-based resolution.

For me though, it seems to be having some serious side effects because I can't modify state directly (should be handled by immer) in my reducers. I'm forced to do the following workaround:

export const userSlice = createSlice({
  name: 'user',
  initialState,
  reducers: {
    setUser: (state, action: PayloadAction<UserSliceState>) => {
      // FIXME: Immer is not resolving properly in this project so state cannot be mutated directly
      return { ...state, ...action.payload };
    },
  },
});

Has anyone else experienced this side effect?

Versions:

  • "@reduxjs/toolkit": "^1.9.5",
  • "@nx/expo": "16.10.0",

@markerikson
Copy link
Contributor

@techgerm : That's because RTK 1.9 uses Immer 9, and neither of them are updated to work correctly with Node + ESM.

Please upgrade to our RTK 2.0 betas, which use Immer 10 and have specifically been updated for proper Node+ESM compatibility. I actually just published 2.0.0-beta.4 a few minutes ago:

https://github.com/reduxjs/redux-toolkit/releases/tag/v2.0.0-beta.4

@techgerm
Copy link

@markerikson ah gotcha okay thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants