rsbuild icon indicating copy to clipboard operation
rsbuild copied to clipboard

[Bug]: HMR throws "Module not found" error when dependency rebuilds in monorepo

Open curlykay opened this issue 3 months ago • 9 comments

Version

System:
    OS: macOS 15.6.1
    CPU: (14) arm64 Apple M4 Pro
    Memory: 11.55 GB / 48.00 GB
    Shell: 5.9 - /bin/zsh
  Browsers:
    Chrome: 140.0.7339.208
    Edge: 140.0.3485.94
    Safari: 18.6

Details

Description

In a monorepo environment, when using turbo watch to monitor dependency library source code changes and trigger automatic rebuilds, Rsbuild's HMR throws a Module not found error, while Vite behaves normally under the same conditions.

Rsbuild Environment Test Results

  • ✅ When dependencies are built with tsup, HMR works correctly
  • ❌ When dependencies are built with rslib, HMR throws errors and interrupts the development workflow
  • ❌ When dependencies are built with tsdown, HMR throws errors and interrupts the development workflow

Vite Environment Test Results (Comparison)

  • ✅ When dependencies are built with tsup, HMR works correctly
  • ✅ When dependencies are built with rslib, HMR works correctly
  • ✅ When dependencies are built with tsdown, HMR works correctly

Conclusion: With the same monorepo configuration and dependency build process, HMR works correctly for all dependency libraries in the Vite environment. The issue appears to be with Rsbuild.

Reproduction

I've created a minimal reproduction repository: issue-rsbuild-rsrepo

Steps to Reproduce

  1. Clone the repository and initialize:
git clone https://github.com/curlykay/issue-rsbuild-rsrepo.git
cd rsrepo
pnpm run setup
  1. Test Rsbuild (problematic):
pnpm run prt:dev-watch
  • Modify packages/lib-rslib/src/index.ts, HMR throws error ❌
  • Modify packages/lib-tsdown/src/index.ts, HMR throws error ❌
  • Modify packages/lib-tsup/src/index.ts, HMR works correctly ✅
  1. Test Vite (working):
pnpm run pvt:dev-watch
  • Modify packages/lib-rslib/src/index.ts, HMR works correctly ✅
  • Modify packages/lib-tsdown/src/index.ts, HMR works correctly ✅
  • Modify packages/lib-tsup/src/index.ts, HMR works correctly ✅

Expected Behavior

