react-refresh-webpack-plugin
react-refresh-webpack-plugin copied to clipboard
Webpack top level await doesn't work correctly
Hello pmmmwh, I found out that your lib doesn't fully support TLA, let me give an example:
We have 3 files in the same folder: App.jsx, A.jsx and B.jsx
App.jsx
import A from './A'
import B from './B'
export default function App() {
return <div>App</div>
}
A.jsx
await new Promise((resolve) => setTimeout(() => resolve(0), 1000))
B.jsx
await new Promise((resolve) => setTimeout(() => resolve(0), 2000))
The flow of setup & cleanup is (I printed it out):
-
setup(App):$Refresh$.moduleId = App,$Refresh$.cleanup = cleanup_of_App -
setup(A):$Refresh$.moduleId = A,$Refresh$.cleanup = cleanup_of_ABecause A is still loading, so it's not finished yet, meanwhile, start loading B (it's how webpack works) -
setup(B):$Refresh$.moduleId = B,$Refresh$.cleanup = cleanup_of_BBecause B is still loading, so it's not finished yet, meanwhile, A is finished -
cleanup(A): Calling$Refresh$.cleanup (= cleanup_of_B), inside the function,currentModuleId !== cleanupModuleIdso the cleanup don't happen,$Refresh$don't change. Furthermore, when the code runs intogetRefreshModuleRuntimeblock,$ReactRefreshModuleId$gets wrong value
const $ReactRefreshModuleId$ = __webpack_require__.$Refresh$.moduleId; // In A.jsx, the value is B.jsx
const $ReactRefreshCurrentExports$ = __react_refresh_utils__.getModuleExports(
$ReactRefreshModuleId$
);
And because the cleanup don't happen, it trigger a domino effect, leads to every file has wrong $ReactRefreshModuleId$ value, leads to the HMR don't work properly
About the timeout number
The reason I set timeout of B (2000ms) higher than A (1000ms) is that I want A to be finished before B (while the current $Refresh$ is still storing values of B). If I set timeout of B smaller than A, everything works normally.
The root cause
The root cause of this issue is the way webpack handle multiple imported async modules. Let's say, if App.jsx import 3 async modules A.jsx, B.jsx and C.jsx in the order A > B > C, the order when they are resolved can be randomly (A > C > B, B > C > A, etc). Therefore leads to mismatch __webpack_require__.$Refresh$.moduleId
My suggest solution
Let's go back to the code of getRefreshModuleRuntime block, can we change
const $ReactRefreshModuleId$ = __webpack_require__.$Refresh$.moduleId;
into
const $ReactRefreshModuleId$ = module.id;
That will ensure the correct module id always. What I don't understand is why you don't use it in the first place, is there any case that __webpack_require__.$Refresh$.moduleId should be different from module.id?
Thank you @pmmmwh and others, I attach a working example below, please give it a try
Thanks for submitting this! I've been seeing similar behavior when webpack has asyncWebAssembly enabled and modules import a library with async wasm (e.g. rapier). Refreshes no-op with no explanation in the console.
The workaround was to avoid this type of import dependency for now.