next.js icon indicating copy to clipboard operation
next.js copied to clipboard

Module resolution is inconsistent between browser and server builds

Open migueloller opened this issue 3 years ago • 4 comments

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' }

migueloller avatar Mar 24 '22 17:03 migueloller

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.

migueloller avatar Mar 24 '22 17:03 migueloller

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!')
}

migueloller avatar Mar 25 '22 16:03 migueloller

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).

migueloller avatar Mar 25 '22 17:03 migueloller

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

mutewinter avatar Oct 26 '22 20:10 mutewinter