@@ -30,6 +30,7 @@ export async function* runEsBuildBuildAction(
30
30
progress ?: boolean ;
31
31
deleteOutputPath ?: boolean ;
32
32
poll ?: number ;
33
+ signal ?: AbortSignal ;
33
34
} ,
34
35
) : AsyncIterable < ( ExecutionResult [ 'outputWithFiles' ] | ExecutionResult [ 'output' ] ) & BuilderOutput > {
35
36
const {
@@ -75,75 +76,89 @@ export async function* runEsBuildBuildAction(
75
76
let result : ExecutionResult ;
76
77
try {
77
78
result = await withProgress ( 'Building...' , ( ) => action ( ) ) ;
78
-
79
- if ( writeToFileSystem ) {
80
- // Write output files
81
- await writeResultFiles ( result . outputFiles , result . assetFiles , outputPath ) ;
82
-
83
- yield result . output ;
84
- } else {
85
- // Requires casting due to unneeded `JsonObject` requirement. Remove once fixed.
86
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
87
- yield result . outputWithFiles as any ;
88
- }
89
-
90
- // Finish if watch mode is not enabled
91
- if ( ! watch ) {
92
- return ;
93
- }
94
79
} finally {
95
80
// Ensure Sass workers are shutdown if not watching
96
81
if ( ! watch ) {
97
82
shutdownSassWorkerPool ( ) ;
98
83
}
99
84
}
100
85
101
- if ( progress ) {
102
- logger . info ( 'Watch mode enabled. Watching for file changes...' ) ;
86
+ // Setup watcher if watch mode enabled
87
+ let watcher : import ( '../../tools/esbuild/watcher' ) . BuildWatcher | undefined ;
88
+ if ( watch ) {
89
+ if ( progress ) {
90
+ logger . info ( 'Watch mode enabled. Watching for file changes...' ) ;
91
+ }
92
+
93
+ // Setup a watcher
94
+ const { createWatcher } = await import ( '../../tools/esbuild/watcher' ) ;
95
+ watcher = createWatcher ( {
96
+ polling : typeof poll === 'number' ,
97
+ interval : poll ,
98
+ ignored : [
99
+ // Ignore the output and cache paths to avoid infinite rebuild cycles
100
+ outputPath ,
101
+ cacheOptions . basePath ,
102
+ // Ignore all node modules directories to avoid excessive file watchers.
103
+ // Package changes are handled below by watching manifest and lock files.
104
+ '**/node_modules/**' ,
105
+ '**/.*/**' ,
106
+ ] ,
107
+ } ) ;
108
+
109
+ // Setup abort support
110
+ options . signal ?. addEventListener ( 'abort' , ( ) => void watcher ?. close ( ) ) ;
111
+
112
+ // Temporarily watch the entire project
113
+ watcher . add ( projectRoot ) ;
114
+
115
+ // Watch workspace for package manager changes
116
+ const packageWatchFiles = [
117
+ // manifest can affect module resolution
118
+ 'package.json' ,
119
+ // npm lock file
120
+ 'package-lock.json' ,
121
+ // pnpm lock file
122
+ 'pnpm-lock.yaml' ,
123
+ // yarn lock file including Yarn PnP manifest files (https://yarnpkg.com/advanced/pnp-spec/)
124
+ 'yarn.lock' ,
125
+ '.pnp.cjs' ,
126
+ '.pnp.data.json' ,
127
+ ] ;
128
+
129
+ watcher . add ( packageWatchFiles . map ( ( file ) => path . join ( workspaceRoot , file ) ) ) ;
130
+
131
+ // Watch locations provided by the initial build result
132
+ watcher . add ( result . watchFiles ) ;
103
133
}
104
134
105
- // Setup a watcher
106
- const { createWatcher } = await import ( '../../tools/esbuild/watcher' ) ;
107
- const watcher = createWatcher ( {
108
- polling : typeof poll === 'number' ,
109
- interval : poll ,
110
- ignored : [
111
- // Ignore the output and cache paths to avoid infinite rebuild cycles
112
- outputPath ,
113
- cacheOptions . basePath ,
114
- // Ignore all node modules directories to avoid excessive file watchers.
115
- // Package changes are handled below by watching manifest and lock files.
116
- '**/node_modules/**' ,
117
- '**/.*/**' ,
118
- ] ,
119
- } ) ;
120
-
121
- // Temporarily watch the entire project
122
- watcher . add ( projectRoot ) ;
123
-
124
- // Watch workspace for package manager changes
125
- const packageWatchFiles = [
126
- // manifest can affect module resolution
127
- 'package.json' ,
128
- // npm lock file
129
- 'package-lock.json' ,
130
- // pnpm lock file
131
- 'pnpm-lock.yaml' ,
132
- // yarn lock file including Yarn PnP manifest files (https://yarnpkg.com/advanced/pnp-spec/)
133
- 'yarn.lock' ,
134
- '.pnp.cjs' ,
135
- '.pnp.data.json' ,
136
- ] ;
137
-
138
- watcher . add ( packageWatchFiles . map ( ( file ) => path . join ( workspaceRoot , file ) ) ) ;
139
-
140
- // Watch locations provided by the initial build result
141
- let previousWatchFiles = new Set ( result . watchFiles ) ;
142
- watcher . add ( result . watchFiles ) ;
135
+ // Output the first build results after setting up the watcher to ensure that any code executed
136
+ // higher in the iterator call stack will trigger the watcher. This is particularly relevant for
137
+ // unit tests which execute the builder and modify the file system programmatically.
138
+ if ( writeToFileSystem ) {
139
+ // Write output files
140
+ await writeResultFiles ( result . outputFiles , result . assetFiles , outputPath ) ;
141
+
142
+ yield result . output ;
143
+ } else {
144
+ // Requires casting due to unneeded `JsonObject` requirement. Remove once fixed.
145
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
146
+ yield result . outputWithFiles as any ;
147
+ }
148
+
149
+ // Finish if watch mode is not enabled
150
+ if ( ! watcher ) {
151
+ return ;
152
+ }
143
153
144
154
// Wait for changes and rebuild as needed
155
+ let previousWatchFiles = new Set ( result . watchFiles ) ;
145
156
try {
146
157
for await ( const changes of watcher ) {
158
+ if ( options . signal ?. aborted ) {
159
+ break ;
160
+ }
161
+
147
162
if ( verbose ) {
148
163
logger . info ( changes . toDebugString ( ) ) ;
149
164
}
0 commit comments