Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/runtime/watch-mode.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ Use `bun --hot` to enable hot reloading when executing code with Bun. Unlike `--
bun --hot server.ts
```

Starting from the entrypoint (`server.ts` in this example), Bun builds a registry of all imported source files (excluding those in `node_modules`) and watches them for changes. When a file changes, Bun performs a "soft reload". Bun re-evaluates all files, but global state (notably, the `globalThis` object) persists.
Starting from the entrypoint (`server.ts` in this example), Bun builds a registry of all imported source files (excluding those in `node_modules`) and watches them for changes. If you start Bun in a package folder of `node_modules` or below one, the files of that package are watched too (on Windows, not yet when the folder is entered through a link). When a file changes, Bun performs a "soft reload". Bun re-evaluates all files, but global state (notably, the `globalThis` object) persists.

```ts title="server.ts" icon="/icons/typescript.svg"
// make TypeScript happy
Expand Down
16 changes: 12 additions & 4 deletions src/jsc/VirtualMachine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4653,9 +4653,14 @@ impl VirtualMachine {
}
}

/// `add_to_watcher_if_needed`, for threads that work for this one. `None` unless watch mode is
/// `add_to_watcher_if_needed`, for threads that work for this one, with the watcher's rule for package files. `None` unless watch mode is
/// enabled.
pub fn watcher_for_threads(&self) -> Option<impl Fn(&[u8]) + Send + Sync + 'static> {
pub fn watcher_for_threads(
&self,
) -> Option<(
bun_paths::strings::NodeModulesScope,
impl Fn(&[u8]) + Send + Sync + 'static,
)> {
struct Shared {
watcher: *mut crate::hot_reloader::ImportWatcher,
_ticket: crate::Ticket,
Expand All @@ -4666,17 +4671,20 @@ impl VirtualMachine {
if !self.is_watcher_enabled() {
return None;
}
// SAFETY: as in `add_to_watcher_if_needed`. The scope is never written after `Watcher::init`.
let scope = unsafe { *(*self.bun_watcher_ptr()).node_modules_scope() };
// One of these threads at a time has the `&mut`.
let shared = bun_threading::Guarded::new(Shared {
watcher: self.bun_watcher_ptr(),
_ticket: self.ticket(),
});
Some(move |path: &[u8]| {
let watch = move |path: &[u8]| {
let shared = shared.lock();
let watcher = shared.watcher;
// SAFETY: as in `add_to_watcher_if_needed`.
let _ = unsafe { (*watcher).add_file_by_path_slow(path) };
})
};
Some((scope, watch))
}

/// `bun_resolver` holds the manager as an opaque forward-decl (it cannot
Expand Down
23 changes: 20 additions & 3 deletions src/jsc/hot_reloader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use bun_core::ZStr;
#[cfg(not(windows))]
use bun_paths::SEP;
use bun_paths::strings;
use bun_paths::strings::NodeModulesScope;
#[cfg(not(windows))]
use bun_resolver::fs::PathName;
use bun_resolver::fs::{self as Fs, FileSystem};
Expand Down Expand Up @@ -63,7 +64,15 @@ impl ImportWatcher {
}
}

/// Declines a file inside `node_modules`: the caller keeps `fd`.
#[inline]
pub fn node_modules_scope(&self) -> &NodeModulesScope {
match self {
ImportWatcher::Hot(w) | ImportWatcher::Watch(w) => w.node_modules_scope(),
ImportWatcher::None => &NodeModulesScope::WHOLE_PATH,
}
}

/// Declines a package file by the watcher's `node_modules_scope`: the caller keeps `fd`.
#[inline]
pub fn add_file<const COPY_FILE_PATH: bool>(
&mut self,
Expand All @@ -77,7 +86,10 @@ impl ImportWatcher {
) -> bun_sys::Result<bun_watcher::FdOwnership> {
match self {
ImportWatcher::Hot(watcher) | ImportWatcher::Watch(watcher) => {
if strings::path_is_inside_node_modules(file_path) {
if watcher
.node_modules_scope()
.file_is_inside_node_modules(file_path)
{
return Ok(bun_watcher::FdOwnership::Caller);
}
watcher.add_file::<COPY_FILE_PATH>(fd, file_path, hash, dir_fd, package_json)
Expand Down Expand Up @@ -726,7 +738,12 @@ where
}));

// SAFETY: see above; `watcher_top_level_dir` returns `&'static [u8]`.
let watcher = match Watcher::init(reloader, unsafe { (*this).watcher_top_level_dir() }) {
let top_level_dir = unsafe { (*this).watcher_top_level_dir() };
let watcher = match Watcher::init(
reloader,
top_level_dir,
bun_watcher::NodeModulesRule::BelowProjectRoot,
) {
Ok(w) => w,
Err(err) => {
bun_core::pretty_errorln!(
Expand Down
Loading
Loading