Appearance
Watch Mode
When to use
Use watch mode when developing locally and you want automatic rebuilds, retests, or reprocessing on file changes. Watch mode eliminates the manual save-switch-run cycle and provides continuous feedback during development.
The pattern
Three approaches depending on what you need to watch:
- npm script watchers — Run build and test watchers in parallel using
npm-run-all. - Node.js
--watchflag — Built-in file watching for Node.js scripts (v18.11+). - DOMContentLoaded auto-processing — Automatically process dynamic content when the page loads, so changes to includes or components appear immediately.
Parallel watchers with npm-run-all
Define separate watch scripts for each concern, then run them together with run-p (parallel mode). This example runs build and test watchers alongside a development server in one terminal:
json
{
"scripts": {
"dev": "run-p server:dev build:watch test:watch",
"server:dev": "nodemon --inspect server.js",
"build:watch": "vite build --watch",
"test:watch": "node --test --watch test/**/*.test.js"
}
}run-p from npm-run-all runs all scripts in the same terminal. Output from each is interleaved. When any script exits, the others are terminated.
Node.js --watch flag
Node.js has built-in watch mode that restarts the process when imported files change:
bash
node --watch server.jsUse with --test for continuous testing:
bash
node --test --watchFor more control, use nodemon which supports config files, ignore patterns, and custom signals. This example passes environment variables to the watched process:
javascript
import { sh } from "sh-cmd-tag";
async function startDev() {
sh`nodemon --inspect server/index.js`.run({
cwd: String(appRoot),
env: {
...process.env,
NODE_ENV: "development",
},
});
}Conditional watch mode lets users opt in via a CLI flag:
javascript
program.option("-w, --watch", "run in watch mode");
program.action(async function (options) {
const command = sh`
node --test
${options.watch ? "--watch" : undefined}
test/**/*.test.js
`;
await command.run({ stdio: "inherit" });
});DOMContentLoaded auto-processing
For browser-side development tooling, automatically process dynamic content when the DOM is ready. This pattern works for custom include systems, component loaders, or any setup that needs to run once on page load:
javascript
if (document.readyState === "loading") {
document.addEventListener(
"DOMContentLoaded",
processIncludes
);
} else {
processIncludes();
}This handles both cases: script loaded before the DOM is ready (deferred scripts) and script loaded after (dynamically inserted or at end of body).
Trade-offs
- Parallel watchers compete for terminal output. When build and test watchers both print at once, output can interleave confusingly. Consider using separate terminals or a tool like
concurrentlythat prefixes each line. - Node.js
--watchrestarts the entire process. File changes kill and restart the process, which means in-memory state is lost. For stateful servers, use HMR (hot module replacement) instead. nodemonis an extra dependency. For simple scripts,node --watch(built-in since v18.11) is sufficient. Usenodemonwhen you need ignore patterns, custom restart delays, or signal handling.- DOMContentLoaded runs once. It only fires on initial page load. For dynamic content added after load, you need a
MutationObserveror explicit re-processing calls.