-
Notifications
You must be signed in to change notification settings - Fork 27k
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
Module incorrectly persists between hot updates (HMR) in the browser #69098
Comments
The only unusual thing that may be related is the MSW setup: Can some of these hooks/functions lead to component rendering being messed up? |
Exploration
// next.config.js
module.exports = {
reactStrictMode: false,
}
The last point makes me believe that the way |
Okay, I think I understand better what's happening now. It looks like HRM doesn't terminate previously attached request handlers (see the screenshot below). This is a screenshot of a duplicate request log in the console after HMR. You can see that although HMR has happened, and I've modified the handler, the "old" handler is still firing (the first one), taking precedence over the updated handler (the second one).
This is not a bug in MSW. If you observe the handlers between hot updates, you will see that The fact that the old handler persists and still works is likely related to how the module is re-evaluated. @feedthejim @eps1lon, with this new intel, do you have any thoughts on what may be causing this? Suggestions on how to debug this further are also welcome. |
WorkaroundAs I suspected, the "cleanup" of handlers is not happening somewhere in memory in the If I apply this diff, the issue is fixed: // mocks/browser.ts
import { setupWorker } from 'msw/browser'
-import { handlers } from './handlers'
-export const worker = setupWorker(...handlers)
+export const worker = setupWorker() // msw-provider.tsx
'use client'
import { Suspense, use } from 'react'
+import { handlers } from '../mocks/handlers'
// ...
await worker.start()
+worker.use(...handlers)
Can it be that webpack incorrectly updates the modules in HMR? I think Remix used to have the exact issue, but the they've fixed it somehow (cc @pcattori). It must not matter where you import the handlers. If the hot update originates in
But this update path seems to be faulty, resulting in the old and new handlers array persisting at the same time across hot updates. |
@sebws, thanks for providing more insight. Yeah, I'd expect that. The If it was a typical leak, we'd have N number of workers across N hot updates, each with their own set of handlers. But that's not the case. The latest worker instance accumulates all previous handlers, like I've illustrated on the screenshot above. |
@kettanaito ah yep, I did that screenshot without the workaround. with the workaround, on hot reload it is no longer initialising a new worker (I suppose since there's no import of handlers into browser.ts), so adding a |
just wondering, is starting up a new worker preferable? as far as I've seen before that's normally the way rather than a resetHandlers call so I'm curious if there's a particular reason |
The thing is, your module is supposed to produce the same result upon evaluation during HMR, normally. We've gathered multiple examples of how other frameworks handle a nearly identical server-side and client-side setups: Remix, Svelte, Vue. There's no need to perform any magic around worker or server. When HMR comes, the old module gets thrown away, the changed module takes place. The other frameworks also act as an additional proof that the issue doesn't lie with MSW, otherwise, anywhere you use
It works because it "remediates" the problem by clearing the handlers that persisted between hot updates before attaching new handlers. That's a workaround, not a solution I can recommend.
Well, it's the initial browser integration so, yes, it's not only preferable but is the only way to enable MSW in the browser. |
I'm a little confused by this. What I was seeing, without your next specific workaround, was exactly that, a new worker per hot update. Only when you change it (in the workaround) do you get the issue you're describing with the handlers being merged. Which makes sense to me, since you're now calling the methods on the same worker (without any reason for it to have been reloaded). So you call start, get redundant start message then add the new handlers to the pre-existing worker. Am I missing something? |
That is not the case. You can follow the reproduction steps in the first post to get the problematic behavior I'm describing. The list of handlers persists across HMR when it shouldn't. My workaround showcases that it's an import problem. |
You get duplicate logs because there are old handler surviving HMR, as I've shown here. You don't see duplicates in The old and new evaluation of the same module overlap, which makes it tricky to understand what's going on. |
I would love to hear some input on this from the Next.js team. This looks like a webpack issue. I really, really hate to ping about this, but it's been over a month since this has been reported. An initial assessment would be great to have to see how we can move forward with this. cc @eps1lon. |
Seems like we’re saying the same thing then, because that’s what I’m saying. That you can see old handlers but it’s because the old worker has persisted (with old handlers), not one worker with x handlers per refresh. |
I'm having some luck :) It's another funny workaround/dodgy type of thing, however I added the following to module.hot?.dispose(() => { worker.stop(); }); as well as moving the fallback value in the Suspense from With this, on HMR you get just the one handler persisting! I don't think this is a sustainable solution but might hopefully help in seeing what is happening |
Actually, looks like it's even better to just put |
I was getting some odd issues with just worker.stop. Moving it to an arrow function |
Looks like it's still an issue in [email protected] |
It will be solved in 16 XD. |
I think I have found why exactly I'm hoping to see now why the other frameworks aren't having this as an issue if this is the reason. |
@sebws, such an interesting find, thank you for looking into this! I am surprised HMR doesn't kill that interval though. It should be a part of garbage collection from the previous "frame", and it's not even something the framework is doing. Context: the interval is there to keep the worker-client channel alive (it gets killed if nothing has been transferred on it in some time). We won't be removing that interval, it's intentional and needed. Since HMR destroys the previous execution context and its worker, I'd expect it to clear that interval as well. The fact that this doesn't cause issues in other frameworks still circles out Next.js and, likely webpack handling HMR. |
Link to the code that reproduces this issue
mswjs/examples#101
To Reproduce
pnpm install
.cd examples/with-next
.pnpm dev
src/mocks/handlers.ts
. Change the payload of thegraphql.query()
handler (e.g. remove any word from a movie title).Current vs. Expected behavior
Current behavior
The entire
MovieList
component gets re-rendered a bunch of times on hot update tohandlers.ts
. Re-rendering is expected, but it looks like Next.js re-applies event listeners to the same button multiple times.This is not an MSW issue. You can log something in the
MovieList
component manually, and see that it re-renders quite a lot. I suspect during those re-renderings, theonClick
listener gets applied more than it needs to.The number of times the listener is excessively applied is directly proportionate to the number of HMR changes issued (e.g. 1 change = 2 listeners; 2 changes = 3 listeners; etc).
Provide environment information
Operating System: Platform: darwin Arch: x64 Version: Darwin Kernel Version 23.3.0: Wed Dec 20 21:28:58 PST 2023; root:xnu-10002.81.5~7/RELEASE_X86_64 Available memory (MB): 65536 Available CPU cores: 16 Binaries: Node: 18.19.0 npm: 10.2.3 Yarn: 1.22.10 pnpm: 9.6.0 Relevant Packages: next: 15.0.0-canary.121 // Latest available version is detected (15.0.0-canary.121). eslint-config-next: N/A react: 19.0.0-rc-14a4699f-20240725 react-dom: 19.0.0-rc-14a4699f-20240725 typescript: 5.3.3 Next.js Config: output: N/A
Which area(s) are affected? (Select all that apply)
Developer Experience, Runtime
Which stage(s) are affected? (Select all that apply)
next dev (local)
Additional context
No response
The text was updated successfully, but these errors were encountered: