Skip to content

Commit 1e3fb57

Browse files
committed
fix #4178: add the --watch-delay= option
1 parent c1f5f18 commit 1e3fb57

11 files changed

Lines changed: 98 additions & 3 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,12 @@
4141
(()=>{})();
4242
```
4343
44+
* Support a configurable delay in watch mode before rebuilding ([#3476](https://github.com/evanw/esbuild/issues/3476), [#4178](https://github.com/evanw/esbuild/issues/4178))
45+
46+
The `watch()` API now takes a `delay` option that lets you add a delay (in milliseconds) before rebuilding when a change is detected in watch mode. If you use a tool that regenerates multiple source files very slowly, this should make it more likely that esbuild's watch mode won't generate a broken intermediate build before the successful final build. This option is also available via the CLI using the `--watch-delay=` flag.
47+
48+
This should also help avoid confusion about the `watch()` API's options argument. It was previously empty to allow for future API expansion, which caused some people to think that the documentation was missing. It's no longer empty now that the `watch()` API has an option.
49+
4450
* Allow mixed array for `entryPoints` API option ([#4223](https://github.com/evanw/esbuild/issues/4223))
4551
4652
The TypeScript type definitions now allow you to pass a mixed array of both string literals and object literals to the `entryPoints` API option, such as `['foo.js', { out: 'lib', in: 'bar.js' }]`. This was always possible to do in JavaScript but the TypeScript type definitions were previously too restrictive.

cmd/esbuild/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ var helpText = func(colors logger.Colors) string {
5454
safari11, edge16, node10, ie9, opera45, default esnext)
5555
--watch Watch mode: rebuild on file system changes (stops when
5656
stdin is closed, use "--watch=forever" to ignore stdin)
57+
--watch-delay=... How many milliseconds to wait before watch mode rebuilds
5758
5859
` + colors.Bold + `Advanced options:` + colors.Reset + `
5960
--allow-overwrite Allow output files to overwrite input files

cmd/esbuild/service.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,11 @@ func (service *serviceType) handleIncomingPacket(bytes []byte) {
334334
go func() {
335335
defer service.keepAliveWaitGroup.Done()
336336
defer build.disposeWaitGroup.Done()
337-
if err := ctx.Watch(api.WatchOptions{}); err != nil {
337+
var options api.WatchOptions
338+
if value, ok := request["delay"]; ok {
339+
options.Delay = value.(int)
340+
}
341+
if err := ctx.Watch(options); err != nil {
338342
service.sendPacket(encodeErrorPacket(p.id, err))
339343
} else {
340344
service.sendPacket(encodePacket(packet{

lib/shared/common.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1060,11 +1060,13 @@ function buildOrContextImpl(
10601060
watch: (options = {}) => new Promise((resolve, reject) => {
10611061
if (!streamIn.hasFS) throw new Error(`Cannot use the "watch" API in this environment`)
10621062
const keys: OptionKeys = {}
1063+
const delay = getFlag(options, keys, 'delay', mustBeInteger)
10631064
checkForInvalidFlags(options, keys, `in watch() call`)
10641065
const request: protocol.WatchRequest = {
10651066
command: 'watch',
10661067
key: buildKey,
10671068
}
1069+
if (delay) request.delay = delay
10681070
sendRequest<protocol.WatchRequest, null>(refs, request, error => {
10691071
if (error) reject(new Error(error))
10701072
else resolve(undefined)

lib/shared/stdio_protocol.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ export interface CancelRequest {
9898
export interface WatchRequest {
9999
command: 'watch'
100100
key: number
101+
delay?: number
101102
}
102103

103104
export interface OnServeRequest {

lib/shared/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -513,7 +513,9 @@ export interface AnalyzeMetafileOptions {
513513
verbose?: boolean
514514
}
515515

516+
/** Documentation: https://esbuild.github.io/api/#watch-arguments */
516517
export interface WatchOptions {
518+
delay?: number // In milliseconds
517519
}
518520

519521
export interface BuildContext<ProvidedOptions extends BuildOptions = BuildOptions> {

pkg/api/api.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -501,7 +501,9 @@ type ServeResult struct {
501501
Hosts []string
502502
}
503503

504+
// Documentation: https://esbuild.github.io/api/#watch-arguments
504505
type WatchOptions struct {
506+
Delay int // In milliseconds
505507
}
506508

507509
type BuildContext interface {

pkg/api/api_impl.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1081,6 +1081,7 @@ func (ctx *internalContext) Watch(options WatchOptions) error {
10811081
rebuild: func() fs.WatchData {
10821082
return ctx.rebuild().watchData
10831083
},
1084+
delayInMS: time.Duration(options.Delay),
10841085
}
10851086

10861087
// All subsequent builds will be watch mode builds

pkg/api/watcher.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ type watcher struct {
5151
data fs.WatchData
5252
fs fs.FS
5353
rebuild func() fs.WatchData
54+
delayInMS time.Duration
5455
recentItems []string
5556
itemsToScan []string
5657
mutex sync.Mutex
@@ -68,7 +69,11 @@ func (w *watcher) setWatchData(data fs.WatchData) {
6869
// Print something for the end of the first build
6970
if w.shouldLog && w.data.Paths == nil {
7071
logger.PrintTextWithColor(os.Stderr, w.useColor, func(colors logger.Colors) string {
71-
return fmt.Sprintf("%s[watch] build finished, watching for changes...%s\n", colors.Dim, colors.Reset)
72+
var delay string
73+
if w.delayInMS > 0 {
74+
delay = fmt.Sprintf(" with a %dms delay", w.delayInMS)
75+
}
76+
return fmt.Sprintf("%s[watch] build finished, watching for changes%s...%s\n", colors.Dim, delay, colors.Reset)
7277
})
7378
}
7479

@@ -100,6 +105,11 @@ func (w *watcher) start() {
100105

101106
// Rebuild if we're dirty
102107
if absPath := w.tryToFindDirtyPath(); absPath != "" {
108+
// Optionally wait before rebuilding
109+
if w.delayInMS > 0 {
110+
time.Sleep(w.delayInMS * time.Millisecond)
111+
}
112+
103113
if w.shouldLog {
104114
logger.PrintTextWithColor(os.Stderr, w.useColor, func(colors logger.Colors) string {
105115
prettyPath := resolver.PrettyPath(w.fs, logger.Path{Text: absPath, Namespace: "file"})

pkg/cli/cli_impl.go

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ const (
5151

5252
type parseOptionsExtras struct {
5353
watch bool
54+
watchDelay int
5455
metafile *string
5556
mangleCache *string
5657
}
@@ -127,6 +128,17 @@ func parseOptionsImpl(
127128
extras.watch = value
128129
}
129130

131+
case strings.HasPrefix(arg, "--watch-delay=") && buildOpts != nil:
132+
value := arg[len("--watch-delay="):]
133+
delay, err := strconv.Atoi(value)
134+
if err != nil {
135+
return parseOptionsExtras{}, cli_helpers.MakeErrorWithNote(
136+
fmt.Sprintf("Invalid value %q in %q", value, arg),
137+
"The watch delay must be an integer.",
138+
)
139+
}
140+
extras.watchDelay = delay
141+
130142
case isBoolFlag(arg, "--minify"):
131143
if value, err := parseBoolFlag(arg, true); err != nil {
132144
return parseOptionsExtras{}, err
@@ -893,6 +905,7 @@ func parseOptionsImpl(
893905
"tsconfig-raw": true,
894906
"tsconfig": true,
895907
"watch": true,
908+
"watch-delay": true,
896909
}
897910

898911
colon := map[string]bool{
@@ -1329,7 +1342,9 @@ func runImpl(osArgs []string, plugins []api.Plugin) int {
13291342
return 1
13301343
}
13311344

1332-
ctx.Watch(api.WatchOptions{})
1345+
ctx.Watch(api.WatchOptions{
1346+
Delay: extras.watchDelay,
1347+
})
13331348

13341349
// Do not exit if we're in watch mode
13351350
<-make(chan struct{})

0 commit comments

Comments
 (0)