After the dependency library finishes rebuilding, Rsbuild should execute HMR correctly, updating the page content without errors (just like Vite's behavior).

Actual Behavior

When dependency libraries built with rslib or tsdown finish rebuilding, Rsbuild HMR throws an error:

playground-rsbuild-ts:dev: start   building removed lib-rslib/dist/index.js
playground-rsbuild-ts:dev: error   Build error:
playground-rsbuild-ts:dev: File: ./src/index.ts:1:1
playground-rsbuild-ts:dev:   × Module not found: Can't resolve 'lib-rslib' in
'/Users/ck/Space/fiddle/rsrepo/packages/playground-rsbuild-ts/src'
playground-rsbuild-ts:dev:     ╭─[10:9]
playground-rsbuild-ts:dev:   8 │   <div class="content">
playground-rsbuild-ts:dev:   9 │
playground-rsbuild-ts:dev:  10 │     <p>${getLibRsLib()}</p>
playground-rsbuild-ts:dev:     ·          ───────────
playground-rsbuild-ts:dev:  11 │     <p>${getLibTsdown()}</p>
playground-rsbuild-ts:dev:  12 │     <p>${getLibTsup()}</p>
playground-rsbuild-ts:dev:     ╰────

An error mask appears on the page, interrupting the development workflow.

Note: The first modification to lib-rslib may work correctly, but the second modification will always fail.

Workaround

Trigger a save in packages/playground-rsbuild-ts/src/index.ts to temporarily recover.

Environment

  • Rsbuild: 1.5.13
  • Node: 22.20.0
  • Package Manager: [email protected]
  • OS: macOS (Darwin 24.6.0)
  • Monorepo Tool: Turborepo 2.5.8

Additional Context

The log message building removed lib-rslib/dist/index.js indicates that Rsbuild detected the dependency file change.

Since Vite behaves normally under the exact same monorepo environment and dependency build process, while Rsbuild only works correctly with tsup-built dependencies, the possible causes are:

  1. Rsbuild HMR module resolution timing issue: After detecting file changes, Rsbuild may attempt to re-resolve modules before the dependency files are fully written, whereas Vite may have better waiting or retry mechanisms

  2. Rslib/Tsdown build output strategy differences: These tools may adopt a "delete-then-write" or "multi-stage write" strategy, creating a brief file absence window during the update process, and Rsbuild's HMR happens to attempt module resolution during this window

  3. Tsup atomic output: Tsup may use a faster or more atomic file writing approach (such as writing to a temporary file first, then renaming), ensuring that Rsbuild always reads complete files

  4. Turborepo file watching event timing: Although Vite working normally suggests this is less likely, there may still be timing coordination issues between the file change events triggered by Turborepo and Rsbuild's handling logic

The specific cause requires further analysis. Thank you for helping diagnose this issue!

Reproduce link

https://github.com/curlykay/issue-rsbuild-rsrepo

Reproduce Steps

rsbuild:

  1. Run "pnpm run setup && pnpm run prt:dev-watch"
  2. Modify “packages/lib-tsup/src/index.ts”, observe terminal output and page refresh. Repeat modification twice.
  3. Modify “packages/lib-rslib/src/index.ts”, observe terminal output and page refresh. Repeat modification twice.
  4. Compare results

vite:

  1. Run "pnpm run setup && pnpm run pvt:dev-watch"
  2. Modify “packages/lib-tsup/src/index.ts”, observe terminal output and page refresh. Repeat modification twice.
  3. Modify “packages/lib-rslib/src/index.ts”, observe terminal output and page refresh. Repeat modification twice.
  4. Compare results

curlykay avatar Sep 30 '25 08:09 curlykay

Thank you for your detailed explanation, I will research this issue

chenjiahan avatar Oct 09 '25 13:10 chenjiahan

From my initial testing, this issue shows a mix of expected behavior and areas that could be improved:

  1. When you run rslib build multiple times, Rslib clean the dist directory before generating new bundles. This is the expected behavior. If you want to avoid clearing the output each time, use rslib build --watch instead of running the build command repeatedly.
  2. When the dist directory is cleaned, Rsbuild will report that it can't resolve the lib-rslib/dist/index.js file — this is also expected.
  3. Once the bundles are re-generated in the dist directory, Rsbuild should recover from the error state and finish a successful build. However, this recovery doesn't currently work as expected, we'll look into it further.

chenjiahan avatar Oct 09 '25 14:10 chenjiahan

Thank you for your prompt follow-up. I attempted to find a temporary solution and successfully resolved the Hot Module Replacement (HMR) issue by enabling rslib build --watch mode. However, this requires some custom modifications to package.json and turbo.json. The specific changes can be referenced in this commit: https://github.com/curlykay/issue-rsbuild-rsrepo/commit/4254469f8fc51f4088e42a138056a492c38acb6d.

While effective, this solution comes with a minor drawback: to ensure watch mode functions correctly, I can no longer use ^build within dependsOn in turbo.json. This compromises Turborepo's ability to automatically analyze task dependencies. In my view, this stems primarily from the lack of an “exclude” capability in turbo.json's dependsOn configuration, making it less straightforward to have fine-grained control over dependencies.

Therefore, I eagerly anticipate the Rsbuild team's fix for this issue. Once Rsbuild can properly “recover from the error state and finish a successful build” I won't need to rely on these custom configurations and can organize my projects in a more standard and concise manner.

curlykay avatar Oct 10 '25 06:10 curlykay

Makes sense. @stormslowly is already investigating the monorepo error recovery issue and hopes to resolve it soon. 😄

chenjiahan avatar Oct 10 '25 06:10 chenjiahan

related to https://github.com/web-infra-dev/rspack/issues/11239

SoonIter avatar Oct 14 '25 03:10 SoonIter

Currently workaround:

import { defineConfig } from "@rsbuild/core";

export default defineConfig({
  tools: {
    rspack: {
      watchOptions: {
        followSymlinks: true,
        ignored: [],
      },
    },
  },
});

the root cause is: https://github.com/web-infra-dev/rspack/issues/11239#issuecomment-3404402002

I will fix it later.

stormslowly avatar Oct 15 '25 06:10 stormslowly

Useful configuration! It perfectly solved the problem—truly astonishing efficiency!

Aside:

When /package/rslib/dist is removed by npm run build:rslib

When starting a Vite project (pnpm run pvt:dev-watch), I experimentally deleted only the packages/lib-rslib/dist directory manually. The Vite service showed no reaction—no error logs, no hot module replacement triggered. It only triggered HMR after modifying the source code in packages/lib-rslib/src/index.ts and regenerating the new dist files.

That said, this behavior actually provides a decent development experience. Whether this is Vite's intended mechanism, a deliberate optimization, or an accidental “bug” remains unclear to me at this point.

curlykay avatar Oct 15 '25 18:10 curlykay

I experimentally deleted only the packages/lib-rslib/dist directory manually. The Vite service showed no reaction

This doesn't seem like the correct behavior, since it doesn't properly reflect the file changes.

chenjiahan avatar Oct 16 '25 02:10 chenjiahan

You're right; the results do show that. If I delete packages/lib-rslib/dist/index.js, the Vite service terminal immediately outputs an error log stating ‘file does not exist’. It's as if it deliberately ignores the deletion of the packages/lib-rslib/dist directory.

curlykay avatar Oct 16 '25 03:10 curlykay