Watch mode (nub watch) runs your entry point and restarts it whenever a file it depends on changes.

The engine underneath is Node's own --watch (run with --watch-preserve-output), with a Nub-side layer that reports the in-memory-transpiled dependency graph — plus the off-graph files that still invalidate a process — back to Node's watcher.

For how nub runs files in the first place, see Running files; for scripts, Running scripts.

Watching a file

The watcher takes a file, not a script name. Point nub watch at an entry file — the one your dev script runs, for example. It runs the file, then restarts the process on any change to the file or anything in its resolved import graph.

nub watch src/server.ts

TypeScript, JSX, .env* loading, tsconfig.json paths — everything nub <file> gives you is active here too, because nub watch runs the same augmented Node underneath. A project nub.jsonc is in force on every restart: preloads, environment files, loaders, conditions, TypeScript transforms, and the selected tsconfig.

A --watch placed after a script name is forwarded to the script like any other argument — nub run test --watch runs your test tool's own watch mode, it does not invoke Nub's watcher. See nub run → Forward arguments.

--watch

Flag and subcommand are aliases: nub --watch <file> and nub watch <file> run the same code path. The flag form matches node --watch script.ts.

nub --watch src/server.ts

Preserve output

Nub runs Node's watcher with --watch-preserve-output, so a restart prints Node's own status lines instead of wiping the terminal — the previous run's error message stays visible:

$ nub watch src/server.ts
Completed running 'src/server.ts'. Waiting for file changes before restarting...
Restarting 'src/server.ts'

Watched files

The watch set is loader-instrumented, not glob-based: nub watch only watches files loaded as part of the run, plus a small set of off-graph files that invalidate the process when they change. A restart fires on a change to:

  • Any file in the resolved dependency graph of the entry — including .ts / .tsx files transpiled in-memory through module.registerHooks() that Node never sees on disk.
  • Your .env* files — these are read once at boot and aren't in any import graph, so Nub reports them to the watcher explicitly. Files named with --env-file or --env-file-if-exists are watched the same way, and every restart re-reads them, so an edited value is live on the next run. Re-reading on restart needs Node 20.6 or newer, the version that introduced the underlying flag; below it the values are captured once at startup, and an edit applies only after you restart nub watch itself.
  • The tsconfig.json extends chain — editing a tsconfig (for example, changing a paths mapping) restarts the process even though tsconfig isn't imported by anything.
  • package.json.

Nub reports the off-graph files to Node's watcher over its WATCH_REPORT_DEPENDENCIES channel.

A rebuild that emits dist/foo.js does not trigger a restart unless something in the run imported dist/foo.js.

nub watch src/server.ts
# editing dist/** or coverage/** does not restart

Plain Node --watch

Nub's watch mode adds the banner, the preserve-output default, and the off-graph reporting on top of Node's. For vanilla node --watch, run it directly:

node --watch script.js

Nub's per-invocation PATH shim only affects descendants of an active nub call. A node installed by nub node shim resolves the pinned version and adds nothing else, so node --watch stays vanilla there too.

Read the full docs for Node's --watch flag on nodejs.org.

  • Running files — how nub <file> executes TypeScript and JSX.
  • Script runnernub run and its workspace surface.
  • Bin runnernubx, running local CLIs from node_modules/.bin.
  • FAQ — short answers across the whole surface.