8
8
9
9
import { BuilderContext , BuilderOutput , createBuilder } from '@angular-devkit/architect' ;
10
10
import * as assert from 'assert' ;
11
- import type { Message , OutputFile } from 'esbuild' ;
11
+ import type { BuildInvalidate , BuildOptions , Message , OutputFile } from 'esbuild' ;
12
12
import * as fs from 'fs/promises' ;
13
13
import * as path from 'path' ;
14
14
import { deleteOutputDir } from '../../utils' ;
@@ -19,18 +19,56 @@ import { FileInfo } from '../../utils/index-file/augment-index-html';
19
19
import { IndexHtmlGenerator } from '../../utils/index-file/index-html-generator' ;
20
20
import { augmentAppWithServiceWorkerEsbuild } from '../../utils/service-worker' ;
21
21
import { getSupportedBrowsers } from '../../utils/supported-browsers' ;
22
- import { createCompilerPlugin } from './compiler-plugin' ;
22
+ import { SourceFileCache , createCompilerPlugin } from './compiler-plugin' ;
23
23
import { bundle , logMessages } from './esbuild' ;
24
24
import { logExperimentalWarnings } from './experimental-warnings' ;
25
25
import { NormalizedBrowserOptions , normalizeOptions } from './options' ;
26
26
import { Schema as BrowserBuilderOptions } from './schema' ;
27
27
import { bundleStylesheetText } from './stylesheets' ;
28
- import { createWatcher } from './watcher' ;
28
+ import { ChangedFiles , createWatcher } from './watcher' ;
29
+
30
+ interface RebuildState {
31
+ codeRebuild ?: BuildInvalidate ;
32
+ codeBundleCache ?: SourceFileCache ;
33
+ fileChanges : ChangedFiles ;
34
+ }
35
+
36
+ /**
37
+ * Represents the result of a single builder execute call.
38
+ */
39
+ class ExecutionResult {
40
+ constructor (
41
+ private success : boolean ,
42
+ private codeRebuild ?: BuildInvalidate ,
43
+ private codeBundleCache ?: SourceFileCache ,
44
+ ) { }
45
+
46
+ get output ( ) {
47
+ return {
48
+ success : this . success ,
49
+ } ;
50
+ }
51
+
52
+ createRebuildState ( fileChanges : ChangedFiles ) : RebuildState {
53
+ this . codeBundleCache ?. invalidate ( [ ...fileChanges . modified , ...fileChanges . removed ] ) ;
54
+
55
+ return {
56
+ codeRebuild : this . codeRebuild ,
57
+ codeBundleCache : this . codeBundleCache ,
58
+ fileChanges,
59
+ } ;
60
+ }
61
+
62
+ dispose ( ) : void {
63
+ this . codeRebuild ?. dispose ( ) ;
64
+ }
65
+ }
29
66
30
67
async function execute (
31
68
options : NormalizedBrowserOptions ,
32
69
context : BuilderContext ,
33
- ) : Promise < BuilderOutput > {
70
+ rebuildState ?: RebuildState ,
71
+ ) : Promise < ExecutionResult > {
34
72
const startTime = Date . now ( ) ;
35
73
36
74
const {
@@ -47,9 +85,13 @@ async function execute(
47
85
getSupportedBrowsers ( projectRoot , context . logger ) ,
48
86
) ;
49
87
88
+ const codeBundleCache = options . watch
89
+ ? rebuildState ?. codeBundleCache ?? new SourceFileCache ( )
90
+ : undefined ;
91
+
50
92
const [ codeResults , styleResults ] = await Promise . all ( [
51
93
// Execute esbuild to bundle the application code
52
- bundleCode ( options , target ) ,
94
+ bundle ( rebuildState ?. codeRebuild ?? createCodeBundleOptions ( options , target , codeBundleCache ) ) ,
53
95
// Execute esbuild to bundle the global stylesheets
54
96
bundleGlobalStylesheets ( options , target ) ,
55
97
] ) ;
@@ -62,7 +104,7 @@ async function execute(
62
104
63
105
// Return if the bundling failed to generate output files or there are errors
64
106
if ( ! codeResults . outputFiles || codeResults . errors . length ) {
65
- return { success : false } ;
107
+ return new ExecutionResult ( false , rebuildState ?. codeRebuild , codeBundleCache ) ;
66
108
}
67
109
68
110
// Structure the code bundling output files
@@ -93,7 +135,7 @@ async function execute(
93
135
94
136
// Return if the global stylesheet bundling has errors
95
137
if ( styleResults . errors . length ) {
96
- return { success : false } ;
138
+ return new ExecutionResult ( false , codeResults . rebuild , codeBundleCache ) ;
97
139
}
98
140
99
141
// Generate index HTML file
@@ -160,13 +202,13 @@ async function execute(
160
202
} catch ( error ) {
161
203
context . logger . error ( error instanceof Error ? error . message : `${ error } ` ) ;
162
204
163
- return { success : false } ;
205
+ return new ExecutionResult ( false , codeResults . rebuild , codeBundleCache ) ;
164
206
}
165
207
}
166
208
167
209
context . logger . info ( `Complete. [${ ( Date . now ( ) - startTime ) / 1000 } seconds]` ) ;
168
210
169
- return { success : true } ;
211
+ return new ExecutionResult ( true , codeResults . rebuild , codeBundleCache ) ;
170
212
}
171
213
172
214
function createOutputFileFromText ( path : string , text : string ) : OutputFile {
@@ -179,7 +221,11 @@ function createOutputFileFromText(path: string, text: string): OutputFile {
179
221
} ;
180
222
}
181
223
182
- async function bundleCode ( options : NormalizedBrowserOptions , target : string [ ] ) {
224
+ function createCodeBundleOptions (
225
+ options : NormalizedBrowserOptions ,
226
+ target : string [ ] ,
227
+ sourceFileCache ?: SourceFileCache ,
228
+ ) : BuildOptions {
183
229
const {
184
230
workspaceRoot,
185
231
entryPoints,
@@ -194,9 +240,10 @@ async function bundleCode(options: NormalizedBrowserOptions, target: string[]) {
194
240
advancedOptimizations,
195
241
} = options ;
196
242
197
- return bundle ( {
243
+ return {
198
244
absWorkingDir : workspaceRoot ,
199
245
bundle : true ,
246
+ incremental : options . watch ,
200
247
format : 'esm' ,
201
248
entryPoints,
202
249
entryNames : outputNames . bundles ,
@@ -234,6 +281,7 @@ async function bundleCode(options: NormalizedBrowserOptions, target: string[]) {
234
281
tsconfig,
235
282
advancedOptimizations,
236
283
fileReplacements,
284
+ sourceFileCache,
237
285
} ,
238
286
// Component stylesheet options
239
287
{
@@ -255,7 +303,7 @@ async function bundleCode(options: NormalizedBrowserOptions, target: string[]) {
255
303
...( optimizationOptions . scripts ? { 'ngDevMode' : 'false' } : undefined ) ,
256
304
'ngJitMode' : 'false' ,
257
305
} ,
258
- } ) ;
306
+ } ;
259
307
}
260
308
261
309
async function bundleGlobalStylesheets ( options : NormalizedBrowserOptions , target : string [ ] ) {
@@ -383,13 +431,16 @@ export async function* buildEsbuildBrowser(
383
431
}
384
432
385
433
// Initial build
386
- yield await execute ( normalizedOptions , context ) ;
434
+ let result = await execute ( normalizedOptions , context ) ;
435
+ yield result . output ;
387
436
388
437
// Finish if watch mode is not enabled
389
438
if ( ! initialOptions . watch ) {
390
439
return ;
391
440
}
392
441
442
+ context . logger . info ( 'Watch mode enabled. Watching for file changes...' ) ;
443
+
393
444
// Setup a watcher
394
445
const watcher = createWatcher ( {
395
446
polling : typeof initialOptions . poll === 'number' ,
@@ -416,10 +467,14 @@ export async function* buildEsbuildBrowser(
416
467
context . logger . info ( changes . toDebugString ( ) ) ;
417
468
}
418
469
419
- yield await execute ( normalizedOptions , context ) ;
470
+ result = await execute ( normalizedOptions , context , result . createRebuildState ( changes ) ) ;
471
+ yield result . output ;
420
472
}
421
473
} finally {
474
+ // Stop the watcher
422
475
await watcher . close ( ) ;
476
+ // Cleanup incremental rebuild state
477
+ result . dispose ( ) ;
423
478
}
424
479
}
425
480
0 commit comments