perf(externals-loading): merge multiple fetchBundle calls for the same URL#2307
perf(externals-loading): merge multiple fetchBundle calls for the same URL#2307
Conversation
🦋 Changeset detectedLatest commit: 82e7124 The changes in this PR will be included in the next version bump. This PR includes changesets to release 2 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
Important Review skippedReview was skipped due to path filters ⛔ Files ignored due to path filters (1)
CodeRabbit blocks several paths by default. You can override this behavior by explicitly including those paths in the path filters. For example, including ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughMerges multiple fetchBundle calls targeting the same URL into a single request by changing runtime code generation to use a URL-keyed Map and Set; adds tests, test configs, and mock external bundles to verify the merge behavior. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
7883c13 to
1a2c431
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
1a2c431 to
3304a22
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
packages/webpack/externals-loading-webpack-plugin/test/cases/externals-loading/merge-fetchBundle-calls/index.js (1)
11-28: Consider adding a clarifying comment for the assertion logic.The split-based counting technique works correctly (split produces N+1 parts for N occurrences), but the magic number
2may not be immediately obvious to future readers. A brief comment explaining the expected behavior would improve maintainability.📝 Suggested improvement
const backgroundFetchBundleCalls = background.split('fetchBundle' + '('); const mainThreadFetchBundleCalls = mainThread.split('fetchBundle' + '('); + // split produces N+1 parts for N occurrences; expect exactly 1 fetchBundle call per file expect(backgroundFetchBundleCalls.length).toBe(2); expect(mainThreadFetchBundleCalls.length).toBe(2);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/webpack/externals-loading-webpack-plugin/test/cases/externals-loading/merge-fetchBundle-calls/index.js` around lines 11 - 28, The assertions using background.split('fetchBundle' + '(') and mainThread.split('fetchBundle' + '(') compare lengths to the magic number 2, which represents one occurrence (split yields N+1 parts); add a short clarifying comment above the two expect lines (near the test "should merge fetchBundle calls" or next to variables backgroundFetchBundleCalls and mainThreadFetchBundleCalls) that explains "split returns N+1 parts so length===2 means one fetchBundle call" to make the intent clear to future readers.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/webpack/externals-loading-webpack-plugin/src/index.ts`:
- Around line 418-422: The generated async load code references handler${i} (in
the loadCode.add call using mountVar and createLoadExternalAsync) but handlers
are emitted in URL-unique order, so handler indices don’t match external indices
and can be undefined; fix by resolving the correct handler variable for the
external’s URL before emitting the async string (e.g. compute or look up the
emitted handler index/variable for this external — call it handlerForUrl or
handlerIndex — or capture the handler into a const in the loop) and use that
resolved handler variable in createLoadExternalAsync; apply the same change
where handlers are emitted later (the handler emission block) so both places
reference the same handler variable mapping instead of handler${i}.
---
Nitpick comments:
In
`@packages/webpack/externals-loading-webpack-plugin/test/cases/externals-loading/merge-fetchBundle-calls/index.js`:
- Around line 11-28: The assertions using background.split('fetchBundle' + '(')
and mainThread.split('fetchBundle' + '(') compare lengths to the magic number 2,
which represents one occurrence (split yields N+1 parts); add a short clarifying
comment above the two expect lines (near the test "should merge fetchBundle
calls" or next to variables backgroundFetchBundleCalls and
mainThreadFetchBundleCalls) that explains "split returns N+1 parts so length===2
means one fetchBundle call" to make the intent clear to future readers.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 5a6fe5f8-0e72-4f44-b904-eacdbd06f616
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (17)
.changeset/merge-fetchbundle-calls.mdpackages/webpack/externals-loading-webpack-plugin/package.jsonpackages/webpack/externals-loading-webpack-plugin/src/index.tspackages/webpack/externals-loading-webpack-plugin/test/cases.test.tspackages/webpack/externals-loading-webpack-plugin/test/cases/externals-loading/merge-fetchBundle-calls/index.jspackages/webpack/externals-loading-webpack-plugin/test/cases/externals-loading/merge-fetchBundle-calls/rspack.config.jspackages/webpack/externals-loading-webpack-plugin/test/cases/externals-loading/merge-fetchBundle-calls/test.config.cjspackages/webpack/externals-loading-webpack-plugin/test/helpers/external-bundle-mock/bar/index.jspackages/webpack/externals-loading-webpack-plugin/test/helpers/external-bundle-mock/bar/package.jsonpackages/webpack/externals-loading-webpack-plugin/test/helpers/external-bundle-mock/baz/common.jspackages/webpack/externals-loading-webpack-plugin/test/helpers/external-bundle-mock/baz/index.jspackages/webpack/externals-loading-webpack-plugin/test/helpers/external-bundle-mock/baz/package.jsonpackages/webpack/externals-loading-webpack-plugin/test/helpers/external-bundle-mock/baz/sub1/index.jspackages/webpack/externals-loading-webpack-plugin/test/helpers/external-bundle-mock/baz/sub2/index.jspackages/webpack/externals-loading-webpack-plugin/test/helpers/external-bundle-mock/foo/index.jspackages/webpack/externals-loading-webpack-plugin/test/helpers/external-bundle-mock/foo/package.jsonpackages/webpack/externals-loading-webpack-plugin/test/helpers/setup-env.js
There was a problem hiding this comment.
♻️ Duplicate comments (1)
packages/webpack/externals-loading-webpack-plugin/src/index.ts (1)
418-422:⚠️ Potential issue | 🔴 CriticalCritical: async path still uses external index instead of URL handler index.
At Line 419,
handler${i}is keyed by external iteration index, but handlers are emitted by unique URL order at Line 438-Line 440. With shared URLs, this can reference an undefined handler and break loading at runtime.🐛 Proposed fix
url2fetchCode.set( url, `lynx.fetchBundle(${JSON.stringify(url)}, {});`, ); + const handlerIndex = [...url2fetchCode.keys()].indexOf(url); const mountVar = `${ getLynxExternalGlobal( externalsLoadingPluginOptions.globalObject, ) }[${JSON.stringify(libraryNameStr)}]`; if (async) { loadCode.add( - `${mountVar} = ${mountVar} === undefined ? createLoadExternalAsync(handler${i}, ${ + `${mountVar} = ${mountVar} === undefined ? createLoadExternalAsync(handler${handlerIndex}, ${ JSON.stringify(layerOptions.sectionPath) }) : ${mountVar};`, ); continue; } loadCode.add( `${mountVar} = ${mountVar} === undefined ? createLoadExternalSync(handler${ - [...url2fetchCode.keys()].indexOf(url) + handlerIndex }, ${ JSON.stringify(layerOptions.sectionPath) }, ${timeout}) : ${mountVar};`, );Also applies to: 438-440
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/webpack/externals-loading-webpack-plugin/src/index.ts` around lines 418 - 422, The async-load line uses handler${i} (external iteration index) but the actual handlers are emitted in URL-unique order, so with duplicate URLs handler${i} can be undefined; change the generated async path to look up the handler by the URL's emitted index (e.g., use the same handlerIndex mapping used when emitting the handlers array — reference the handlers emission logic that produces the handlers list/ordering) and pass that handler (or its index) into createLoadExternalAsync instead of handler${i}; update the code that pushes into loadCode (the line assigning mountVar using createLoadExternalAsync) to use the URL-derived handler lookup so async and sync paths reference the same handler.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@packages/webpack/externals-loading-webpack-plugin/src/index.ts`:
- Around line 418-422: The async-load line uses handler${i} (external iteration
index) but the actual handlers are emitted in URL-unique order, so with
duplicate URLs handler${i} can be undefined; change the generated async path to
look up the handler by the URL's emitted index (e.g., use the same handlerIndex
mapping used when emitting the handlers array — reference the handlers emission
logic that produces the handlers list/ordering) and pass that handler (or its
index) into createLoadExternalAsync instead of handler${i}; update the code that
pushes into loadCode (the line assigning mountVar using createLoadExternalAsync)
to use the URL-derived handler lookup so async and sync paths reference the same
handler.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: bd673249-6d3a-43c5-9a1d-aa7861c1b7c9
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (17)
.changeset/merge-fetchbundle-calls.mdpackages/webpack/externals-loading-webpack-plugin/package.jsonpackages/webpack/externals-loading-webpack-plugin/src/index.tspackages/webpack/externals-loading-webpack-plugin/test/cases.test.tspackages/webpack/externals-loading-webpack-plugin/test/cases/externals-loading/merge-fetchBundle-calls/index.jspackages/webpack/externals-loading-webpack-plugin/test/cases/externals-loading/merge-fetchBundle-calls/rspack.config.jspackages/webpack/externals-loading-webpack-plugin/test/cases/externals-loading/merge-fetchBundle-calls/test.config.cjspackages/webpack/externals-loading-webpack-plugin/test/helpers/external-bundle-mock/bar/index.jspackages/webpack/externals-loading-webpack-plugin/test/helpers/external-bundle-mock/bar/package.jsonpackages/webpack/externals-loading-webpack-plugin/test/helpers/external-bundle-mock/baz/common.jspackages/webpack/externals-loading-webpack-plugin/test/helpers/external-bundle-mock/baz/index.jspackages/webpack/externals-loading-webpack-plugin/test/helpers/external-bundle-mock/baz/package.jsonpackages/webpack/externals-loading-webpack-plugin/test/helpers/external-bundle-mock/baz/sub1/index.jspackages/webpack/externals-loading-webpack-plugin/test/helpers/external-bundle-mock/baz/sub2/index.jspackages/webpack/externals-loading-webpack-plugin/test/helpers/external-bundle-mock/foo/index.jspackages/webpack/externals-loading-webpack-plugin/test/helpers/external-bundle-mock/foo/package.jsonpackages/webpack/externals-loading-webpack-plugin/test/helpers/setup-env.js
✅ Files skipped from review due to trivial changes (1)
- packages/webpack/externals-loading-webpack-plugin/test/helpers/external-bundle-mock/bar/package.json
🚧 Files skipped from review as they are similar to previous changes (9)
- packages/webpack/externals-loading-webpack-plugin/test/helpers/external-bundle-mock/baz/sub2/index.js
- .changeset/merge-fetchbundle-calls.md
- packages/webpack/externals-loading-webpack-plugin/test/helpers/external-bundle-mock/baz/index.js
- packages/webpack/externals-loading-webpack-plugin/test/cases/externals-loading/merge-fetchBundle-calls/rspack.config.js
- packages/webpack/externals-loading-webpack-plugin/test/cases/externals-loading/merge-fetchBundle-calls/test.config.cjs
- packages/webpack/externals-loading-webpack-plugin/test/helpers/external-bundle-mock/bar/index.js
- packages/webpack/externals-loading-webpack-plugin/test/cases.test.ts
- packages/webpack/externals-loading-webpack-plugin/test/cases/externals-loading/merge-fetchBundle-calls/index.js
- packages/webpack/externals-loading-webpack-plugin/test/helpers/external-bundle-mock/baz/package.json
Merging this PR will degrade performance by 5.58%
Performance Changes
Comparing Footnotes
|
Web Explorer#8055 Bundle Size — 384.5KiB (0%).82e7124(current) vs f730d3c main#8048(baseline) Bundle metrics
|
| Current #8055 |
Baseline #8048 |
|
|---|---|---|
155.59KiB |
155.59KiB |
|
35.1KiB |
35.1KiB |
|
0% |
0% |
|
8 |
8 |
|
8 |
8 |
|
239 |
239 |
|
16 |
16 |
|
2.98% |
2.98% |
|
4 |
4 |
|
0 |
0 |
Bundle size by type no changes
| Current #8055 |
Baseline #8048 |
|
|---|---|---|
253.55KiB |
253.55KiB |
|
95.85KiB |
95.85KiB |
|
35.1KiB |
35.1KiB |
Bundle analysis report Branch perf/merge-fetchbundle-calls Project dashboard
Generated by RelativeCI Documentation Report issue
This PR was opened by the [Changesets release](https://github.com/changesets/action) GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated. # Releases ## @lynx-js/devtool-mcp-server@0.5.0 ### Minor Changes - Use `@lynx-js/devtool-connector` instead of `@lynx-js/debug-router-connector`. ([#2284](#2284)) The new connector avoids using keep-alive connections, which makes the connection much more reliable. - **BREAKING CHANGE**: Remove the `./debug-router-connector` exports. ([#2284](#2284)) ## @lynx-js/web-elements@0.12.0 ### Minor Changes - feat: add `willchange` event to `x-viewpager-ng` ([#2305](#2305)) ### Patch Changes - fix: firefox `@supports(width:1rex)` ([#2288](#2288)) - fix: check computed overflow style in `getTheMostScrollableKid` to avoid treating `overflow: visible` elements as scroll containers ([#2309](#2309)) - fix: the inline-truncation should only work as a direct child of x-text ([#2287](#2287)) - fix: getVisibleCells cannot work in firefox due to contentvisibilityautostatechange not propagate list-item ([#2308](#2308)) - fix: foldview stuck issue ([#2304](#2304)) ## @lynx-js/gesture-runtime@2.1.3 ### Patch Changes - Optimize gesture callbacks and relationships to prevent unnecessary gesture registration and rerenders. ([#2277](#2277)) ## @lynx-js/react@0.116.5 ### Patch Changes - Improve React runtime hook profiling. ([#2235](#2235)) Enable Profiling recording first, then enter the target page so the trace includes full render/hydrate phases. - Record trace events for `useEffect` / `useLayoutEffect` hook entry, callback, and cleanup phases. - Log trace events for `useState` setter calls. - Wire `profileFlowId` support in debug profile utilities and attach flow IDs to related hook traces. - Instrument hydrate/background snapshot profiling around patch operations with richer args (e.g. snapshot id/type, dynamic part index, value type, and source when available). - Capture vnode source mapping in dev and use it in profiling args to improve trace attribution. - Expand debug test coverage for profile utilities, hook profiling behavior, vnode source mapping, and hydrate profiling branches. - refactor: call loadWorkletRuntime once in each module ([#2315](#2315)) ## @lynx-js/rspeedy@0.13.5 ### Patch Changes - feat: opt-in the web platform's new binary output format ([#2281](#2281)) Introduce a new flag to enable the new binary output format. Currently it's an internal-use-only flag that will be removed in the future; set the corresponding environment variable to 'true' to enable it. - Avoid generating `Rsbuild vundefined` in greeting message. ([#2275](#2275)) - Updated dependencies \[]: - @lynx-js/web-rsbuild-server-middleware@0.19.8 ## @lynx-js/lynx-bundle-rslib-config@0.2.2 ### Patch Changes - Support bundle and load css in external bundle ([#2143](#2143)) ## @lynx-js/external-bundle-rsbuild-plugin@0.0.3 ### Patch Changes - Updated dependencies \[[`c28b051`](c28b051), [`4cbf809`](4cbf809)]: - @lynx-js/externals-loading-webpack-plugin@0.0.4 ## @lynx-js/react-rsbuild-plugin@0.12.10 ### Patch Changes - Support bundle and load css in external bundle ([#2143](#2143)) - Updated dependencies \[[`59f2933`](59f2933), [`453e006`](453e006)]: - @lynx-js/template-webpack-plugin@0.10.5 - @lynx-js/css-extract-webpack-plugin@0.7.0 - @lynx-js/react-webpack-plugin@0.7.4 - @lynx-js/react-alias-rsbuild-plugin@0.12.10 - @lynx-js/use-sync-external-store@1.5.0 - @lynx-js/react-refresh-webpack-plugin@0.3.4 ## @lynx-js/web-core-wasm@0.0.5 ### Patch Changes - Updated dependencies \[[`4963907`](4963907), [`8fd936a`](8fd936a), [`0d41253`](0d41253), [`d32c4c6`](d32c4c6), [`7518b72`](7518b72), [`fca9d4a`](fca9d4a)]: - @lynx-js/web-elements@0.12.0 ## @lynx-js/externals-loading-webpack-plugin@0.0.4 ### Patch Changes - perf: optimize external bundle loading by merging multiple `fetchBundle` calls for the same URL into a single request. ([#2307](#2307)) - Support bundle and load css in external bundle ([#2143](#2143)) ## @lynx-js/template-webpack-plugin@0.10.5 ### Patch Changes - feat: allow `templateDebugUrl` to be customized via `output.publicPath` or the `beforeEncode` hook. ([#2274](#2274)) - feat: opt-in the web platform's new binary output format ([#2281](#2281)) Introduce a new flag to enable the new binary output format. Currently it's an internal-use-only flag that will be removed in the future; set the corresponding environment variable to 'true' to enable it. - Updated dependencies \[]: - @lynx-js/web-core-wasm@0.0.5 ## create-rspeedy@0.13.5 ## @lynx-js/react-alias-rsbuild-plugin@0.12.10 ## upgrade-rspeedy@0.13.5 Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Summary
This PR optimizes the loading of external bundles by merging multiple
fetchBundlecalls for the same URL into a single request.Previously, if multiple independent components or modules requested the same external bundle simultaneously, each would trigger a separate
fetchBundlecall. This optimization ensures that subsequent requests for an in-flight URL wait for the first request to complete and share the result.Summary by CodeRabbit
New Features
Tests
Chores
Checklist