You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
RFC: Load new chunk dependencies during HMR
#16013
Allow HMR to load new chunk dependencies before updated modules execute. This should work for both initial chunks and async chunks that the page has already loaded.
The missing step is resource loading. Updating a module factory does not automatically load the chunks that its new code needs.
Reuse the existing HMR download and apply phases. Download the required factories and stylesheets during prepare, install them during apply, and update chunk-loading state before accept callbacks run.
A fresh page loads the new files through its startup path. An already-open page does not run that path again.
Existing JavaScript factories can survive a move between chunks, so movement alone does not always break JavaScript. But a newly needed factory may be missing, and CSS needs the actual stylesheet to be loaded.
This is the resource-loading gap behind Rsbuild #8543. Reloading the page works, but loses application state.
Async chunks have the same gap
An async chunk normally loads through import(). HMR cannot assume that the application will call that import again.
Suppose the application calls loadPage() once. A later edit adds a shared dependency to page.js, and splitting puts that dependency in a new async chunk:
Rspack updates the generated loading code in loader.js. Even if HMR executes that module again, it only defines loadPage() again; it does not call the function.
Meanwhile, HMR can execute the updated page.js, which immediately requires the shared module. Its factory is missing because nothing loaded shared-async.
A local browser reproduction confirmed this sequence: the loader ran twice, but the import was called only once. HMR failed on the missing factory. Keeping the dependency in the page's update payload, or manually preloading the new shared chunk, made the update succeed. Manual preloading was a diagnostic control, not the proposed implementation.
Proposed behavior
For each chunk group already used by the page, make its newly required dependencies available before applying the update.
A chunk group is the set of chunks needed by one entry or one dynamic import. Its required set can change between compilations.
Old group: [page]
New group: [shared-async, page]
Already used by this page?
Yes -> prepare shared-async before applying the page update
No -> keep the group lazy; load it on a future import()
The new dependency does not have to be a newly created chunk. It may already exist in the compilation but have never been loaded by this page.
No application configuration should be required.
Implementation
1. Describe added dependencies
Compare each affected group's old and new chunk membership, separately for each runtime. Record which dependencies were added.
Use a general name such as load, rather than initial, because the same operation is needed by already-loaded async groups.
In this simple example, main and page are old chunk IDs that uniquely identify the affected entry and async group. The arrays contain the chunk IDs to supplement. None of these values are module IDs.
c lists regular update payloads. load independently describes supplemental payloads for newly required dependencies. A supplement-only chunk does not need to appear in c.
During prepare, collect regular updates from c for chunks already loaded or loading. Separately collect supplements from load for groups used by this page. Merge the requests, download each chunk's update payload once, and wait for all downloads before apply.
An existing chunk may also have a real code update and be loaded through a different group. Keep that regular update in c, even when the chunk also appears in load, so those pages receive it independently. The two fields may overlap; request deduplication handles that overlap.
Async activation needs care when groups share chunks. Loading a shared chunk through one group does not prove that another group was used. The general implementation must preserve group activation identity, using explicit group metadata where a chunk ID is not an unambiguous anchor. The example above does not define that part of the wire format.
Initial groups must also retain page isolation when multiple entries share one runtime, including dependOn parents.
2. Download during prepare
Reuse the same phase that downloads changed hot-update chunks. Its promises already block apply.
For a required chunk that the page has never loaded, emit a complete hot-update payload containing its module factories. A normal delta could omit unchanged modules that are absent from this page's runtime.
“Complete” describes the payload contents. The file still uses the hot-update format; it is not an ordinary chunk injected through ensure().
Its callback stages factories in hotCurrentUpdate and stages runtime functions alongside them. It does not immediately overwrite the live factory table.
3. Install during apply
After downloads complete and normal HMR acceptance checks succeed:
Dispose modules affected by real updates.
Install new and changed factories into __webpack_require__.m.
Execute updated runtime functions.
Mark successfully supplemented chunks as loaded and resolve relevant waiting promises.
Run accept callbacks and re-execute self-accepted modules.
Registering a factory does not execute its module. A later require() executes it through the existing module cache.
Synchronizing chunk-loading state is necessary for future imports and later HMR rounds. Downloading a payload without recording that its chunk was installed leaves those paths with an incorrect view of the runtime.
4. Preserve module identity and update versions
Moving an unchanged module between chunks should preserve its factory and cached instance. Explicit invalidation must still work, and real code changes must follow normal HMR acceptance rules.
Compare generated code for the receiving runtime. The same module ID can have different generated exports in different runtimes; belonging to the same compilation does not make those factories interchangeable. Duplicate payload delivery must not make correctness depend on network arrival order.
Supplemental JavaScript uses the existing hash-based hot-update filenames, tying it to the update being applied. Required historical assets must remain available while clients apply that update. CSS must also refer to the intended update's asset contents; a query parameter alone cannot pin a file whose contents are overwritten.
Missing update assets should follow existing failure handling, rather than silently loading files from a different compilation.
5. Prepare CSS replacements
Native CSS and extracted CSS need the same dependency reconciliation.
Load replacement stylesheets during prepare. Activate the replacements during apply before removing obsolete stylesheets. If preparation fails, retain the old stylesheets.
A chunk can gain or lose CSS without disappearing as a JavaScript chunk, so track stylesheet membership and filenames separately.
Preserve existing CSS ordering guarantees. This proposal does not add a global ordering guarantee across independent chunk groups.
Scope and trade-offs
This is a Rspack compiler/runtime change. It keeps existing splitting choices, lazy loading, and HMR acceptance behavior. An unaccepted update can still require a reload.
PR #15955 implements the initial-dependency part. General async-group activation and supplementation are proposed follow-up work, not functionality already provided by that PR.
Rsbuild's reload guard requires separate integration. This RFC does not propose disabling it before the relevant resource transitions are supported.
Complete supplemental payloads cost more bytes than module deltas, and group tracking adds compiler/runtime bookkeeping. Restricting supplementation to groups used by the current page avoids eagerly loading unopened features.
Fetching ordinary chunks is an alternative, but their normal callbacks write directly into the factory table. That would need additional work to preserve HMR staging and update-version consistency. Reusing hot-update payloads fits the existing transaction more closely.
Validation
Regression coverage should include:
Supplement-only chunks appearing in load without appearing in c, and overlapping regular updates downloading only once.
New dependencies of open entries and already-loaded async groups, with no second import() call.
Unopened async groups remaining lazy, including groups that share chunks.
Multiple pages sharing a runtime and entries using dependOn.
New factories, unchanged moved instances, explicit invalidation, and runtime-specific exports.
Native and extracted CSS additions, removals, and replacement failures.
Repeated updates, chunk removal and recreation, in-flight imports, and a server ahead of the browser.
Supported HMR chunk-loading modes, including JSONP and ESM.
Check requested files, installed factories, chunk-loading state, application state, and computed styles. A successful JavaScript callback alone does not prove CSS correctness.
Questions for discussion
What is the smallest reliable way to identify active async groups when they share chunks?
Should initial support land first, followed by the general load representation and async coverage?
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Initial implementation: #15955 — Load added initial chunk dependencies
Summary
Allow HMR to load new chunk dependencies before updated modules execute. This should work for both initial chunks and async chunks that the page has already loaded.
The missing step is resource loading. Updating a module factory does not automatically load the chunks that its new code needs.
Reuse the existing HMR download and apply phases. Download the required factories and stylesheets during prepare, install them during apply, and update chunk-loading state before accept callbacks run.
Motivation
Initial chunks
Consider an already-open page:
A fresh page loads the new files through its startup path. An already-open page does not run that path again.
Existing JavaScript factories can survive a move between chunks, so movement alone does not always break JavaScript. But a newly needed factory may be missing, and CSS needs the actual stylesheet to be loaded.
This is the resource-loading gap behind Rsbuild #8543. Reloading the page works, but loses application state.
Async chunks have the same gap
An async chunk normally loads through
import(). HMR cannot assume that the application will call that import again.For example:
Suppose the application calls
loadPage()once. A later edit adds a shared dependency topage.js, and splitting puts that dependency in a new async chunk:Rspack updates the generated loading code in
loader.js. Even if HMR executes that module again, it only definesloadPage()again; it does not call the function.Meanwhile, HMR can execute the updated
page.js, which immediately requires the shared module. Its factory is missing because nothing loadedshared-async.A local browser reproduction confirmed this sequence: the loader ran twice, but the import was called only once. HMR failed on the missing factory. Keeping the dependency in the page's update payload, or manually preloading the new shared chunk, made the update succeed. Manual preloading was a diagnostic control, not the proposed implementation.
Proposed behavior
For each chunk group already used by the page, make its newly required dependencies available before applying the update.
A chunk group is the set of chunks needed by one entry or one dynamic import. Its required set can change between compilations.
The new dependency does not have to be a newly created chunk. It may already exist in the compilation but have never been loaded by this page.
No application configuration should be required.
Implementation
1. Describe added dependencies
Compare each affected group's old and new chunk membership, separately for each runtime. Record which dependencies were added.
Use a general name such as
load, rather thaninitial, because the same operation is needed by already-loaded async groups.An illustrative JavaScript update manifest is:
{ "c": ["main", "page"], "r": [], "m": [], "load": { "main": ["shared-initial"], "page": ["shared-async"] } }In this simple example,
mainandpageare old chunk IDs that uniquely identify the affected entry and async group. The arrays contain the chunk IDs to supplement. None of these values are module IDs.clists regular update payloads.loadindependently describes supplemental payloads for newly required dependencies. A supplement-only chunk does not need to appear inc.During prepare, collect regular updates from
cfor chunks already loaded or loading. Separately collect supplements fromloadfor groups used by this page. Merge the requests, download each chunk's update payload once, and wait for all downloads before apply.An existing chunk may also have a real code update and be loaded through a different group. Keep that regular update in
c, even when the chunk also appears inload, so those pages receive it independently. The two fields may overlap; request deduplication handles that overlap.Async activation needs care when groups share chunks. Loading a shared chunk through one group does not prove that another group was used. The general implementation must preserve group activation identity, using explicit group metadata where a chunk ID is not an unambiguous anchor. The example above does not define that part of the wire format.
Initial groups must also retain page isolation when multiple entries share one runtime, including
dependOnparents.2. Download during prepare
Reuse the same phase that downloads changed hot-update chunks. Its promises already block apply.
For a required chunk that the page has never loaded, emit a complete hot-update payload containing its module factories. A normal delta could omit unchanged modules that are absent from this page's runtime.
“Complete” describes the payload contents. The file still uses the hot-update format; it is not an ordinary chunk injected through
ensure().Its callback stages factories in
hotCurrentUpdateand stages runtime functions alongside them. It does not immediately overwrite the live factory table.3. Install during apply
After downloads complete and normal HMR acceptance checks succeed:
__webpack_require__.m.Registering a factory does not execute its module. A later
require()executes it through the existing module cache.Synchronizing chunk-loading state is necessary for future imports and later HMR rounds. Downloading a payload without recording that its chunk was installed leaves those paths with an incorrect view of the runtime.
4. Preserve module identity and update versions
Moving an unchanged module between chunks should preserve its factory and cached instance. Explicit invalidation must still work, and real code changes must follow normal HMR acceptance rules.
Compare generated code for the receiving runtime. The same module ID can have different generated exports in different runtimes; belonging to the same compilation does not make those factories interchangeable. Duplicate payload delivery must not make correctness depend on network arrival order.
Supplemental JavaScript uses the existing hash-based hot-update filenames, tying it to the update being applied. Required historical assets must remain available while clients apply that update. CSS must also refer to the intended update's asset contents; a query parameter alone cannot pin a file whose contents are overwritten.
Missing update assets should follow existing failure handling, rather than silently loading files from a different compilation.
5. Prepare CSS replacements
Native CSS and extracted CSS need the same dependency reconciliation.
Load replacement stylesheets during prepare. Activate the replacements during apply before removing obsolete stylesheets. If preparation fails, retain the old stylesheets.
A chunk can gain or lose CSS without disappearing as a JavaScript chunk, so track stylesheet membership and filenames separately.
Preserve existing CSS ordering guarantees. This proposal does not add a global ordering guarantee across independent chunk groups.
Scope and trade-offs
This is a Rspack compiler/runtime change. It keeps existing splitting choices, lazy loading, and HMR acceptance behavior. An unaccepted update can still require a reload.
PR #15955 implements the initial-dependency part. General async-group activation and supplementation are proposed follow-up work, not functionality already provided by that PR.
Rsbuild's reload guard requires separate integration. This RFC does not propose disabling it before the relevant resource transitions are supported.
Complete supplemental payloads cost more bytes than module deltas, and group tracking adds compiler/runtime bookkeeping. Restricting supplementation to groups used by the current page avoids eagerly loading unopened features.
Fetching ordinary chunks is an alternative, but their normal callbacks write directly into the factory table. That would need additional work to preserve HMR staging and update-version consistency. Reusing hot-update payloads fits the existing transaction more closely.
Validation
Regression coverage should include:
loadwithout appearing inc, and overlapping regular updates downloading only once.import()call.dependOn.Check requested files, installed factories, chunk-loading state, application state, and computed styles. A successful JavaScript callback alone does not prove CSS correctness.
Questions for discussion
loadrepresentation and async coverage?All reactions