Skip to content

fix(externals-loading-webpack-plugin): deduplicate loadScript calls for externals sharing the same section#2465

Merged
upupming merged 3 commits intomainfrom
fix/deduplicate-loadscript-calls
Apr 15, 2026
Merged

fix(externals-loading-webpack-plugin): deduplicate loadScript calls for externals sharing the same section#2465
upupming merged 3 commits intomainfrom
fix/deduplicate-loadscript-calls

Conversation

@upupming
Copy link
Copy Markdown
Collaborator

@upupming upupming commented Apr 14, 2026

Summary

When multiple externals had different libraryName values but pointed to the same bundle URL and sectionPath, createLoadExternalSync/createLoadExternalAsync was called once per external, causing lynx.loadScript to execute redundantly for the same section.

Before (example with multiple aliases sharing the same section):

global["PkgA"] = ... createLoadExternalSync(handler0, "common", 2) ...;
global["PkgB"] = ... createLoadExternalSync(handler0, "common", 2) ...; // loadScript called again!
global["PkgC"] = ... createLoadExternalSync(handler0, "common", 2) ...; // and again!

After:

global["PkgA"] = ... createLoadExternalSync(handler0, "common", 2) ...;
global["PkgB"] = global["PkgB"] === undefined ? global["PkgA"] : global["PkgB"]; // reuse — no extra loadScript
global["PkgC"] = global["PkgC"] === undefined ? global["PkgA"] : global["PkgC"]; // reuse — no extra loadScript

Fix: Added a sectionLoadTracker map (keyed by urlKey||sectionPath||async) in #genExternalsLoadingCode. The first external for each (bundle, section, async) triple generates the actual createLoadExternalSync/Async call; subsequent externals in the same group reuse the already-loaded result (still guarded by === undefined so host-injected globals are preserved). async is included in the key because sync and async externals resolve to different runtime shapes (plain value vs Promise) and must not be merged.

Checklist

  • Tests updated (or not required).
  • Documentation updated (or not required).
  • Changeset added, and when a BREAKING CHANGE occurs, it needs to be clearly marked (or not required).

Summary by CodeRabbit

  • Bug Fixes
    • Fixed unnecessary duplicate script loading operations when multiple externals resolve to the same bundle URL and section path. The plugin now loads scripts only once per unique bundle/location combination and reuses results for subsequent externals sharing that combination.

…or externals sharing the same section

When multiple externals had different libraryName values but pointed to the
same bundle URL and sectionPath, createLoadExternalSync/Async was invoked
once per external, causing lynx.loadScript to execute redundantly for the
same section. Add a sectionLoadTracker map keyed by (urlKey||sectionPath)
so only the first external in each group triggers the load; subsequent
externals in the group are assigned the already-loaded result directly.
@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Apr 14, 2026

🦋 Changeset detected

Latest commit: ad76375

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
@lynx-js/externals-loading-webpack-plugin Patch
@lynx-js/external-bundle-rsbuild-plugin Patch

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

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 14, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b4f9b2a8-b02c-4cbb-be75-dd89420b2242

📥 Commits

Reviewing files that changed from the base of the PR and between aa27ab5 and ad76375.

📒 Files selected for processing (2)
  • packages/webpack/externals-loading-webpack-plugin/test/cases/externals-loading/merge-loadscript-calls/index.js
  • packages/webpack/externals-loading-webpack-plugin/test/cases/externals-loading/merge-loadscript-calls/rspack.config.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/webpack/externals-loading-webpack-plugin/test/cases/externals-loading/merge-loadscript-calls/index.js

📝 Walkthrough

Walkthrough

Deduplicates externals load logic so externals resolving to the same (bundle URL, section path, async) key emit a single lynx.loadScript call; subsequent externals reuse the first generated mount variable via a sectionLoadTracker map.

Changes

Cohort / File(s) Summary
Changeset metadata
\.changeset/fix-duplicate-loadscript-calls.md
Added changeset documenting a patch to deduplicate loadScript calls per (bundle URL, section path, async).
Core implementation
packages/webpack/externals-loading-webpack-plugin/src/index.ts
Added sectionLoadTracker: Map and generation logic to record the first mountVar per (urlKey, sectionPath, async) and reuse it for subsequent externals instead of emitting duplicate createLoadExternal* calls.
Tests & configs
packages/webpack/externals-loading-webpack-plugin/test/cases/externals-loading/merge-loadscript-calls/index.js, packages/webpack/externals-loading-webpack-plugin/test/cases/externals-loading/merge-loadscript-calls/rspack.config.js, packages/webpack/externals-loading-webpack-plugin/test/cases/externals-loading/merge-loadscript-calls/test.config.cjs
Added a test case and rspack/test configs that create shared (bundle, section) scenarios; assertions verify only one load call per handler and that subsequent externals reuse the previously-loaded global/mount variable.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • colinaaa
  • luhc228

