parcel icon indicating copy to clipboard operation
parcel copied to clipboard

HMR doesn't work in a monorepo folder structure

Open lilyannh opened this issue 2 years ago • 9 comments

🐛 bug report

Fast refresh changes don't apply when making code changes

🎛 Configuration

My folder structure is as follows:

- /app1
    - /src
    - package.json (just scripts and aliases)
- /app2
    - /src
    - package.json (just scripts and aliases)
- /super-simple-test-react-app
    - /src
    - package.json
- /packages
    - /package1
    - /package2
- package.json (has all dependencies)

😯 Current Behavior

Websocket sees the update come in, the console clears, but the page doesn't actually update.

💁 Possible Solution

When I move the src folder from my test to the root, HMR works as expected.

- /app1
    - /src
    - package.json (just scripts and aliases)
- /app2
    - /src
    - package.json (just scripts and aliases)
- /src-from-super-simple-test-react-app <---- move to root
- /packages
    - /package1
    - /package2
- package.json (has all dependencies) <----add parcel serve script here

🌍 Your Environment

Software Version(s)
Parcel 2.3.2
Node 16.13.0
npm/Yarn 8.1.4
Operating System Windows 10

lilyannh avatar Mar 04 '22 20:03 lilyannh

I had a similar problem and creating a separate frontend only package.json and launching from the a separate frontend CWD solved the problem. In my case I believe this was due to babel plugins and other settings interfering with parcel.

To further investigate your issue it would be very helpful if you could upload a sample repo demonstrating the problem.

denysonique avatar Mar 07 '22 19:03 denysonique

I've created a reproduction here: https://github.com/lilyannh/parcel-issue-7853

lilyannh avatar Mar 31 '22 05:03 lilyannh

This is resolved by making sure that parcel is a dependency of each package and not a dependency of the project itself.

probablykabari avatar Apr 27 '22 17:04 probablykabari

I'm having the same issue in my monorepo. Unfortunately it's not open sourced(contracted).

I do have parcel installed only in the package with the react app.

BlackFenix2 avatar May 14 '22 06:05 BlackFenix2

welp, i found the culprit. i had to run yarn why react-refresh and found that @storybook/react was using react-refresh v11 when parcel was using v9. i removed @storybook/react to troubleshoot and now my monorepo HMR is working like a charm.

@lilyannh try running yarn why react-refresh or npm ls react-refresh and see if you get more than one version.

BlackFenix2 avatar May 14 '22 15:05 BlackFenix2

This worked for me, I have a browser extension in one directory and a Next.js website in another. Next.js was using React refresh 0.8.3 and the browser extension (which uses Parcel) was on 0.9.0 so I updated Next.js and it works like a charm now!

Thanks @BlackFenix2 you saved my thousands of F5 presses!

Southclaws avatar May 16 '22 21:05 Southclaws

@BlackFenix2 same situation of having Storybook in a monorepo, I just forced a resolution of react-refresh and it seems to be working much better!


	"resolutions": {
		"**/react-refresh": "^0"
	}

Offirmo avatar May 19 '22 04:05 Offirmo

No I only have one version of react-refresh in my project :(

lilyannh avatar May 28 '22 21:05 lilyannh

I believe I've tracked down a root cause here (at least, for one version of this scenario that seems to match the above).

TL;DR for people trying to work around this

  • The parcel plugin setup for fast refresh is brittle and needs react-refresh to be deduped
  • If you run npm ls react-refresh and don't see deduped by @parcel/runtime-react-refresh and @parcel/transformer-react-refresh-wrap, you'll be affected by this bug
  • Example: react-native caused this issue for me by having a transitive dependency on react-refresh that was a different version
  • Workaround: use package.json's overrides field to pin the correct version
    • You need npm>8.3 and will likely need to blow away node_modules and package-lock.json to get a fresh install. If you're on a monorepo, overrides has to go on the root package.json.

Root cause

React's Fast Refresh is implemented across two Parcel plugins -- @parcel/runtime-react-refresh and @parcel/transformer-react-refresh-wrap -- these both have a dependency on react-refresh. Crucially they must load the same version of react-refresh, as that library uses some module level state. If the plugins have different modules, fast refresh doesn't work.

The key problem here is that npm will happily get into a state where the react-refresh module lives in a different place for those two plugins, even if the version is the same. You can confirm this with npm ls react-refresh -- if it doesn't have a deduped for the versions used by the plugins, they live in different places and resolve to different modules when imported by each of those plugins.

This is easy to have bite you if you have some other transitive dependency. In my case it came from react-native.

The fix?

TBH I'm not sure what the right approach is here. The core issue is having react-refresh get used/required by two different plugins. You're at the mercy of NPM's deduping which is a little inscrutable.

Maybe having some intermediary wrapping library over react-refresh would work? It'd be much less likely to be non-deduped, at least.

shz avatar Jul 18 '22 05:07 shz

@shz Thank you!! This issue has been driving me crazy and it was very difficult to find the root cause. For me, the issue wasn't even different versions of the module, it was the fact that an identical version was not de-duped. See the following dep tree:

$ npm ls react-refresh
[email protected]
└─┬ [email protected]
  └─┬ @parcel/[email protected]
    ├─┬ @parcel/[email protected]
    │ └── [email protected]
    └─┬ @parcel/[email protected]
      └── [email protected]

Workaround

npm dedupe react-refresh

Running the above resulted in the following dependency tree and fixed react-refresh for me:

[email protected]
└─┬ [email protected]
  └─┬ @parcel/[email protected]
    ├─┬ @parcel/[email protected]
    │ └── [email protected]
    └─┬ @parcel/[email protected]
      └── [email protected] deduped

Maybe @parcel/runtime-react-refresh should list react-refresh as a peer dependency?

robert-w-gries avatar Nov 18 '22 14:11 robert-w-gries

I'm also working on a monorepo and if I want to see the changes that I made to the code reflected on the browser I need to stop the dev server, delete .parcel-cache, and start the server again 😢

yarn why react-refresh returns this:

├─ @parcel/runtime-react-refresh@npm:2.8.3
│  └─ react-refresh@npm:0.9.0 (via npm:^0.9.0)
│
└─ @parcel/transformer-react-refresh-wrap@npm:2.8.3
   └─ react-refresh@npm:0.9.0 (via npm:^0.9.0)

yarn dedupe react-refresh doesn't solve the issue, neither does setting the version on package.json

edit: I did some digging and it works fine with @parcel/watcher@npm:2.0.7 breaks with 2.1.0.

alcercu avatar Jan 19 '23 19:01 alcercu

This issue has been automatically marked as stale because it has not had recent activity. It will be closed in 14 days if no further activity occurs.

github-actions[bot] avatar Jul 19 '23 12:07 github-actions[bot]