Module resolution is inconsistent between browser and server builds
Verify canary release
- [X] I verified that the issue exists in Next.js canary release
Provide environment information
/bin/sh: pnpm: command not found
Operating System:
Platform: darwin
Arch: arm64
Version: Darwin Kernel Version 21.3.0: Wed Jan 5 21:37:58 PST 2022; root:xnu-8019.80.24~20/RELEASE_ARM64_T6000
Binaries:
Node: 16.13.1
npm: 8.1.2
Yarn: 1.22.17
pnpm: N/A
Relevant packages:
next: 12.1.1-canary.17
react: 17.0.2
react-dom: 17.0.2
What browser are you using? (if relevant)
Chrome 99.0.4844.74
How are you deploying your application? (if relevant)
next dev
Describe the Bug
When creating the browser and server bundles, Next.js handles module resolution differently. Specifically, when bundling for Node.js, Next.js will use the exports field in package.json but not the module field. When bundling for the browser, it will support both exports and module.
This bug results in there being two instances of a peer dependency in the server build only (i.e., not in the client build). This causes issues with React hooks, for example.
Expected Behavior
The expected behavior is that both bundles are consistent.
To Reproduce
git clone https://github.com/migueloller/next-module-bug
cd next-module-bug
yarn
yarn workspace app dev // visit http://localhost:3000 and compare logs between browser and server
Browser logs should show:
{ moduleLib: 'esm', exportsLib: 'esm' }
Server logs should show:
{ moduleLib: 'cjs', exportsLib: 'esm' }
I've updated the repro to include an analysis of the webpack bundle. Run ANALYZE=true yarn workspace app build and search for chunk in the server build. You will see that there's chunk.es.js and chunk.cjs.js present. In the browser build there's just chunk.es.js present.
In our own application, this error manifested itself with a React context provider and hook not matching since the Next.js app which rendered the provider was using the ES module version of the chunk, while a component library that was using the hook, was using the Common JS version of the chunk.
Hopefully the reproduction repo helps!
EDIT: Also notice the logs in the server:
chunk-cjs
chunk-esm
Only one of these chunks should ever be executed on the server, not both.
I've just updated the reproduction so that it shows the peer dependency mismatch more explicitly:
import 'exports-lib' // this is the peer dependency
export const moduleType = 'esm'
export function check(exportsLib) {
if (exportsLib !== moduleType) throw new Error('Peer dependency mismatch!')
}
It's also worth mentioning that this issue wouldn't happen if we weren't using Yarn Workspaces. In the case where the peer dependency is in node_modules and it's not symlinked, it will use the CommonJS module regardless from where it's imported (i.e., the Next.js app or the library).
Ran into this issue today with Next 12.3 with pixi.js.
Hacky fix was to yarn patch pixi.js and remove references to the esm modules from the package.json file. That causes Next to always import the CJS version on client and server.
Related #38611