fix: Prevent partial bundle loading and double fetching in `TemplateM…#2386
fix: Prevent partial bundle loading and double fetching in `TemplateM…#2386PupilTong merged 2 commits intolynx-family:mainfrom
Conversation
…anager.fetchBundle` by handling concurrent requests and renaming template-related methods to bundle.
🦋 Changeset detectedLatest commit: 9dca8a0 The changes in this PR will be included in the next version bump. This PR includes changesets to release 9 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 |
📝 WalkthroughWalkthroughRefactors TemplateManager to split storage into completed ( Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 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 |
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
Merging this PR will degrade performance by 21.15%
Performance Changes
Comparing Footnotes
|
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
packages/web-platform/web-core/tests/template-manager.spec.ts (1)
398-455: Cover the mixedoverrideConfigrace in this regression test.The bug report path is
fetchBundle(url)racing withfetchBundle(url, ..., { enableCSSSelector: ... })fromqueryComponent(). This test only covers two identical requests, so it won't catch URL-only dedupe dropping the second caller's override. I'd extend it with one overridden caller and assert both the override and single-fetch behavior.✅ Suggested test extension
await Promise.all([ templateManager.fetchBundle( 'http://example.com/template_concurrent', Promise.resolve(instance1 as unknown as LynxViewInstance), false, false, ), templateManager.fetchBundle( 'http://example.com/template_concurrent', Promise.resolve(instance2 as unknown as LynxViewInstance), false, false, + { enableCSSSelector: 'true' } as any, ), ]); @@ expect(decodedCustomSections).toEqual(sampleTasm.customSections); expect(instance1.onPageConfigReady).toHaveBeenCalled(); - expect(instance2.onPageConfigReady).toHaveBeenCalled(); + expect(instance2.onPageConfigReady).toHaveBeenCalledWith( + expect.objectContaining({ enableCSSSelector: 'true' }), + ); + expect(globalThis.fetch).toHaveBeenCalledTimes(1); });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/web-platform/web-core/tests/template-manager.spec.ts` around lines 398 - 455, Extend the concurrent-fetch test to also exercise the mixed-override race: call templateManager.fetchBundle twice concurrently where one call passes an overrideConfig (e.g., { enableCSSSelector: true } or the real override shape used by queryComponent) and the other call uses the default override (no overrideConfig), then assert both callers complete and that (1) the stored bundle from templateManager.getBundle(url) remains correct for the fetched bundle, and (2) the caller that supplied overrideConfig received the overridden behavior (verify via the instance's onPageConfigReady / pageConfig or other public observable produced by fetchBundle) while the other caller still receives normal behavior; reference templateManager.fetchBundle, getBundle, queryComponent, overrideConfig and the instances' onPageConfigReady to locate and implement the assertions.
🤖 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/web-platform/web-core/ts/client/mainthread/TemplateManager.ts`:
- Around line 65-75: The dedupe branch is collapsing concurrent requests by URL
only, which loses per-call overrideConfig from queryComponent; update the logic
around `#loadingPromises` (the dedupe path that returns
this.#loadingPromises.get(url)) so it preserves per-caller overrideConfig:
either include overrideConfig in the dedupe key (e.g., use a composite key of
url + serialized overrideConfig when storing/checking `#loadingPromises` for
fetchBundle) or, if overrideConfig exists on the waiting caller, skip the
URL-only dedupe and start a new fetch; then ensure the waiting caller uses its
overrideConfig when calling lynxViewInstance.onPageConfigReady and related
methods (references: `#loadingPromises`, fetchBundle, queryComponent,
lynxViewInstancePromise, onPageConfigReady).
- Around line 198-213: When handling the 'done' case, ensure the in-flight entry
is settled and removed even if lynxViewInstancePromise rejects: move the calls
to this.#resolvePromise(url) and this.#loadingPromises.delete(url) out of the
.then handler and into a .finally (or mirror them in both .then and .catch), and
in the .catch propagate or log the error so callers don't hang; keep the
existing backgroundThread markTiming calls inside the successful path
(lynxViewInstancePromise.then) and ensure bundle promotion (this.#bundles/set
and this.#loadingBundles.delete) remains unchanged.
---
Nitpick comments:
In `@packages/web-platform/web-core/tests/template-manager.spec.ts`:
- Around line 398-455: Extend the concurrent-fetch test to also exercise the
mixed-override race: call templateManager.fetchBundle twice concurrently where
one call passes an overrideConfig (e.g., { enableCSSSelector: true } or the real
override shape used by queryComponent) and the other call uses the default
override (no overrideConfig), then assert both callers complete and that (1) the
stored bundle from templateManager.getBundle(url) remains correct for the
fetched bundle, and (2) the caller that supplied overrideConfig received the
overridden behavior (verify via the instance's onPageConfigReady / pageConfig or
other public observable produced by fetchBundle) while the other caller still
receives normal behavior; reference templateManager.fetchBundle, getBundle,
queryComponent, overrideConfig and the instances' onPageConfigReady to locate
and implement the assertions.
🪄 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: e032d625-beda-403b-ad39-d5fccb8df10a
📒 Files selected for processing (5)
.changeset/fix-bundle-race-condition.mdpackages/web-platform/web-core/tests/template-manager.spec.tspackages/web-platform/web-core/ts/client/mainthread/LynxViewInstance.tspackages/web-platform/web-core/ts/client/mainthread/TemplateManager.tspackages/web-platform/web-core/ts/client/mainthread/createMainThreadGlobalAPIs.ts
Web Explorer#8469 Bundle Size — 728.55KiB (+0.09%).9dca8a0(current) vs a32bc28 main#8457(baseline) Bundle metrics
Bundle size by type
Bundle analysis report Branch PupilTong:p/hw/fetchBundle-race Project dashboard Generated by RelativeCI Documentation Report issue |
React MTF Example#25 Bundle Size — 207.47KiB (0%).9dca8a0(current) vs a32bc28 main#13(baseline) Bundle metrics
|
| Current #25 |
Baseline #13 |
|
|---|---|---|
0B |
0B |
|
0B |
0B |
|
0% |
0% |
|
0 |
0 |
|
3 |
3 |
|
168 |
168 |
|
66 |
66 |
|
46.13% |
46.13% |
|
2 |
2 |
|
0 |
0 |
Bundle size by type no changes
| Current #25 |
Baseline #13 |
|
|---|---|---|
111.23KiB |
111.23KiB |
|
96.24KiB |
96.24KiB |
Bundle analysis report Branch PupilTong:p/hw/fetchBundle-race Project dashboard
Generated by RelativeCI Documentation Report issue
React Example#6891 Bundle Size — 237.89KiB (0%).9dca8a0(current) vs a32bc28 main#6879(baseline) Bundle metrics
|
| Current #6891 |
Baseline #6879 |
|
|---|---|---|
0B |
0B |
|
0B |
0B |
|
0% |
0% |
|
0 |
0 |
|
4 |
4 |
|
174 |
174 |
|
69 |
69 |
|
46.44% |
46.44% |
|
2 |
2 |
|
0 |
0 |
Bundle size by type no changes
| Current #6891 |
Baseline #6879 |
|
|---|---|---|
145.76KiB |
145.76KiB |
|
92.13KiB |
92.13KiB |
Bundle analysis report Branch PupilTong:p/hw/fetchBundle-race Project dashboard
Generated by RelativeCI Documentation Report issue
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (1)
packages/web-platform/web-core/ts/client/mainthread/TemplateManager.ts (1)
54-85:⚠️ Potential issue | 🟠 Major
overrideConfigis still missing from the cache/dedupe key.This state is still keyed by bare
url, so a load started with one override can be replayed for concurrent callers with a different override, and the completed#bundlesentry can later serve that overridden config back to a no-override caller on the fast path. Please key shared entries by(url, overrideConfig)or avoid sharing/caching overridden loads.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/web-platform/web-core/ts/client/mainthread/TemplateManager.ts` around lines 54 - 85, The cache/dedupe keys for bundle loading are currently only using url, so entries in this.#bundles and this.#loadingPromises can be reused across different overrideConfig values causing incorrect config replay; change the keying to include overrideConfig (for example, combine url and a stable serialization of overrideConfig) wherever entries are set and looked up (references: this.#bundles, this.#loadingPromises, createBundle, and the call site of this.#load) or alternatively avoid caching when overrideConfig is present by branching to always call this.#load for overrideConfig !== undefined; ensure lookups, set calls, and the fast path that reads bundle?.config all use the same (url, overrideConfig) composite key so no cross-contamination occurs.
🤖 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/web-platform/web-core/ts/client/mainthread/TemplateManager.ts`:
- Around line 275-319: createBundle currently destroys the existing entry in
`#bundles` immediately, exposing a missing/partial bundle during reloads; change
the flow so createBundle only creates an empty in-flight record in
`#loadingBundles` (do not revoke or delete from `#bundles` there), perform
fetch/build into that loading entry, and on the successful completion event
('done') swap the completed bundle into `#bundles` while then revoking/cleaning
the previous `#bundles` entry; update getBundle usage so it falls back to `#bundles`
as before; also modify `#removeBundle` to only clear the in-flight state in
`#loadingBundles` (do not call createBundle to perform the clear), and ensure
cleanup/revocation logic references bundle.lepusCode, bundle.backgroundCode, and
bundle.styleSheet when swapping/removing the old bundle.
- Around line 198-206: The 'done' branch in TemplateManager (within
`#handleSection` and similar branches) promotes the bundle from `#loadingBundles` to
`#bundles` and deletes the `#loadingBundles` entry immediately, which lets async
section writers resume later and silently lose updates; change the logic so you
either (a) wait until the per-URL section queue/section writers have drained
before deleting the `#loadingBundles` entry (i.e., keep the loading entry until
all pending section promises for that URL have completed), or (b) update the
section-writer paths to detect a promoted bundle and apply their
config/style/code writes to the already-promoted bundle (fall back to `#bundles`
when `#loadingBundles.get`(url) is missing). Apply the same fix to other similar
blocks that call `#cleanup`, move entries between `#loadingBundles` and `#bundles`,
call `#resolvePromise`, and delete `#loadingPromises` (the other occurrences noted
in the review).
---
Duplicate comments:
In `@packages/web-platform/web-core/ts/client/mainthread/TemplateManager.ts`:
- Around line 54-85: The cache/dedupe keys for bundle loading are currently only
using url, so entries in this.#bundles and this.#loadingPromises can be reused
across different overrideConfig values causing incorrect config replay; change
the keying to include overrideConfig (for example, combine url and a stable
serialization of overrideConfig) wherever entries are set and looked up
(references: this.#bundles, this.#loadingPromises, createBundle, and the call
site of this.#load) or alternatively avoid caching when overrideConfig is
present by branching to always call this.#load for overrideConfig !== undefined;
ensure lookups, set calls, and the fast path that reads bundle?.config all use
the same (url, overrideConfig) composite key so no cross-contamination occurs.
🪄 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: d5d139b4-3a6b-4924-9e8f-08eee14b0ac5
📒 Files selected for processing (1)
packages/web-platform/web-core/ts/client/mainthread/TemplateManager.ts
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/rspeedy@0.14.0 ### Minor Changes - feat: add `Minify.mainThreadOptions` and `Minify.backgroundOptions` for thread-specific minifier. ([#2336](#2336)) ### Patch Changes - Bump Rsbuild v1.7.4 with Rspack v1.7.10. ([#2384](#2384)) - Updated dependencies \[]: - @lynx-js/web-rsbuild-server-middleware@0.20.0 ## @lynx-js/lynx-bundle-rslib-config@0.3.0 ### Minor Changes - **BREAKING CHANGE**: ([#2370](#2370)) Simplify the API for external bundle builds by `externalsPresets` and `externalsPresetDefinitions`. ### Patch Changes - Preserve the default external-bundle `output.minify.jsOptions` when users set `output.minify: true` in `defineExternalBundleRslibConfig`, so required minifier options are not lost. ([#2390](#2390)) ## @lynx-js/external-bundle-rsbuild-plugin@0.1.0 ### Minor Changes - **BREAKING CHANGE**: ([#2370](#2370)) Simplify the API for external bundle builds by `externalsPresets` and `externalsPresetDefinitions`. ### Patch Changes - Updated dependencies \[[`7b7a0c6`](7b7a0c6)]: - @lynx-js/externals-loading-webpack-plugin@0.1.0 ## @lynx-js/react-rsbuild-plugin@0.14.0 ### Minor Changes - feat: support `optimizeBundleSize` option to remove unused code for main-thread and background. ([#2336](#2336)) - If `optimizeBundleSize` is `true` or `optimizeBundleSize.background` is `true`, `lynx.registerDataProcessors` calls will be marked as pure for the background thread output. - If `optimizeBundleSize` is `true` or `optimizeBundleSize.mainThread` is `true`, `NativeModules.call` and `lynx.getJSModule` calls will be marked as pure for the main-thread output. ### Patch Changes - refactor: remove `modifyWebpackChain` since Rsbuild 2.0 dropped webpack support ([#2397](#2397)) - Updated dependencies \[[`9193711`](9193711)]: - @lynx-js/template-webpack-plugin@0.10.7 - @lynx-js/css-extract-webpack-plugin@0.7.0 - @lynx-js/react-webpack-plugin@0.8.0 - @lynx-js/react-alias-rsbuild-plugin@0.14.0 - @lynx-js/use-sync-external-store@1.5.0 - @lynx-js/react-refresh-webpack-plugin@0.3.5 ## @lynx-js/web-core@0.20.0 ### Minor Changes - **This is a breaking change** ([#2322](#2322)) ## Architectural Upgrade: `web-core-wasm` replaces `web-core` This release marks a major architectural upgrade for the web platform. The experimental, WASM-powered engine formerly known as `web-core-wasm` has been fully stabilized and merged into the main branch, completely replacing the previous pure JS/TS based `web-core` implementation. This consolidation massively improves execution performance and aligns the API boundaries of the Web platform directly with other native Lynx implementations. ### 🎉 Added Features - **Core API Enhancements**: Successfully exposed and supported `__QuerySelector` and `__InvokeUIMethod` methods. - **Security & CSP Compliance**: Added a `nonce` attribute to the iframe's `srcdoc` script execution, strengthening Content Security Policy (CSP) compliance. - **`<lynx-view>` Parameter Enhancements**: - Added the `browser-config` attribute and property to `<lynx-view>`. Development environments can now supply a `BrowserConfig` object (e.g., configuring `pixelRatio`, `pixelWidth`, `pixelHeight`) allowing the `systemInfo` payload to be dynamically configured at the instance level. ### 🔄 Changed Features - **Legacy JSON Backwards Compatibility**: Delivered comprehensive fixes and optimizations to deeply support legacy JSON output templates: - Added support for lazy loading execution mode (`lazy usage`). - Implemented the correct decoding and handling of `@keyframe` animation rules. - Rectified rule scoping matching including scoped CSS, root selectors, and type selectors. - **Ecosystem Migration**: Updated testing and ecosystem applications (such as `web-explorer` and `shell-project`) to migrate away from obsolete fragmented dependencies. The new WASM architecture seamlessly integrates Element APIs and CSS directly inside the core client module, requiring a much simpler initialization footprint. **Before (Legacy `web-core` + `web-elements`):** ```typescript // Required multiple imports to assemble the environment import "@lynx-js/web-core/client"; import type { LynxViewElement as LynxView } from "@lynx-js/web-core"; // Had to manually import separate elements and their CSS import "@lynx-js/web-elements/index.css"; import "@lynx-js/web-elements/all"; const lynxView = document.createElement("lynx-view") as LynxView; // ... ``` **After (New `web-core` unified architecture):** ```typescript // The new engine natively registers Web Components and injects fundamental CSS import "@lynx-js/web-core/client"; import type { LynxViewElement as LynxView } from "@lynx-js/web-core/client"; const lynxView = document.createElement("lynx-view") as LynxView; // ... ``` _(Applications can now drop `@lynx-js/web-elements` entirely from their `package.json` dependencies)._ - **Dependency & Boot Sequence Improvements**: Re-architected module loading pathways. Promoted `wasm-feature-detect` directly to a core dependency, and hardened the web worker count initialization assertions. - **Initialization Optimizations**: Converted `SERVER_IN_SHADOW_CSS` initialization bounds to use compilation-time constant expressions for better optimization. ### 🗑️ Deleted Features & Structural Deprecations - **`<lynx-view>` Parameter Removals**: - Removed the `thread-strategy` property and attribute. Historically, this permitted consumers to toggle between `'multi-thread'` and `'all-on-ui'` modes depending on how they wanted the background logic to be executed. The WASM-driven architecture enforces a consolidated concurrency model, deprecating this `<lynx-view>` attribute entirely. - Removed the `overrideLynxTagToHTMLTagMap` property/attribute. HTML tag overriding mechanism has been deprecated in the new engine. - Removed the `customTemplateLoader` property handler from `<lynx-view>`. - Removed the `inject-head-links` property and attribute (`injectHeadLinks`), which previously was used to automatically inject `<link rel="stylesheet">` tags from the document head into the `lynx-view` shadow root. - **Fragmented Packages Removal**: The new cohesive WASM architecture native to `@lynx-js/web-core` handles cross-thread communication, worker boundaries, and rendering loops uniformly. Consequently, multiple obsolete packages have been completely removed from the workspace: - `@lynx-js/web-mainthread-apis` - `@lynx-js/web-worker-runtime` - `@lynx-js/web-core-server` - `@lynx-js/web-core-wasm-e2e` (transitioned into standard test suites) - Added support for `rpx` unit ([#2377](#2377)) **This is a breaking change** The following Styles has been added to `web-core` ```css lynx-view { width: 100%; container-name: lynx-view; container-type: inline-size; --rpx-unit: 1cqw; } ``` Check MDN for the details about these styles: - <https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/container-name> - <https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/container-type> - <https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Containment/Container_queries> ### how it works? For the following code ```html <view style="height:1rpx"></view> ``` it will be transformed to ```html <view style="height:calc(1 * var(--rpx-unit))"></view> ``` Therefore you could use any `<length>` value to replace the unit, for example: ```html <lynx-view style="--rpx-unit:1px"></lynx-view> ``` By default, the --rpx-unit value is `1cqw` - Added support for transform `vw` and `vh` unit ([#2377](#2377)) Add `transform-vw` and `transform-vh` attributes and properties on `<lynx-view>`. For the following code ```html <view style="height:1vw"></view> ``` If the `transform-vw` is enabled `<lynx-view transform-vw="true">`, it will be transformed to ```html <view style="height:calc(1 * var(--vw-unit))"></view> ``` Therefore you could use any `<length>` value to replace the unit, for example: ```html <lynx-view style="--vw-unit:1px"></lynx-view> ``` ### Patch Changes - feat(web-core): add `is_bubble` parameter to `common_event_handler` to properly handle non-bubbling events like `window.Event('click', { bubbles: false })`. ([#2399](#2399)) - chore: update readme ([#2380](#2380)) - fix: the output format should be module ([#2388](#2388)) - opt: use opt-level 3 to compile wasm ([#2371](#2371)) - fix(web-core): avoid partial bundle loading and double fetching when fetchBundle is called concurrently for the same url. ([#2386](#2386)) - fix(web-core): fallback to the original export chunk when `processEvalResult` is absent during `queryComponent` execution ([#2399](#2399)) - fix: tokenizing inline style values correctly to support rpx and ppx unit conversion ([#2381](#2381)) This fixes an issue where the `transform_inline_style_key_value_vec` API bypassed the CSS tokenizer, preventing dimension units like `rpx` or `ppx` from being successfully transformed into `calc` strings when specified via inline styles. - feat: add mts lynx.querySelectorAll API ([#2382](#2382)) - fix: mts in lazy component ([#2375](#2375)) - fix: enableJSDataProcessor not work ([#2372](#2372)) - feat: add `ppx` unit support for CSS, transforming to `calc(... * var(--ppx-unit))` directly. ([#2381](#2381)) - Updated dependencies \[]: - @lynx-js/web-worker-rpc@0.20.0 ## @lynx-js/externals-loading-webpack-plugin@0.1.0 ### Minor Changes - **BREAKING CHANGE**: ([#2370](#2370)) Simplify the API for external bundle builds by `externalsPresets` and `externalsPresetDefinitions`. ## @lynx-js/devtool-connector@0.1.1 ### Patch Changes - fix: align GlobalKeys with Android DevToolSettings keys and filter global switch responses ([#2392](#2392)) ## @lynx-js/devtool-mcp-server@0.5.1 ### Patch Changes - Updated dependencies \[[`95fff27`](95fff27)]: - @lynx-js/devtool-connector@0.1.1 ## @lynx-js/react@0.117.1 ### Patch Changes - Update preact version to simplify `setProperty` implementation ([#2367](#2367)) ## @lynx-js/react-umd@0.117.1 ### Patch Changes - Add a new `entry` export to `@lynx-js/react-umd` for reuse by wrapper libraries of `@lynx-js/react`. ([#2370](#2370)) ## create-rspeedy@0.14.0 ### Patch Changes - Add optional Lynx DevTool skill. ([#2421](#2421)) - move Vitest integration to create-rstack extraTools and merge the Vitest templates into a single incremental overlay ([#2408](#2408)) ## @lynx-js/kitten-lynx-test-infra@0.1.2 ### Patch Changes - Updated dependencies \[[`95fff27`](95fff27)]: - @lynx-js/devtool-connector@0.1.1 ## @lynx-js/template-webpack-plugin@0.10.7 ### Patch Changes - use path.posix.format instead of path.format to ensure consistent path separators across platforms ([#2359](#2359)) - Updated dependencies \[[`75960cd`](75960cd), [`518c310`](518c310), [`863469e`](863469e), [`dc18c5c`](dc18c5c), [`7d242f3`](7d242f3), [`62bebcf`](62bebcf), [`75960cd`](75960cd), [`182f568`](182f568), [`1aa051d`](1aa051d), [`6b46f7e`](6b46f7e), [`fcda36a`](fcda36a), [`182f568`](182f568), [`138f727`](138f727), [`138f727`](138f727)]: - @lynx-js/web-core@0.20.0 ## @lynx-js/react-alias-rsbuild-plugin@0.14.0 ## upgrade-rspeedy@0.14.0 ## @lynx-js/web-rsbuild-server-middleware@0.20.0 ## @lynx-js/web-worker-rpc@0.20.0 Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
…anager.fetchBundle` by handling concurrent requests and renaming template-related methods to bundle.
Summary by CodeRabbit
Checklist