Skip to content

fix(watch): rebuild when a previously missing file is created#8562

Merged
hyf0 merged 8 commits intorolldown:mainfrom
hyf0-agent:fix/watch-missing-file-create
Mar 7, 2026
Merged

fix(watch): rebuild when a previously missing file is created#8562
hyf0 merged 8 commits intorolldown:mainfrom
hyf0-agent:fix/watch-missing-file-create

Conversation

@hyf0-agent
Copy link
Contributor

@hyf0-agent hyf0-agent commented Mar 6, 2026

When a build fails because an imported file doesn't exist, the watcher now tracks the target directory so it can detect when the missing file is created and automatically trigger a rebuild.

How it works

                     BUILD N (has error)
                     ══════════════════

watch_task.rs: build()

 1. active_missing_dirs.clear()
 2. bundler.clear_resolver_cache()
 3. scan_modules()
      │
      ▼
 resolve_utils.rs: resolve_dependencies()
      │
      │  import './utils/helper.js'
      │  → ResolveError::NotFound
      │
      ▼
 track_missing_imports()
      │
      │  computes target_dir:
      │    importer: /src/index.js
      │    specifier: ./utils/helper.js
      │    → /src/utils/  (parent of joined path)
      │
      ▼
 plugin_driver.missing_import_dirs.insert("/src/utils")

 4. update_watch_files_from()
      │
      │  reads missing_import_dirs → ["/src/utils"]
      │
      ├─► active_missing_dirs.insert("/src/utils")
      │
      └─► register with notify:
            /src/utils exists?
            ├─ YES → watch /src/utils (NonRecursive)
            │        registered_missing_dirs.insert()
            └─ NO  → walk up ancestors:
                     /src exists? YES → watch /src
                     (do NOT add to registered — retry
                      next build)

 5. Build errors → emit error event to user
    (files still watched even on error)


                     WAITING
                     ═══════

User creates /src/utils/helper.js

 notify emits: EventKind::Create
      │
      ▼
 task_fs_event_handler.rs: handle_event()
      │
      │  maps Create → WatcherChangeKind::Create
      │  path: "/src/utils/helper.js"
      │
      ▼
 watcher.rs: receives FileChanges message
      │
      ▼
 watch_task.rs: mark_needs_rebuild()
      │
      │  watched_files.contains(path)?  NO
      │  kind == Create?                YES
      │
      │  Check 1: active_missing_dirs
      │    .contains("/src/utils/helper.js")?  NO
      │
      │  Check 2: parent = "/src/utils"
      │    active_missing_dirs
      │    .contains("/src/utils")?  YES ✓
      │
      └─► needs_rebuild = true


                     BUILD N+1 (recovery)
                     ════════════════════

watch_task.rs: build()

 1. active_missing_dirs.clear()
 2. bundler.clear_resolver_cache()  ← stale cache gone
 3. scan_modules()
      │
      │  import './utils/helper.js'
      │  → resolves to /src/utils/helper.js  ✓
      │
      │  (no ResolveError → no track_missing_imports)
      │
 4. active_missing_dirs stays empty
    (registered_missing_dirs still has "/src/utils"
     — harmless, just means notify keeps watching it)

 5. Build succeeds → emit success event


             ANCESTOR FALLBACK VARIANT
             ═════════════════════════
             (when target dir doesn't exist either)

import './new-folder/file.js'  →  target_dir = /src/new-folder
/src/new-folder doesn't exist  →  watch /src (ancestor)
NOT added to registered_missing_dirs

User creates /src/new-folder/  →  notify: Create "/src/new-folder"
mark_needs_rebuild:
  active_missing_dirs.contains("/src/new-folder")?  YES → rebuild

Build N+1:
  /src/new-folder exists now  →  watch it directly
  registered_missing_dirs.insert("/src/new-folder")
  import still fails (file.js not there yet)
  → track_missing_imports records /src/new-folder again

User creates /src/new-folder/file.js  →  parent check matches → rebuild
Build N+2: resolves successfully ✓

Key design points

  • active_missing_dirs is per-build (cleared + repopulated) — stale dirs from fixed imports don't trigger spurious rebuilds
  • registered_missing_dirs is monotonic — purely prevents redundant notify registrations
  • Ancestor fallback retried each build — only "locks in" when the actual target dir is watched directly
  • Resolver cache always clearedpackage.json/tsconfig edits also invalidate resolutions
  • Watch files registered even on error — user can fix the problem and recovery happens automatically

Tests

Includes test fixes from #8560 — the tests now use named imports (import { foo } from './foo.js') instead of side-effect imports (import './foo.js') to avoid tree-shaking removing the imported content from output.

All 27 watch tests pass with zero regressions.

Closes the functionality gap noted in #8560.

sapphi-red and others added 2 commits March 6, 2026 14:05
When a build fails because an imported file doesn't exist, the watcher
now tracks the expected file path so it can detect when the missing
file is created and automatically trigger a successful rebuild.

Three changes work together:

1. **resolve_utils.rs**: When a path-like import fails with NotFound,
   compute the expected absolute path and add it to watch_files.

2. **watch_task.rs**: Remove the path.exists() requirement. For
   non-existing files, watch the parent directory instead (PollWatcher
   compat). Also clear the resolver cache before each rebuild so
   previously-failed lookups are re-evaluated.

3. **watch_coordinator.rs**: Filter out file change events that don't
   match any watched file, preventing spurious rebuilds from unrelated
   files in watched directories.
@hyf0 hyf0 force-pushed the fix/watch-missing-file-create branch from c88c40b to 6483ee7 Compare March 6, 2026 16:03
@netlify
Copy link

netlify bot commented Mar 6, 2026

Deploy Preview for rolldown-rs ready!

Name Link
🔨 Latest commit 48d5a18
🔍 Latest deploy log https://app.netlify.com/projects/rolldown-rs/deploys/69ac1468e9e7e8000855eb1a
😎 Deploy Preview https://deploy-preview-8562--rolldown-rs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR improves rolldown’s watch mode recovery when a build fails due to missing imports by tracking missing-import locations, watching for subsequent filesystem creates/renames, and triggering rebuilds when those changes become relevant.

Changes:

  • Track missing-import directories during resolution and expose them via the bundle/plugin driver.
  • Extend the watcher to watch missing-import directories and filter fs events to only rebuild on relevant changes.
  • Add watch tests for “import missing file then create it” and “import missing file then rename to it”, plus update watch-mode design notes.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
packages/rolldown/tests/watch/watch.test.ts Adds regression tests for missing-import recovery via create/rename.
meta/design/watch-mode.md Documents the missing-file detection strategy and notify event mapping updates.
crates/rolldown_watcher/src/watch_task.rs Watches missing-import directories and (conditionally) clears resolver cache on rebuild.
crates/rolldown_watcher/src/watch_coordinator.rs Filters out irrelevant file events to avoid spurious rebuilds.
crates/rolldown_watcher/src/task_fs_event_handler.rs Adjusts notify rename event mapping to Create/Delete/Update.
crates/rolldown_plugin/src/plugin_driver/plugin_driver_factory.rs Adds missing_import_dirs storage to the plugin driver.
crates/rolldown_plugin/src/plugin_driver/mod.rs Clears missing_import_dirs on plugin driver reset.
crates/rolldown/src/module_loader/resolve_utils.rs Records missing-import directories on ResolveError::NotFound for path-like specifiers.
crates/rolldown/src/bundler/impl_bundler_getter.rs Adds Bundler::clear_resolver_cache().
crates/rolldown/src/bundle/bundle.rs Exposes get_missing_import_dirs() from Bundle.

@hyf0 hyf0 enabled auto-merge (squash) March 7, 2026 12:04
@hyf0 hyf0 merged commit ae219fc into rolldown:main Mar 7, 2026
37 checks passed
graphite-app bot pushed a commit that referenced this pull request Mar 8, 2026
Partially reverts #8562 but still keeps the fix for #8560
@github-actions github-actions bot mentioned this pull request Mar 9, 2026
shulaoda added a commit that referenced this pull request Mar 9, 2026
## [1.0.0-rc.8] - 2026-03-09

### 🚀 Features

- watch: enable full functional fs watcher in wasm (#8575) by @hyf0
- watch: expose debounce related options (#8572) by @hyf0

### 🐛 Bug Fixes

- detect new URL(…, import.meta.url) with no-sub template literal (#8565) by @char
- devtools: trace dynamic imports in devtools (#8581) by @cal-gooo
- watch: rebuild when a previously missing file is created (#8562) by @hyf0-agent
- watch: filter out Access events to prevent infinite rebuild loop on Linux (#8557) by @hyf0-agent

### 🚜 Refactor

- watch: remove auto watch for fail imports (#8585) by @hyf0
- fs_watcher: unify the way of constructing watcher (#8571) by @hyf0
- cli: migrate CLI to CAC (#8551) by @h-a-n-a
- switch asset module support from hard-code to builtin plugin (#8546) by @hyf0

### 📚 Documentation

- fix subject-verb agreement in why-bundlers.md (#8591) by @brandonzylstra
- maintenance: align release and canary workflow guide (#8538) by @minsoo-web
- add `format` option to directives example config (#8590) by @shulaoda
- fix: change twitter to x logo in team (#8552) by @mdong1909
- correct composable filter support explanation (#8550) by @sapphi-red

### ⚡ Performance

- testing: share tokio runtime across fixture tests (#8567) by @Boshen

### 🧪 Testing

- hmr: fix infinite loop in dev server test retry logic (#8576) by @hyf0-agent
- cli: add more cli-e2e test cases (#8548) by @h-a-n-a

### ⚙️ Miscellaneous Tasks

- docs: update in-depth/directives for `output.strict` option (#8535) by @minsoo-web
- add PNPM_HOME Dev Drive mapping to Windows CI workflows (#8589) by @Boshen
- deps: update github-actions (#8588) by @renovate[bot]
- move Windows cargo target dir to Dev Drive (#8586) by @Boshen
- optimize cache keys to fix race conditions and reduce usage (#8578) by @Boshen
- remove WASI build & test pipeline (#8580) by @Boshen
- remove unnecessary submodule checkouts (#8577) by @Boshen
- use Dev Drive for Windows CI jobs (#8574) by @Boshen
- skip redundant native binding build for browser and remove standalone job (#8573) by @Boshen
- parallelize Node tests on ubuntu, single Node 24 on macOS/windows (#8570) by @Boshen
- docs: bump @voidzero-dev/vitepress-theme to 4.8.0 (#8558) by @crusty-voidzero
- dedupe type-check from dev server workflow (#8554) by @Boshen

### ❤️ New Contributors

* @brandonzylstra made their first contribution in [#8591](#8591)
* @char made their first contribution in [#8565](#8565)
* @cal-gooo made their first contribution in [#8581](#8581)
* @hyf0-agent made their first contribution in [#8562](#8562)
* @h-a-n-a made their first contribution in [#8551](#8551)

Co-authored-by: shulaoda <165626830+shulaoda@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants