Commit b962256
authored
fix(esm_library): extract TLA shared modules to break circular dependency (#13606)
* fix(esm_library): extract TLA shared modules to break circular dependency
When using ESM library output with top-level await (TLA), a circular
dependency occurs: the main chunk awaits the async chunk (because it has
TLA), but the async chunk imports shared modules from the main chunk.
This adds `extract_tla_shared_modules()` which scans async chunks'
dependency graphs and extracts modules that reside in ancestor chunks
into separate chunks to break the cycle.
Also emits a warning when TLA is used with ESM library output, as the
bundled execution order may differ from the original source.
* fix: address review feedback for TLA shared module extraction
- Reconnect entry modules to new chunk with entrypoint metadata after
split, preventing entrypoints from losing their startup module
- Group extracted modules by their source chunk set to avoid duplicating
a module into multiple new chunks when it appears in multiple ancestors
- Filter inactive connections via is_target_active() during BFS to avoid
over-approximating the dependency graph
- Fix doc comment: clarify that early exit skips chunk-graph scanning,
not all computation
* fix: only emit TLA warning when circular dep is actually detected
- Fix cargo fmt: merge std imports, fix line break
- Change extract_tla_shared_modules to return true only when modules
were actually extracted, not just when TLA exists
- Only emit the warning diagnostic when a circular dependency is found
and shared modules are extracted, avoiding false warnings in tests
that legitimately use TLA
* fix: remove redundant has_tla check and use is_only_initial
- Remove redundant has_tla early exit since async_chunks.is_empty()
already covers the no-TLA case
- Use is_only_initial instead of can_be_initial to also catch chunks
that belong to both initial and non-initial groups
* perf: single-pass BFS with shared visited set for TLA extraction
Replace per-async-chunk independent BFS with a single BFS pass sharing
one visited set across all async chunks. Pre-compute a reverse map
(chunk → which async chunks consider it an ancestor) to enable O(1)
ancestor lookups during traversal.
Complexity: O(N + E + A × G) down from O(A × (N + E + G))
where N=modules, E=edges, A=async chunks, G=chunk groups.
* test: enable TLA split-sync-modules tests that were previously skipped
Remove test.filter.js from split-sync-modules-{1,2,3} tests that were
skipped with "cycle caused by available modules". These tests exercise
the exact circular dependency scenario that extract_tla_shared_modules
now fixes.
Add warnings.js to expect the TLA circular dependency warning that is
emitted when shared modules are extracted.
* fix: fix clippy collapsible_if and restore skipped TLA tests
- Fix clippy collapsible_if lint by merging nested if-let + if
- Restore test.filter.js for split-sync-modules tests: the chunk
extraction alone is not sufficient to break the cycle in ESM
rendering — additional render-side changes are needed
- Add side_effects_state_artifact param for is_target_active API change
* test: enable TLA split-sync-modules tests
Remove test.filter.js so the tests actually execute, and add warnings.js
to expect the TLA circular dependency warning.
* fix: detect async chunks by TLA-sourced dynamic imports
Previously we detected "async chunks" as non-initial chunks containing
a module marked async. This missed the actual cycle scenario: the async
chunk's modules themselves are sync; what matters is that the importing
module has TLA and uses await-import to load the chunk.
New detection: scan all modules with has_top_level_await, follow their
DynamicImport dependencies, and mark the non-initial target chunks as
potentially problematic. This correctly covers the
split-sync-modules test cases where the imported chunk re-exports from
a lib that sits in the parent chunk due to available-modules dedup.
* fix: correctly handle entry modules in TLA chunk extraction
Previously the extraction reconnected ALL moved modules as entry modules
to every initial entrypoint group of the new chunk. This was wrong —
only modules that were ORIGINALLY entry modules in their source chunks
should be reconnected, and only to their original entrypoint group.
Wrongly promoting shared library modules (like lib.js) to entrypoint
status corrupted the chunk graph and caused runtime deadlocks.
Key changes:
- Only reconnect modules as entry modules if they WERE entry modules in
some source chunk (tracked per-source-chunk via
get_chunk_entry_modules_with_chunk_group_iterable lookup before
disconnecting)
- Use the ORIGINAL entrypoint group when reconnecting, not "all initial
groups of the new chunk"
- Skip DynamicImport dependencies during BFS — they produce promises
and don't force the target chunk to be evaluated synchronously, so
they can't cause the TLA deadlock
- Set chunk_reason for debuggability
This mirrors the pattern used by RemoveDuplicateModulesPlugin.
* test: add snapshot files for TLA split-sync-modules tests
Run with UPDATE_SNAPSHOT=true locally confirms all 3 tests pass:
lib.js is extracted into a separate chunk, main.mjs and async.mjs
both import from it — no circular dependency.
All 198 ESM output tests pass, no regressions.
* fix: per-async-chunk BFS to correctly handle multi-origin shared modules
Previously the BFS used a shared visited set with single-origin tracking
per module. This was buggy when a module was reachable from multiple
async chunks whose ancestors differ:
- Module M lives in chunks [P1, P2]
- P1 is ancestor of async chunk A1, P2 is ancestor of A2
- A1's BFS reaches M first, records extract from P1
- A2's BFS skips M via visited, never records extract from P2
- Result: cycle between A2 and P2 remains
Fix: run an independent BFS per async chunk with its own visited set.
Each module is analyzed in the context of the async chunk actually
traversing it, so extractions for different ancestor→async edges don't
collide. Complexity goes from O(N+E+A·G) back to O(A·(N+E+G)), but
correctness trumps the marginal speedup — A is usually small.
Also add a note about the Phase 1 over-inclusion of function-scoped
import()s; accepted as safe because extraction only fires when real
cross-chunk static cycles exist.1 parent d79172e commit b962256
11 files changed
Lines changed: 376 additions & 5 deletions
File tree
- crates/rspack_plugin_esm_library/src
- tests/rspack-test/esmOutputCases/top-level-await
- split-sync-modules-1
- __snapshots__
- split-sync-modules-2
- __snapshots__
- split-sync-modules-3
- __snapshots__
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | | - | |
| 1 | + | |
2 | 2 | | |
3 | 3 | | |
4 | 4 | | |
| |||
15 | 15 | | |
16 | 16 | | |
17 | 17 | | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
18 | 264 | | |
19 | 265 | | |
20 | 266 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
43 | 43 | | |
44 | 44 | | |
45 | 45 | | |
46 | | - | |
| 46 | + | |
47 | 47 | | |
48 | 48 | | |
49 | 49 | | |
| |||
697 | 697 | | |
698 | 698 | | |
699 | 699 | | |
| 700 | + | |
| 701 | + | |
| 702 | + | |
| 703 | + | |
| 704 | + | |
| 705 | + | |
| 706 | + | |
| 707 | + | |
| 708 | + | |
| 709 | + | |
| 710 | + | |
| 711 | + | |
700 | 712 | | |
701 | 713 | | |
702 | 714 | | |
| |||
Lines changed: 32 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
Lines changed: 0 additions & 1 deletion
This file was deleted.
Lines changed: 1 addition & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
Lines changed: 34 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
Lines changed: 0 additions & 1 deletion
This file was deleted.
Lines changed: 1 addition & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
0 commit comments