Using watch mode
Popular tools like Webpack and Jest provide a "watch mode" feature: After the task is completed, the tool enters a loop where it watches the file system for changes to your source files. Whenever a change is detected, the task runs again to update its output. This speeds up development because (1) rebuilding happens automatically whenever you save a file, and (2) the task can benefit from in-memory caching because its process never terminates.
But these features typically only work for a single project. When working in a monorepo, we need a watch mode that can monitor multiple projects at once.
A thought experiment
Suppose hypothetically that our monorepo has the following projects:
In the above illustration, the circles represent local projects, not external NPM dependencies.
The arrow from D
to C
indicates that D
depends on C
; this means that C
must be built before
D
can be built.
Suppose that you save a change to project B
:
For a multi-project "watch mode", we'd expect the following things to happen in order:
B
should get rebuilt because its file was changed;- next,
C
should get rebuilt because it depends onB
- next,
D
should get rebuilt because it depends onC
- finally, the Webpack dev server (hosted by
D
presumably) refreshes your web browser with the rebuilt app
How to accomplish that with Rush? Suppose our projects B
and C
have a simplistic build script like this:
package.json
. . .
"scripts": {
"build": "rm -Rf lib/ && tsc && jest"
}
. . .
We might try an experiment like invoking rush build --to-except D
in an endless loop...
# Build everything that D depends on (but not D itself),
# and keep doing that in an endless loop:
while true; do rush build --to-except D; done
...and then, while that is running, we invoke heft start
(or webpack serve
) in the folder for project D
.
You'll find that this approach has some problems:
The
rm -Rf lib/
deletes files that are symlink targets. Symlinks seem to confuse Webpack's file watcher, so you may see lots of errors reporting that an imported file cannot be found. Webpack won't recover from that, because the symlink timestamp isn't updated when the file is later rewritten.The
jest
andrm -Rf
steps are generally unimportant while watching. The developer's inner loop for edit -> rebuild -> reload is much slower than it needs to be.
These problems can be solved by creating a special streamlined script for watch mode, something like this:
package.json
. . .
"scripts": {
"build": "rm -Rf lib/ && tsc && jest",
"build:watch": "tsc"
}
. . .
The "watchForChanges" setting (experimental)
Rush's multi-project "watch mode" formalizes this basic idea, replacing the simple loop with an optimized chokidar filesystem monitor.
How you enable Rush multi-project watch mode depends on whether you are using a bulk command or a phased command for your build scripts. We suggest switching to phased builds before enabling watch mode, as it is a better and easier-to-understand experience for developers.
Watch mode for phased commands
In your command-line.json config file, add the new
watchOptions
section to each phased command you want to enable. For example:. . .
"commands": [
{
"commandKind": "phased",
"name": "build",
"phases": ["_phase:build"],
"enableParallelism": true,
"incremental": true,
"watchOptions": {
"alwaysWatch": false,
"watchPhases": ["_phase:build"]
}
},
{
"commandKind": "phased",
"name": "test",
"phases": ["_phase:build", "_phase:test"],
"enableParallelism": true,
"incremental": true,
"watchOptions": {
"alwaysWatch": false,
"watchPhases": ["_phase:build", "_phase:test"]
}
}
]Rush will automatically add a new boolean flag,
--watch
, to any command with thewatchOptions
property.Invoke the command using project selection parameters that select all of
D
's dependencies but notD
itself:# Build everything that D depends on (but not D itself),
# and keep doing that in an endless loop:
$ rush build --watch --to-except DThen, start your dev server in the app folder:
# Start Webpack's dev server in the folder for project D
# (which is the web application in this example):
$ cd apps/D
$ heft start # <-- or your own "npm run start" equivalent here
Watch mode for bulk commands
Add a custom command in your command-line.json config file. Continuing the example above, our custom command will be called
"build:watch"
. The important settings are"incremental"
and"watchForChanges"
:common/config/rush/command-line.json
. . .
"commands": [
{
"name": "build:watch",
"commandKind": "bulk",
"summary": "Build projects and watch for changes",
"description": "For details, see the article \"Using watch mode\" on the Rush website: https://rushjs.io/",
// use incremental build logic (important)
"incremental": true,
"enableParallelism": true,
// Enable "watch mode"
"watchForChanges": true
},
. . .Add a
"build:watch"
script to the package.json file for each Rush project. (PR #2298 aims to simplify this step for projects whose"build:watch"
would be the same as"build"
. Eventually it will also be possible to consolidate these definitions in a shared rig package.)If you're using Heft, your scripts would look like this:
package.json
. . .
"scripts": {
"build": "heft build --clean",
"build:watch": "heft build"
}
. . .Invoke the command using project selection parameters that select all of
D
's dependencies but notD
itself:# Build everything that D depends on (but not D itself),
# and keep doing that in an endless loop:
rush build:watch --to-except DLastly, start your dev server in the app folder:
# Start Webpack's dev server in the folder for project D
# (which is the web application in this example):
cd apps/D
heft start # <-- or your own "npm run start" equivalent hereIn some situations, the
--changed-projects-only
command can be combined with"watchForChanges"
for even faster watching. The section Building changed projects only explains how it works and when it is appropriate.
"Experimental" The
"watchForChanges"
feature is still in its early stages. Feedback is welcome! GitHub issue #1202 tracks additional work items and William Bernting's original dev plan.
Community solutions
The Rush community has shared some interesting alternative approaches to this problem that are also helpful:
@telia/rush-select is an interactive dashboard for monitoring Rush projects and selecting what to rebuild.
rush-dev-watcher is a simple but useful script from Daniel Imfeld that performs an initial build and then launches multiple watchers.