Poem

🐇
I hop where scripts once doubled, two by two,
Now one leap fetches all — a tidy view.
Mount assigned, others borrow my cheer,
One load, many friends, no echoes here.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix(externals-loading-webpack-plugin): deduplicate loadScript calls for externals sharing the same section' clearly and specifically describes the main behavioral fix: deduplicating loadScript calls for externals with the same bundle URL and section path.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/deduplicate-loadscript-calls

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 887397b6c5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/webpack/externals-loading-webpack-plugin/src/index.ts Outdated
Comment thread packages/webpack/externals-loading-webpack-plugin/src/index.ts Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/webpack/externals-loading-webpack-plugin/test/cases/externals-loading/merge-loadscript-calls/index.js (1)

7-42: Please add a mixed async/sync regression variant for shared (url, sectionPath).

This test is strong for sync dedup. A second case with shared pair but different async values would guard against cross-mode reuse regressions in generated externals.

🤖 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-loadscript-calls/index.js`
around lines 7 - 42, Add a new test variant that covers the mixed async/sync
case where two externals share the same (url, sectionPath) but differ in async
mode so generated calls are not reused across modes: duplicate the existing test
logic (using background and mainThread reads and the h0/h1 markers for
createLoadExternalSync) but create one handler that is synchronous and another
that is asynchronous (so the generator will emit both createLoadExternalSync and
createLoadExternal or the async equivalent), then assert that each
(bundle,section,mode) pair produces exactly one loader call (use split checks
like background.split(hX).length / mainThread.split(hX).length for both sync and
async variants or the appropriate marker strings), and also verify there is no
cross-mode assignment reuse (similar to pkgBAssignment check) so PkgB does not
incorrectly reuse PkgA across async vs sync paths.
🤖 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 487-495: The deduplication key built as sectionKey =
`${urlKey}||${layerOptions.sectionPath}` can incorrectly reuse a mount value
across async vs sync externals; update the key to include the external's
async/type mode (e.g., append the external type or a boolean like isAsync) so
that sectionLoadTracker distinguishes entries by (urlKey, sectionPath,
externalMode). Locate the block using sectionKey, sectionLoadTracker,
existingMountVar, and mountVar and change the key construction and any places
that set/get from sectionLoadTracker so async and sync externals are never
conflated.

---

Nitpick comments:
In
`@packages/webpack/externals-loading-webpack-plugin/test/cases/externals-loading/merge-loadscript-calls/index.js`:
- Around line 7-42: Add a new test variant that covers the mixed async/sync case
where two externals share the same (url, sectionPath) but differ in async mode
so generated calls are not reused across modes: duplicate the existing test
logic (using background and mainThread reads and the h0/h1 markers for
createLoadExternalSync) but create one handler that is synchronous and another
that is asynchronous (so the generator will emit both createLoadExternalSync and
createLoadExternal or the async equivalent), then assert that each
(bundle,section,mode) pair produces exactly one loader call (use split checks
like background.split(hX).length / mainThread.split(hX).length for both sync and
async variants or the appropriate marker strings), and also verify there is no
cross-mode assignment reuse (similar to pkgBAssignment check) so PkgB does not
incorrectly reuse PkgA across async vs sync paths.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 39accffa-b80e-40a7-bbd3-ae5d86979f3b

📥 Commits

Reviewing files that changed from the base of the PR and between d1c16c5 and 887397b.

📒 Files selected for processing (5)
  • .changeset/fix-duplicate-loadscript-calls.md
  • packages/webpack/externals-loading-webpack-plugin/src/index.ts
  • packages/webpack/externals-loading-webpack-plugin/test/cases/externals-loading/merge-loadscript-calls/index.js
  • packages/webpack/externals-loading-webpack-plugin/test/cases/externals-loading/merge-loadscript-calls/rspack.config.js
  • packages/webpack/externals-loading-webpack-plugin/test/cases/externals-loading/merge-loadscript-calls/test.config.cjs

Comment thread packages/webpack/externals-loading-webpack-plugin/src/index.ts
@codecov
Copy link
Copy Markdown

codecov bot commented Apr 14, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ All tests successful. No failed tests found.

📢 Thoughts on this report? Let us know!

…tion dedup

- Include `async` in the sectionKey so sync and async externals sharing the
  same section are not merged (they resolve to different runtime shapes:
  plain value vs Promise)
- Restore the `=== undefined` guard on the alias assignment so host-injected
  globals are not unconditionally overwritten
@codspeed-hq
Copy link
Copy Markdown

codspeed-hq bot commented Apr 14, 2026

Merging this PR will degrade performance by 10.31%

❌ 1 regressed benchmark
✅ 80 untouched benchmarks
⏩ 21 skipped benchmarks1

⚠️ Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Benchmark BASE HEAD Efficiency
transform 1000 view elements 42.7 ms 47.6 ms -10.31%

Comparing fix/deduplicate-loadscript-calls (ad76375) with main (d1c16c5)

Open in CodSpeed

Footnotes

  1. 21 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@relativeci
Copy link
Copy Markdown

relativeci bot commented Apr 14, 2026

React Example

#7310 Bundle Size — 223.58KiB (0%).

ad76375(current) vs d1c16c5 main#7300(baseline)

Bundle metrics  no changes
                 Current
#7310
     Baseline
#7300
No change  Initial JS 0B 0B
No change  Initial CSS 0B 0B
No change  Cache Invalidation 0% 0%
No change  Chunks 0 0
No change  Assets 4 4
No change  Modules 179 179
No change  Duplicate Modules 70 70
No change  Duplicate Code 45.76% 45.76%
No change  Packages 2 2
No change  Duplicate Packages 0 0
Bundle size by type  no changes
                 Current
#7310
     Baseline
#7300
No change  IMG 145.76KiB 145.76KiB
No change  Other 77.82KiB 77.82KiB

Bundle analysis reportBranch fix/deduplicate-loadscript-callsProject dashboard


Generated by RelativeCIDocumentationReport issue

@relativeci
Copy link
Copy Markdown

relativeci bot commented Apr 14, 2026

React External

#427 Bundle Size — 582.81KiB (0%).

ad76375(current) vs d1c16c5 main#418(baseline)

Bundle metrics  no changes
                 Current
#427
     Baseline
#418
No change  Initial JS 0B 0B
No change  Initial CSS 0B 0B
No change  Cache Invalidation 0% 0%
No change  Chunks 0 0
No change  Assets 3 3
No change  Modules 17 17
No change  Duplicate Modules 5 5
No change  Duplicate Code 8.59% 8.59%
No change  Packages 0 0
No change  Duplicate Packages 0 0
Bundle size by type  no changes
                 Current
#427
     Baseline
#418
No change  Other 582.81KiB 582.81KiB

Bundle analysis reportBranch fix/deduplicate-loadscript-callsProject dashboard


Generated by RelativeCIDocumentationReport issue

@relativeci
Copy link
Copy Markdown

relativeci bot commented Apr 14, 2026

Web Explorer

#8884 Bundle Size — 749.3KiB (0%).

ad76375(current) vs d1c16c5 main#8874(baseline)

Bundle metrics  Change 2 changes
                 Current
#8884
     Baseline
#8874
No change  Initial JS 44.45KiB 44.45KiB
No change  Initial CSS 2.16KiB 2.16KiB
No change  Cache Invalidation 0% 0%
No change  Chunks 8 8
No change  Assets 10 10
Change  Modules 148(-0.67%) 149
No change  Duplicate Modules 11 11
Change  Duplicate Code 35.02%(+0.03%) 35.01%
No change  Packages 3 3
No change  Duplicate Packages 0 0
Bundle size by type  no changes
                 Current
#8884
     Baseline
#8874
No change  Other 401.63KiB 401.63KiB
No change  JS 345.51KiB 345.51KiB
No change  CSS 2.16KiB 2.16KiB

Bundle analysis reportBranch fix/deduplicate-loadscript-callsProject dashboard


Generated by RelativeCIDocumentationReport issue

@relativeci
Copy link
Copy Markdown

relativeci bot commented Apr 14, 2026

React MTF Example

#443 Bundle Size — 192.85KiB (0%).

ad76375(current) vs d1c16c5 main#433(baseline)

Bundle metrics  no changes
                 Current
#443
     Baseline
#433
No change  Initial JS 0B 0B
No change  Initial CSS 0B 0B
No change  Cache Invalidation 0% 0%
No change  Chunks 0 0
No change  Assets 3 3
No change  Modules 173 173
No change  Duplicate Modules 67 67
No change  Duplicate Code 45.36% 45.36%
No change  Packages 2 2
No change  Duplicate Packages 0 0
Bundle size by type  no changes
                 Current
#443
     Baseline
#433
No change  IMG 111.23KiB 111.23KiB
No change  Other 81.61KiB 81.61KiB

Bundle analysis reportBranch fix/deduplicate-loadscript-callsProject dashboard


Generated by RelativeCIDocumentationReport issue

…ync isolation

Extend merge-loadscript-calls with async externals:
- pkg-d and pkg-e: two async externals sharing the same (bundle, section) —
  the async group should merge, calling createLoadExternalAsync only once.
- pkg-f: a sync external sharing the SAME (bundle, section) as pkg-d/pkg-e —
  must NOT merge with the async group because the runtime shape differs
  (plain value vs Promise).
@upupming upupming merged commit 3262ca8 into main Apr 15, 2026
49 of 50 checks passed
@upupming upupming deleted the fix/deduplicate-loadscript-calls branch April 15, 2026 03:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants