Skip to content

fix: snapshot not found error when dev with external bundle#2316

Merged
upupming merged 11 commits intomainfrom
fix/try-fix-externals-hmr
Mar 11, 2026
Merged

fix: snapshot not found error when dev with external bundle#2316
upupming merged 11 commits intomainfrom
fix/try-fix-externals-hmr

Conversation

@upupming
Copy link
Copy Markdown
Collaborator

@upupming upupming commented Mar 6, 2026

Summary

This PR addresses the "snapshot not found" error that occurs during development with external bundles.

Root Cause:
The underlying issue occurs because __webpack_modules__ is not inherently shared between the host application and external bundles. However, certain development features (like React Refresh and specific module loading logic) rely on cross-boundary module access to function properly, leading to "snapshot not found" when the module cannot be resolved.

How we fix it:
To resolve this, we explicitly expose development modules via globalThis[Symbol.for('__LYNX_WEBPACK_MODULES__')] so that the host and externals can seamlessly share module contexts. Crucially, we ensure this global sharing is strictly limited to development mode to prevent memory leaks, security issues, or pollution in production.

Key Changes:

  1. Explicit conditional injection for __LYNX_WEBPACK_MODULES__:
    • Refactored intercept.cjs and the core Webpack/Rspack React Refresh plugins to conditionally expose __webpack_modules__ ONLY when the compiler mode is development.
    • In production, this code is completely stripped (evaluated as if (false)), preventing production code pollution.
  2. LoadingConsumerModulesRuntimeModule Management:
    • Renamed MyCustomRuntimeModule to LoadingConsumerModulesRuntimeModule.
    • Refined MainThreadRuntimeWrapperWebpackPlugin.ts so that this runtime module is solely injected in development mode, correctly registering external consumer modules for HMR/refresh purposes without bloating the production bundle.
  3. Rigorous Test Coverage:
    • Added robust tests in external-bundle.test.ts to strongly assert that macros like __DEV__ handle development and production modes correctly, ensuring distinct artifacts.
    • Boosted coverage in intercept.test.ts to verify that the globalThis assignment is effectively removed in production builds across both Rspack and Webpack compiler mocks.

Summary by CodeRabbit

  • Bug Fixes

    • Fixed snapshot not found error when developing with external bundles.
  • Improvements

    • Enhanced development mode handling for optimized builds.
    • Added compatibility check for CSS stylesheet loading (LynxSDK >= 3.7 required).
  • New Features

    • Added development-specific build script variants for component libraries and bundles.

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).

@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Mar 6, 2026

🦋 Changeset detected

Latest commit: 7c2376b

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

This PR includes changesets to release 6 packages
Name Type
@lynx-js/externals-loading-webpack-plugin Patch
@lynx-js/react-refresh-webpack-plugin Patch
@lynx-js/lynx-bundle-rslib-config Patch
@lynx-js/external-bundle-rsbuild-plugin Patch
@lynx-js/react-rsbuild-plugin Patch
@lynx-js/react-alias-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 Mar 6, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR fixes snapshot not found errors in development mode with external bundles by implementing conditional minification based on NODE_ENV, adding runtime module injection for consumer module loading, and introducing guards for CSS stylesheet loading in external bundle contexts.

Changes

Cohort / File(s) Summary
Changeset Documentation
.changeset/six-readers-cover.md
Introduces changeset documenting patch version bumps for three packages with fix for snapshot errors in dev mode with external bundles.
Example Build Configuration
examples/react-externals/package.json, examples/react-externals/rslib-*.config.ts
Adds development build scripts with cross-env NODE_ENV support and removes minify configuration overrides from example rslib configs.
Minification Configuration
packages/rspeedy/lynx-bundle-rslib-config/src/externalBundleRslibConfig.ts
Makes minify setting conditional on NODE_ENV, disabling minification in development mode and retaining object config otherwise.
Runtime Module Injection
packages/rspeedy/lynx-bundle-rslib-config/src/webpack/MainThreadRuntimeWrapperWebpackPlugin.ts
Adds LoadingConsumerModulesRuntimeModule for development mode to inject loader that resolves modules from global Lynx modules registry at runtime.
CSS Loading Guards
packages/webpack/externals-loading-webpack-plugin/src/index.ts
Adds runtime checks to verify __LoadStyleSheet availability before attempting CSS adoption in both async and sync external loading paths.
Webpack Modules Exposure
packages/webpack/react-refresh-webpack-plugin/runtime/intercept.cjs
Exposes internal webpack modules map to globalThis for development mode consumption via LYNX_WEBPACK_MODULES symbol.
Test Coverage
packages/rspeedy/lynx-bundle-rslib-config/test/external-bundle.test.ts, packages/webpack/react-refresh-webpack-plugin/test/intercept.test.ts
Adds comprehensive tests for runtime module injection, mode-based configuration differences, and webpack modules interception in development environments.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~28 minutes

Possibly related PRs

Suggested reviewers

  • colinaaa
  • luhc228

Poem

🐰 Hop, hop, the snapshots now align,
Dev mode bundles load so fine,
Guards protect our CSS streams,
Runtime modules fulfill our dreams! 🎉

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly and accurately describes the primary fix: resolving the 'snapshot not found error' when developing with external bundles, which directly matches the core objective and changeset.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/try-fix-externals-hmr

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.

@codecov
Copy link
Copy Markdown

codecov bot commented Mar 6, 2026

Codecov Report

❌ Patch coverage is 94.87179% with 2 lines in your changes missing coverage. Please review.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
...react-refresh-webpack-plugin/runtime/intercept.cjs 0.00% 2 Missing ⚠️

📢 Thoughts on this report? Let us know!

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq bot commented Mar 6, 2026

Merging this PR will improve performance by 7.78%

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

⚡ 2 improved benchmarks
✅ 70 untouched benchmarks
⏩ 3 skipped benchmarks1

Performance Changes

Benchmark BASE HEAD Efficiency
basic-performance-scroll-view-100 9.2 ms 8.7 ms +6.22%
basic-performance-nest-level-100 7.4 ms 6.9 ms +7.78%

Comparing fix/try-fix-externals-hmr (7c2376b) with main (7fe71ea)

Open in CodSpeed

Footnotes

  1. 3 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 Mar 6, 2026

Web Explorer

#8116 Bundle Size — 384.5KiB (0%).

7c2376b(current) vs 7fe71ea main#8113(baseline)

Bundle metrics  no changes
                 Current
#8116
     Baseline
#8113
No change  Initial JS 155.59KiB 155.59KiB
No change  Initial CSS 35.1KiB 35.1KiB
No change  Cache Invalidation 0% 0%
No change  Chunks 8 8
No change  Assets 8 8
No change  Modules 237 237
No change  Duplicate Modules 16 16
No change  Duplicate Code 2.98% 2.98%
No change  Packages 4 4
No change  Duplicate Packages 0 0
Bundle size by type  no changes
                 Current
#8116
     Baseline
#8113
No change  JS 253.55KiB 253.55KiB
No change  Other 95.85KiB 95.85KiB
No change  CSS 35.1KiB 35.1KiB

Bundle analysis reportBranch fix/try-fix-externals-hmrProject dashboard


Generated by RelativeCIDocumentationReport issue

@upupming upupming force-pushed the fix/try-fix-externals-hmr branch from 4086049 to 1e33f85 Compare March 8, 2026 03:44
@upupming upupming force-pushed the fix/try-fix-externals-hmr branch from 1e33f85 to b5ec4b0 Compare March 8, 2026 04:03
@upupming upupming changed the title fix: externals hmr fix: snapshot not found error when dev with external bundle Mar 8, 2026
@upupming upupming force-pushed the fix/try-fix-externals-hmr branch from 21e426d to 0cb1d71 Compare March 8, 2026 04:12
@upupming upupming marked this pull request as ready for review March 8, 2026 07:08
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: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/webpack/react-refresh-webpack-plugin/src/ReactRefreshRspackPlugin.ts (1)

119-125: ⚠️ Potential issue | 🟠 Major

Use mode as the source of truth once, then pass it through.

Lines 120-121 and 152-153 use NODE_ENV || mode, so an explicit production compile can still enable refresh when the shell exports NODE_ENV=development. Recomputing the flag inside #appendInterceptRuntimeModule() also makes the generated code depend on ambient state instead of the decision made in apply(). Derive isDev once from compiler.options.mode (with an env fallback only when mode is unset, if you still need it) and pass that boolean into the helper.

Suggested fix
   apply(compiler: Compiler): void {
-    const isDev = process.env['NODE_ENV'] === 'development'
-      || compiler.options.mode === 'development';
+    const isDev = compiler.options.mode === undefined
+      ? process.env['NODE_ENV'] === 'development'
+      : compiler.options.mode === 'development';
@@
-          this.#appendInterceptRuntimeModule(compiler, runtimeModule);
+          this.#appendInterceptRuntimeModule(runtimeModule, isDev);
@@
-  `#appendInterceptRuntimeModule`(
-    compiler: Compiler,
-    runtimeModule: RuntimeModule,
-  ) {
-    const isDev = process.env['NODE_ENV'] === 'development'
-      || compiler.options.mode === 'development';
+  `#appendInterceptRuntimeModule`(
+    runtimeModule: RuntimeModule,
+    isDev: boolean,
+  ) {

Also applies to: 139-145, 148-165

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/webpack/react-refresh-webpack-plugin/src/ReactRefreshRspackPlugin.ts`
around lines 119 - 125, Compute the development flag once from
compiler.options.mode (use process.env.NODE_ENV only as a fallback when mode is
undefined), assign it to isDev inside apply(), and pass that boolean into
`#appendInterceptRuntimeModule` instead of re-evaluating NODE_ENV there; update
any other spots that recompute the flag (the blocks around the previous lines
139-145 and 148-165) to use this single isDev value so generated code depends
only on the decision made in apply() and not ambient state.
🧹 Nitpick comments (3)
packages/webpack/react-refresh-webpack-plugin/src/ReactRefreshWebpackPlugin.ts (1)

148-155: Minor duplication: reuse existing isDev variable.

The isDev value is already computed at lines 99-100 in the outer scope. The inner computation at lines 152-153 is redundant since the RefreshRuntimeModule class has access to the outer scope via closure.

♻️ Suggested simplification
             .replaceAll('$MAIN_THREAD_LAYER$', LAYERS.MAIN_THREAD)
             .replaceAll('$BACKGROUND_LAYER$', LAYERS.BACKGROUND)
             .replaceAll(
               '__DEV__',
-              JSON.stringify(
-                process.env['NODE_ENV'] === 'development'
-                  || compiler.options.mode === 'development',
-              ),
+              JSON.stringify(isDev),
             );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/webpack/react-refresh-webpack-plugin/src/ReactRefreshWebpackPlugin.ts`
around lines 148 - 155, The code recomputes the development flag instead of
reusing the existing isDev; update the replacement in RefreshRuntimeModule to
use the outer-scope isDev variable rather than re-evaluating
process.env['NODE_ENV'] || compiler.options.mode. Locate the replaceAll call
that substitutes '__DEV__' inside the RefreshRuntimeModule context and replace
the inner JSON.stringify(...) expression with JSON.stringify(isDev) so the
module uses the previously computed isDev value.
examples/react-externals/package.json (1)

11-12: Consider cross-platform compatibility for NODE_ENV assignment.

The NODE_ENV=development prefix works on Unix-like systems but not on Windows. Consider using cross-env for cross-platform support if Windows development is expected.

💡 Suggested fix using cross-env
-    "dev:comp-lib": "NODE_ENV=development rslib build --config rslib-comp-lib.config.ts",
-    "dev:reactlynx": "NODE_ENV=development rslib build --config rslib-reactlynx.config.ts"
+    "dev:comp-lib": "cross-env NODE_ENV=development rslib build --config rslib-comp-lib.config.ts",
+    "dev:reactlynx": "cross-env NODE_ENV=development rslib build --config rslib-reactlynx.config.ts"

This would require adding cross-env as a devDependency.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/react-externals/package.json` around lines 11 - 12, Update the npm
scripts that set NODE_ENV to be cross-platform: replace the current Unix-only
prefixes used in the "dev:comp-lib" and "dev:reactlynx" scripts with a
cross-platform prefix by adding cross-env as a devDependency and prefixing those
script commands with cross-env NODE_ENV=development so they work on Windows and
Unix alike.
packages/webpack/react-refresh-webpack-plugin/test/intercept.test.ts (1)

135-216: Please cover the real production path too.

These cases flip mode to development before apply(), so they only exercise the __DEV__ substitution inside generate(). A regression where the plugin still installs the intercept runtime in an actual mode: 'production' build—or only leaks when NODE_ENV and mode disagree—would still pass here. Add one case that keeps production mode from the start, stubs NODE_ENV explicitly, and expects no runtime module / no __LYNX_WEBPACK_MODULES__ snippet.

Also applies to: 218-267

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/webpack/react-refresh-webpack-plugin/test/intercept.test.ts` around
lines 135 - 216, Add a complementary test that exercises the real production
path by instantiating ReactRefreshWebpackPlugin and calling plugin.apply with
mockCompiler.options.mode set to 'production' before apply(), stub
process.env.NODE_ENV to 'production' for the test, and verify that
addRuntimeModule is not invoked (generateResult remains empty) and the generated
string does NOT contain the __LYNX_WEBPACK_MODULES__ snippet; update the
existing test harness using the same mockCompiler shape (hooks.compilation.tap,
addRuntimeModule, RuntimeGlobals.interceptModuleExecution) and mirror this
change for the similar case referenced around lines 218-267 so both
development-early-return and true-production paths are covered.
🤖 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/rspeedy/lynx-bundle-rslib-config/src/webpack/MainThreadRuntimeWrapperWebpackPlugin.ts`:
- Around line 73-97: The current isDev check wrongly treats
process.env['NODE_ENV'] as equal weight with compiler.options.mode so a
production build can be contaminated if NODE_ENV=development; change the isDev
logic to make compiler.options.mode authoritative by checking
compiler.options.mode === 'development' first and only falling back to
process.env['NODE_ENV'] when compiler.options.mode is unset/undefined (affecting
the isDev variable used before compiler.hooks.thisCompilation and the runtime
module addition via LoadingConsumerModulesRuntimeModule and
RuntimeGlobals.interceptModuleExecution).
- Around line 62-68: The current __webpack_require__.i.push handler
unconditionally overwrites options.factory, which can clobber local module
implementations; modify the handler added via __webpack_require__.i.push to only
set options.factory from
globalThis[Symbol.for('__LYNX_WEBPACK_MODULES__')][moduleId] when
options.factory is missing/falsey (e.g., if (!options.factory) { options.factory
= globalModules[moduleId]; }), keeping the rest of the logic (moduleId and
globalModules lookup) intact.

---

Outside diff comments:
In
`@packages/webpack/react-refresh-webpack-plugin/src/ReactRefreshRspackPlugin.ts`:
- Around line 119-125: Compute the development flag once from
compiler.options.mode (use process.env.NODE_ENV only as a fallback when mode is
undefined), assign it to isDev inside apply(), and pass that boolean into
`#appendInterceptRuntimeModule` instead of re-evaluating NODE_ENV there; update
any other spots that recompute the flag (the blocks around the previous lines
139-145 and 148-165) to use this single isDev value so generated code depends
only on the decision made in apply() and not ambient state.

---

Nitpick comments:
In `@examples/react-externals/package.json`:
- Around line 11-12: Update the npm scripts that set NODE_ENV to be
cross-platform: replace the current Unix-only prefixes used in the
"dev:comp-lib" and "dev:reactlynx" scripts with a cross-platform prefix by
adding cross-env as a devDependency and prefixing those script commands with
cross-env NODE_ENV=development so they work on Windows and Unix alike.

In
`@packages/webpack/react-refresh-webpack-plugin/src/ReactRefreshWebpackPlugin.ts`:
- Around line 148-155: The code recomputes the development flag instead of
reusing the existing isDev; update the replacement in RefreshRuntimeModule to
use the outer-scope isDev variable rather than re-evaluating
process.env['NODE_ENV'] || compiler.options.mode. Locate the replaceAll call
that substitutes '__DEV__' inside the RefreshRuntimeModule context and replace
the inner JSON.stringify(...) expression with JSON.stringify(isDev) so the
module uses the previously computed isDev value.

In `@packages/webpack/react-refresh-webpack-plugin/test/intercept.test.ts`:
- Around line 135-216: Add a complementary test that exercises the real
production path by instantiating ReactRefreshWebpackPlugin and calling
plugin.apply with mockCompiler.options.mode set to 'production' before apply(),
stub process.env.NODE_ENV to 'production' for the test, and verify that
addRuntimeModule is not invoked (generateResult remains empty) and the generated
string does NOT contain the __LYNX_WEBPACK_MODULES__ snippet; update the
existing test harness using the same mockCompiler shape (hooks.compilation.tap,
addRuntimeModule, RuntimeGlobals.interceptModuleExecution) and mirror this
change for the similar case referenced around lines 218-267 so both
development-early-return and true-production paths are covered.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f6d746b0-8063-4469-9412-30822bd407a3

📥 Commits

Reviewing files that changed from the base of the PR and between 4daa4d9 and 3791734.

📒 Files selected for processing (13)
  • .changeset/six-readers-cover.md
  • examples/react-externals/package.json
  • examples/react-externals/rslib-comp-lib.config.ts
  • examples/react-externals/rslib-reactlynx.config.ts
  • packages/rspeedy/lynx-bundle-rslib-config/etc/lynx-bundle-rslib-config.api.md
  • packages/rspeedy/lynx-bundle-rslib-config/src/externalBundleRslibConfig.ts
  • packages/rspeedy/lynx-bundle-rslib-config/src/webpack/MainThreadRuntimeWrapperWebpackPlugin.ts
  • packages/rspeedy/lynx-bundle-rslib-config/test/external-bundle.test.ts
  • packages/webpack/externals-loading-webpack-plugin/src/index.ts
  • packages/webpack/react-refresh-webpack-plugin/runtime/intercept.cjs
  • packages/webpack/react-refresh-webpack-plugin/src/ReactRefreshRspackPlugin.ts
  • packages/webpack/react-refresh-webpack-plugin/src/ReactRefreshWebpackPlugin.ts
  • packages/webpack/react-refresh-webpack-plugin/test/intercept.test.ts

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: 2

🧹 Nitpick comments (1)
packages/rspeedy/lynx-bundle-rslib-config/src/webpack/MainThreadRuntimeWrapperWebpackPlugin.ts (1)

80-80: Use consistent tap name.

Line 80 uses 'MyRuntimePlugin' while lines 82 and 91 use PLUGIN_NAME. This inconsistency appears to be a leftover from refactoring. Using consistent tap names improves debuggability in webpack's plugin tracing.

♻️ Suggested fix
     if (isDev) {
-      compiler.hooks.thisCompilation.tap('MyRuntimePlugin', (compilation) => {
+      compiler.hooks.thisCompilation.tap(PLUGIN_NAME, (compilation) => {
         compilation.hooks.additionalTreeRuntimeRequirements.tap(
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/rspeedy/lynx-bundle-rslib-config/src/webpack/MainThreadRuntimeWrapperWebpackPlugin.ts`
at line 80, Replace the hardcoded tap name 'MyRuntimePlugin' with the
PLUGIN_NAME constant to keep tap names consistent; update the
compiler.hooks.thisCompilation.tap call inside
MainThreadRuntimeWrapperWebpackPlugin (the line using 'MyRuntimePlugin') to use
PLUGIN_NAME so it matches the other taps (e.g., the ones at lines referencing
PLUGIN_NAME) and improves webpack plugin tracing.
🤖 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/react-refresh-webpack-plugin/test/intercept.test.ts`:
- Around line 135-176: The test mutates process.env['NODE_ENV'] but only
restores it after execution; wrap the mutation and the plugin.apply/assertion in
a try/finally so NODE_ENV is always restored even if plugin.apply() or expect()
throws—change the two tests that set NODE_ENV to 'production' (the one calling
ReactRefreshWebpackPlugin().apply(mockCompiler) and the similar test at 179-205)
to save originalEnv, set NODE_ENV, run the test body in try, and restore
process.env['NODE_ENV'] = originalEnv in finally.
- Around line 135-176: Update the test to assert the production transform
emitted by the plugin instead of only checking that the hook wasn't tapped: in
the ReactRefreshWebpackPlugin test replace the current early-return assertion
with logic that captures the generated runtime source (the intercept runtime,
e.g. the generated intercept.cjs content produced when
plugin.apply(mockCompiler) is invoked) from the mocked compilation/runtime
module and assert it contains the production-optimized conditional (e.g. "if
(false)"). Locate symbols ReactRefreshWebpackPlugin, plugin.apply, the
mockCompiler.hooks.compilation.tap stub and the RuntimeModule mock and change
the tap implementation to capture the runtime source string produced by the
plugin, then replace expect(generateResult).toBe('') with an assertion that the
captured source includes "if (false)"; make the same change for the other test
case referenced (lines 179-205).

---

Nitpick comments:
In
`@packages/rspeedy/lynx-bundle-rslib-config/src/webpack/MainThreadRuntimeWrapperWebpackPlugin.ts`:
- Line 80: Replace the hardcoded tap name 'MyRuntimePlugin' with the PLUGIN_NAME
constant to keep tap names consistent; update the
compiler.hooks.thisCompilation.tap call inside
MainThreadRuntimeWrapperWebpackPlugin (the line using 'MyRuntimePlugin') to use
PLUGIN_NAME so it matches the other taps (e.g., the ones at lines referencing
PLUGIN_NAME) and improves webpack plugin tracing.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ed70a38b-6932-4cbc-a424-bbb35761d65b

📥 Commits

Reviewing files that changed from the base of the PR and between 3791734 and 4d16c22.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (5)
  • examples/react-externals/package.json
  • packages/rspeedy/lynx-bundle-rslib-config/src/webpack/MainThreadRuntimeWrapperWebpackPlugin.ts
  • packages/webpack/react-refresh-webpack-plugin/src/ReactRefreshRspackPlugin.ts
  • packages/webpack/react-refresh-webpack-plugin/src/ReactRefreshWebpackPlugin.ts
  • packages/webpack/react-refresh-webpack-plugin/test/intercept.test.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • examples/react-externals/package.json
  • packages/webpack/react-refresh-webpack-plugin/src/ReactRefreshWebpackPlugin.ts
  • packages/webpack/react-refresh-webpack-plugin/src/ReactRefreshRspackPlugin.ts

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: 2

🤖 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/rspeedy/lynx-bundle-rslib-config/test/external-bundle.test.ts`:
- Around line 248-271: The helper buildWithNodeEnv mutates
process.env['NODE_ENV'] but only restores it after awaiting build() and
decodeTemplate(), so if an error is thrown the env stays mutated; wrap the
awaits in a try/finally inside buildWithNodeEnv (using the existing prevNodeEnv)
and move process.env['NODE_ENV'] = prevNodeEnv into the finally block so build()
and decodeTemplate() are executed inside try and NODE_ENV is always restored.
- Around line 202-230: The test that stubs NODE_ENV via vi.stubEnv('NODE_ENV',
'development') should restore the environment after running to avoid leaking
into later tests; update the "should include LoadingConsumerModulesRuntimeModule
in the main-thread bundle" test (the block that calls vi.stubEnv) to wrap its
body in try/finally and call vi.unstubAllEnvs() in the finally clause so
subsequent helpers like buildWithNodeEnv/readers see the original NODE_ENV; keep
the existing build(...) and decodeTemplate(...) assertions intact and only add
the try/finally + vi.unstubAllEnvs() cleanup around them.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8b488ef7-5477-4420-afde-ffc0045d0110

📥 Commits

Reviewing files that changed from the base of the PR and between 8dfdba1 and 0ee5dac.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (7)
  • examples/react-externals/package.json
  • examples/react-externals/rslib-comp-lib.config.ts
  • examples/react-externals/rslib-reactlynx.config.ts
  • packages/rspeedy/lynx-bundle-rslib-config/src/externalBundleRslibConfig.ts
  • packages/rspeedy/lynx-bundle-rslib-config/src/webpack/MainThreadRuntimeWrapperWebpackPlugin.ts
  • packages/rspeedy/lynx-bundle-rslib-config/test/external-bundle.test.ts
  • packages/webpack/externals-loading-webpack-plugin/src/index.ts
💤 Files with no reviewable changes (2)
  • examples/react-externals/rslib-comp-lib.config.ts
  • examples/react-externals/rslib-reactlynx.config.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/rspeedy/lynx-bundle-rslib-config/src/webpack/MainThreadRuntimeWrapperWebpackPlugin.ts
  • examples/react-externals/package.json
  • packages/rspeedy/lynx-bundle-rslib-config/src/externalBundleRslibConfig.ts
  • packages/webpack/externals-loading-webpack-plugin/src/index.ts

@upupming upupming force-pushed the fix/try-fix-externals-hmr branch from 0ee5dac to 0d9d696 Compare March 10, 2026 12:34
@upupming upupming force-pushed the fix/try-fix-externals-hmr branch from 33894de to 747de5f Compare March 11, 2026 02:59
@upupming upupming merged commit ed566f0 into main Mar 11, 2026
79 of 81 checks passed
@upupming upupming deleted the fix/try-fix-externals-hmr branch March 11, 2026 08:34
colinaaa pushed a commit that referenced this pull request Mar 24, 2026
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/react@0.117.0

### Minor Changes

- feat: export `GlobalPropsProvider`, `GlobalPropsConsumer`,
`useGlobalProps` and `useGlobalPropsChanged` for `__globalProps`
([#2346](#2346))

- `GlobalPropsProvider`: A Provider component that accepts `children`.
It is used to provide the `lynx.__globalProps` context.
- `GlobalPropsConsumer`: A Consumer component that accepts a function as
a child. It is used to consume the `lynx.__globalProps` context.
- `useGlobalProps`: A hook that returns the `lynx.__globalProps` object.
It triggers a re-render when `lynx.__globalProps` changes.
- `useGlobalPropsChanged`: A hook that accepts a callback function. The
callback is invoked when `lynx.__globalProps` changes.

Note: When `globalPropsMode` is not set to `'event'` (default is
`'reactive'`), these APIs will be ineffective (pass-through) and will
log a warning in development mode, as updates are triggered
automatically by full re-render.

- **BREAKING CHANGE**:
([#2319](#2319))

Change preact package from `@hongzhiyuan/preact` to
`@lynx-js/internal-preact`.

Upgrade preact from
[f7693b72](preactjs/preact@f7693b7)
to
[55254ef7](preactjs/preact@55254ef),
see diffs at
[f7693b72...55254ef7](https://github.com/preactjs/preact/compare/f7693b72ecb4a40c66e6e47f54e2d4edc374c9f0...preactjs:preact:55254ef7021e563cc1a86fb816058964a1b6a29a?expand=1).

- feat: add `globalPropsMode` option to `PluginReactLynxOptions`
([#2346](#2346))

- When configured to `"event"`, `updateGlobalProps` will only trigger a
global event and skip the `runWithForce` flow.
- Defaults to `"reactive"`, which means `updateGlobalProps` will trigger
re-render automatically.

### Patch Changes

- Add `__BACKGROUND__` guard on `onBackgroundSnapshotInstanceUpdateId`
event to prevent bundling to main-thread on dev environment.
([#2332](#2332))

- refactor: extract static string in template literal
([#2334](#2334))

- fix: avoid crash when spread undefined ref
([#2333](#2333))

- Avoid registering lifecycle refs for main-thread functions (MTF) that
have not received an `execId` during `renderPage()` first-screen
binding. ([#2320](#2320))

## @lynx-js/react-umd@0.117.0

### Minor Changes

- Add standalone UMD build of the ReactLynx runtime.
([#2331](#2331))

## @lynx-js/react-rsbuild-plugin@0.13.0

### Minor Changes

- **BREAKING CHANGE**:
([#2319](#2319))

Change preact package from `@hongzhiyuan/preact` to
`@lynx-js/internal-preact`.

Upgrade preact from
[f7693b72](preactjs/preact@f7693b7)
to
[55254ef7](preactjs/preact@55254ef),
see diffs at
[f7693b72...55254ef7](https://github.com/preactjs/preact/compare/f7693b72ecb4a40c66e6e47f54e2d4edc374c9f0...preactjs:preact:55254ef7021e563cc1a86fb816058964a1b6a29a?expand=1).

- feat: add `globalPropsMode` option to `PluginReactLynxOptions`
([#2346](#2346))

- When configured to `"event"`, `updateGlobalProps` will only trigger a
global event and skip the `runWithForce` flow.
- Defaults to `"reactive"`, which means `updateGlobalProps` will trigger
re-render automatically.

### Patch Changes

- Updated dependencies
\[[`f1129ea`](f1129ea),
[`27f1cff`](27f1cff),
[`ed566f0`](ed566f0),
[`402ec2b`](402ec2b)]:
    -   @lynx-js/react-webpack-plugin@0.8.0
    -   @lynx-js/react-refresh-webpack-plugin@0.3.5
    -   @lynx-js/react-alias-rsbuild-plugin@0.13.0
    -   @lynx-js/use-sync-external-store@1.5.0
    -   @lynx-js/template-webpack-plugin@0.10.6
    -   @lynx-js/css-extract-webpack-plugin@0.7.0

## @lynx-js/react-webpack-plugin@0.8.0

### Minor Changes

- feat: add `globalPropsMode` option to `PluginReactLynxOptions`
([#2346](#2346))

- When configured to `"event"`, `updateGlobalProps` will only trigger a
global event and skip the `runWithForce` flow.
- Defaults to `"reactive"`, which means `updateGlobalProps` will trigger
re-render automatically.

### Patch Changes

- Fix sourcemap misalignment when wrapping lazy bundle main-thread
chunks. ([#2361](#2361))

The lazy bundle IIFE wrapper is now injected in `processAssets` at
`PROCESS_ASSETS_STAGE_OPTIMIZE_SIZE + 1` by walking chunk groups instead
of patching assets in `beforeEncode`.

- With `experimental_isLazyBundle: true`, the wrapper is applied to
lazy-bundle chunk groups.
- Without lazy bundle mode, the wrapper is applied to async main-thread
chunk groups generated by dynamic import.

Injecting the wrapper in this stage keeps the emitted JS stable after
optimization while still running before `DEV_TOOLING` sourcemap
finalization, so the generated `.js` and `.js.map` stay aligned.

- Set `__DEV__` and `__PROFILE__` to `true` on `NODE_ENV ===
'development'`.
([#2324](#2324))

## @lynx-js/rspeedy@0.13.6

### Patch Changes

- Rename Web Preview label to fix URL alignment
([#2355](#2355))

- Updated dependencies
\[[`799fda8`](799fda8)]:
    -   @lynx-js/cache-events-webpack-plugin@0.0.3
    -   @lynx-js/web-rsbuild-server-middleware@0.19.9

## @lynx-js/lynx-bundle-rslib-config@0.2.3

### Patch Changes

- Fix snapshot not found error when dev with external bundle
([#2316](#2316))

## @lynx-js/external-bundle-rsbuild-plugin@0.0.4

### Patch Changes

- Updated dependencies
\[[`ed566f0`](ed566f0)]:
    -   @lynx-js/externals-loading-webpack-plugin@0.0.5

## @lynx-js/kitten-lynx-test-infra@0.1.1

### Patch Changes

- feat: support page.screenshot()
([#2364](#2364))

- feat: initial commit
([#2272](#2272))

## @lynx-js/testing-environment@0.1.12

### Patch Changes

- Implement `__ElementAnimate` PAPI for web platform animation lifecycle
([#2329](#2329))

## @lynx-js/web-constants@0.19.9

### Patch Changes

- Implement `__ElementAnimate` PAPI for web platform animation lifecycle
([#2329](#2329))

-   Updated dependencies \[]:
    -   @lynx-js/web-worker-rpc@0.19.9

## @lynx-js/web-core@0.19.9

### Patch Changes

- Updated dependencies
\[[`2efecc2`](2efecc2)]:
    -   @lynx-js/web-constants@0.19.9
    -   @lynx-js/web-mainthread-apis@0.19.9
    -   @lynx-js/web-worker-runtime@0.19.9
    -   @lynx-js/web-worker-rpc@0.19.9

## @lynx-js/web-core-wasm@0.0.6

### Patch Changes

- reexports essential utils & types in @lynx-js/web-elements from
@lynx-js/web-core-wasm/client
([#2321](#2321))

-   Updated dependencies \[]:
    -   @lynx-js/web-worker-rpc@0.19.9

## @lynx-js/web-mainthread-apis@0.19.9

### Patch Changes

- Updated dependencies
\[[`2efecc2`](2efecc2)]:
    -   @lynx-js/web-constants@0.19.9

## @lynx-js/web-worker-runtime@0.19.9

### Patch Changes

- Updated dependencies
\[[`2efecc2`](2efecc2)]:
    -   @lynx-js/web-constants@0.19.9
    -   @lynx-js/web-mainthread-apis@0.19.9
    -   @lynx-js/web-worker-rpc@0.19.9

## @lynx-js/cache-events-webpack-plugin@0.0.3

### Patch Changes

- Cache `globalThis.loadDynamicComponent` in the cache events runtime
and add tests covering tt methods, performance events, and globalThis
replay behavior.
([#2343](#2343))

## @lynx-js/externals-loading-webpack-plugin@0.0.5

### Patch Changes

- Fix snapshot not found error when dev with external bundle
([#2316](#2316))

## @lynx-js/react-refresh-webpack-plugin@0.3.5

### Patch Changes

- Fix snapshot not found error when dev with external bundle
([#2316](#2316))

## @lynx-js/template-webpack-plugin@0.10.6

### Patch Changes

- Updated dependencies
\[[`d034dae`](d034dae)]:
    -   @lynx-js/web-core-wasm@0.0.6

## create-rspeedy@0.13.6



## @lynx-js/react-alias-rsbuild-plugin@0.13.0



## upgrade-rspeedy@0.13.6



## @lynx-js/web-core-server@0.19.9



## @lynx-js/web-rsbuild-server-middleware@0.19.9



## @lynx-js/web-worker-rpc@0.19.9

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
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.

2 participants