@@ -42,16 +42,30 @@ interface RebuildState {
42
42
* Represents the result of a single builder execute call.
43
43
*/
44
44
class ExecutionResult {
45
+ readonly outputFiles : OutputFile [ ] = [ ] ;
46
+ readonly assetFiles : { source : string ; destination : string } [ ] = [ ] ;
47
+
45
48
constructor (
46
- private success : boolean ,
47
49
private codeRebuild ?: BundlerContext ,
48
50
private globalStylesRebuild ?: BundlerContext ,
49
51
private codeBundleCache ?: SourceFileCache ,
50
52
) { }
51
53
54
+ addOutputFile ( path : string , content : string ) : void {
55
+ this . outputFiles . push ( createOutputFileFromText ( path , content ) ) ;
56
+ }
57
+
52
58
get output ( ) {
53
59
return {
54
- success : this . success ,
60
+ success : this . outputFiles . length > 0 ,
61
+ } ;
62
+ }
63
+
64
+ get outputWithFiles ( ) {
65
+ return {
66
+ success : this . outputFiles . length > 0 ,
67
+ outputFiles : this . outputFiles ,
68
+ assetFiles : this . assetFiles ,
55
69
} ;
56
70
}
57
71
@@ -67,7 +81,7 @@ class ExecutionResult {
67
81
}
68
82
69
83
async dispose ( ) : Promise < void > {
70
- await Promise . all ( [ this . codeRebuild ?. dispose ( ) , this . globalStylesRebuild ?. dispose ( ) ] ) ;
84
+ await Promise . allSettled ( [ this . codeRebuild ?. dispose ( ) , this . globalStylesRebuild ?. dispose ( ) ] ) ;
71
85
}
72
86
}
73
87
@@ -82,7 +96,6 @@ async function execute(
82
96
projectRoot,
83
97
workspaceRoot,
84
98
optimizationOptions,
85
- outputPath,
86
99
assets,
87
100
serviceWorkerOptions,
88
101
indexHtmlOptions,
@@ -123,24 +136,15 @@ async function execute(
123
136
warnings : [ ...codeResults . warnings , ...styleResults . warnings ] ,
124
137
} ) ;
125
138
126
- // Return if the bundling failed to generate output files or there are errors
127
- if ( codeResults . errors ) {
128
- return new ExecutionResult (
129
- false ,
130
- codeBundleContext ,
131
- globalStylesBundleContext ,
132
- codeBundleCache ,
133
- ) ;
134
- }
139
+ const executionResult = new ExecutionResult (
140
+ codeBundleContext ,
141
+ globalStylesBundleContext ,
142
+ codeBundleCache ,
143
+ ) ;
135
144
136
- // Return if the global stylesheet bundling has errors
137
- if ( styleResults . errors ) {
138
- return new ExecutionResult (
139
- false ,
140
- codeBundleContext ,
141
- globalStylesBundleContext ,
142
- codeBundleCache ,
143
- ) ;
145
+ // Return if the bundling has errors
146
+ if ( codeResults . errors || styleResults . errors ) {
147
+ return executionResult ;
144
148
}
145
149
146
150
// Filter global stylesheet initial files
@@ -150,7 +154,7 @@ async function execute(
150
154
151
155
// Combine the bundling output files
152
156
const initialFiles : FileInfo [ ] = [ ...codeResults . initialFiles , ...styleResults . initialFiles ] ;
153
- const outputFiles : OutputFile [ ] = [ ... codeResults . outputFiles , ...styleResults . outputFiles ] ;
157
+ executionResult . outputFiles . push ( ... codeResults . outputFiles , ...styleResults . outputFiles ) ;
154
158
155
159
// Combine metafiles used for the stats option as well as bundle budgets and console output
156
160
const metafile = {
@@ -180,7 +184,7 @@ async function execute(
180
184
indexHtmlGenerator . readAsset = async function ( filePath : string ) : Promise < string > {
181
185
// Remove leading directory separator
182
186
const relativefilePath = path . relative ( virtualOutputPath , filePath ) ;
183
- const file = outputFiles . find ( ( file ) => file . path === relativefilePath ) ;
187
+ const file = executionResult . outputFiles . find ( ( file ) => file . path === relativefilePath ) ;
184
188
if ( file ) {
185
189
return file . text ;
186
190
}
@@ -202,29 +206,26 @@ async function execute(
202
206
context . logger . warn ( warning ) ;
203
207
}
204
208
205
- outputFiles . push ( createOutputFileFromText ( indexHtmlOptions . output , content ) ) ;
209
+ executionResult . addOutputFile ( indexHtmlOptions . output , content ) ;
206
210
}
207
211
208
212
// Copy assets
209
- let assetFiles ;
210
213
if ( assets ) {
211
214
// The webpack copy assets helper is used with no base paths defined. This prevents the helper
212
215
// from directly writing to disk. This should eventually be replaced with a more optimized helper.
213
- assetFiles = await copyAssets ( assets , [ ] , workspaceRoot ) ;
216
+ executionResult . assetFiles . push ( ... ( await copyAssets ( assets , [ ] , workspaceRoot ) ) ) ;
214
217
}
215
218
216
219
// Write metafile if stats option is enabled
217
220
if ( options . stats ) {
218
- outputFiles . push ( createOutputFileFromText ( 'stats.json' , JSON . stringify ( metafile , null , 2 ) ) ) ;
221
+ executionResult . addOutputFile ( 'stats.json' , JSON . stringify ( metafile , null , 2 ) ) ;
219
222
}
220
223
221
224
// Extract and write licenses for used packages
222
225
if ( options . extractLicenses ) {
223
- outputFiles . push (
224
- createOutputFileFromText (
225
- '3rdpartylicenses.txt' ,
226
- await extractLicenses ( metafile , workspaceRoot ) ,
227
- ) ,
226
+ executionResult . addOutputFile (
227
+ '3rdpartylicenses.txt' ,
228
+ await extractLicenses ( metafile , workspaceRoot ) ,
228
229
) ;
229
230
}
230
231
@@ -235,31 +236,22 @@ async function execute(
235
236
workspaceRoot ,
236
237
serviceWorkerOptions ,
237
238
options . baseHref || '/' ,
238
- outputFiles ,
239
- assetFiles || [ ] ,
239
+ executionResult . outputFiles ,
240
+ executionResult . assetFiles ,
240
241
) ;
241
- outputFiles . push ( createOutputFileFromText ( 'ngsw.json' , serviceWorkerResult . manifest ) ) ;
242
- assetFiles ??= [ ] ;
243
- assetFiles . push ( ...serviceWorkerResult . assetFiles ) ;
242
+ executionResult . addOutputFile ( 'ngsw.json' , serviceWorkerResult . manifest ) ;
243
+ executionResult . assetFiles . push ( ...serviceWorkerResult . assetFiles ) ;
244
244
} catch ( error ) {
245
245
context . logger . error ( error instanceof Error ? error . message : `${ error } ` ) ;
246
246
247
- return new ExecutionResult (
248
- false ,
249
- codeBundleContext ,
250
- globalStylesBundleContext ,
251
- codeBundleCache ,
252
- ) ;
247
+ return executionResult ;
253
248
}
254
249
}
255
250
256
- // Write output files
257
- await writeResultFiles ( outputFiles , assetFiles , outputPath ) ;
258
-
259
251
const buildTime = Number ( process . hrtime . bigint ( ) - startTime ) / 10 ** 9 ;
260
252
context . logger . info ( `Complete. [${ buildTime . toFixed ( 3 ) } seconds]` ) ;
261
253
262
- return new ExecutionResult ( true , codeBundleContext , globalStylesBundleContext , codeBundleCache ) ;
254
+ return executionResult ;
263
255
}
264
256
265
257
async function writeResultFiles (
@@ -521,16 +513,19 @@ function createGlobalStylesBundleOptions(
521
513
/**
522
514
* Main execution function for the esbuild-based application builder.
523
515
* The options are compatible with the Webpack-based builder.
524
- * @param initialOptions The browser builder options to use when setting up the application build
516
+ * @param userOptions The browser builder options to use when setting up the application build
525
517
* @param context The Architect builder context object
526
518
* @returns An async iterable with the builder result output
527
519
*/
528
520
export async function * buildEsbuildBrowser (
529
- initialOptions : BrowserBuilderOptions ,
521
+ userOptions : BrowserBuilderOptions ,
530
522
context : BuilderContext ,
531
- ) : AsyncIterable < BuilderOutput > {
523
+ infrastructureSettings ?: {
524
+ write ?: boolean ;
525
+ } ,
526
+ ) : AsyncIterable < BuilderOutput & { outputFiles ?: OutputFile [ ] } > {
532
527
// Inform user of experimental status of builder and options
533
- logExperimentalWarnings ( initialOptions , context ) ;
528
+ logExperimentalWarnings ( userOptions , context ) ;
534
529
535
530
// Determine project name from builder context target
536
531
const projectName = context . target ?. project ;
@@ -540,36 +535,50 @@ export async function* buildEsbuildBrowser(
540
535
return ;
541
536
}
542
537
543
- const normalizedOptions = await normalizeOptions ( context , projectName , initialOptions ) ;
538
+ const normalizedOptions = await normalizeOptions ( context , projectName , userOptions ) ;
539
+ // Writing the result to the filesystem is the default behavior
540
+ const shouldWriteResult = infrastructureSettings ?. write !== false ;
544
541
545
- // Clean output path if enabled
546
- if ( initialOptions . deleteOutputPath ) {
547
- deleteOutputDir ( normalizedOptions . workspaceRoot , initialOptions . outputPath ) ;
548
- }
542
+ if ( shouldWriteResult ) {
543
+ // Clean output path if enabled
544
+ if ( userOptions . deleteOutputPath ) {
545
+ deleteOutputDir ( normalizedOptions . workspaceRoot , userOptions . outputPath ) ;
546
+ }
549
547
550
- // Create output directory if needed
551
- try {
552
- await fs . mkdir ( normalizedOptions . outputPath , { recursive : true } ) ;
553
- } catch ( e ) {
554
- assertIsError ( e ) ;
555
- context . logger . error ( 'Unable to create output directory: ' + e . message ) ;
548
+ // Create output directory if needed
549
+ try {
550
+ await fs . mkdir ( normalizedOptions . outputPath , { recursive : true } ) ;
551
+ } catch ( e ) {
552
+ assertIsError ( e ) ;
553
+ context . logger . error ( 'Unable to create output directory: ' + e . message ) ;
556
554
557
- return ;
555
+ return ;
556
+ }
558
557
}
559
558
560
559
// Initial build
561
560
let result : ExecutionResult ;
562
561
try {
563
562
result = await execute ( normalizedOptions , context ) ;
564
- yield result . output ;
563
+
564
+ if ( shouldWriteResult ) {
565
+ // Write output files
566
+ await writeResultFiles ( result . outputFiles , result . assetFiles , normalizedOptions . outputPath ) ;
567
+
568
+ yield result . output ;
569
+ } else {
570
+ // Requires casting due to unneeded `JsonObject` requirement. Remove once fixed.
571
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
572
+ yield result . outputWithFiles as any ;
573
+ }
565
574
566
575
// Finish if watch mode is not enabled
567
- if ( ! initialOptions . watch ) {
576
+ if ( ! userOptions . watch ) {
568
577
return ;
569
578
}
570
579
} finally {
571
580
// Ensure Sass workers are shutdown if not watching
572
- if ( ! initialOptions . watch ) {
581
+ if ( ! userOptions . watch ) {
573
582
shutdownSassWorkerPool ( ) ;
574
583
}
575
584
}
@@ -578,8 +587,8 @@ export async function* buildEsbuildBrowser(
578
587
579
588
// Setup a watcher
580
589
const watcher = createWatcher ( {
581
- polling : typeof initialOptions . poll === 'number' ,
582
- interval : initialOptions . poll ,
590
+ polling : typeof userOptions . poll === 'number' ,
591
+ interval : userOptions . poll ,
583
592
// Ignore the output and cache paths to avoid infinite rebuild cycles
584
593
ignored : [ normalizedOptions . outputPath , normalizedOptions . cacheOptions . basePath ] ,
585
594
} ) ;
@@ -598,12 +607,22 @@ export async function* buildEsbuildBrowser(
598
607
for await ( const changes of watcher ) {
599
608
context . logger . info ( 'Changes detected. Rebuilding...' ) ;
600
609
601
- if ( initialOptions . verbose ) {
610
+ if ( userOptions . verbose ) {
602
611
context . logger . info ( changes . toDebugString ( ) ) ;
603
612
}
604
613
605
614
result = await execute ( normalizedOptions , context , result . createRebuildState ( changes ) ) ;
606
- yield result . output ;
615
+
616
+ if ( shouldWriteResult ) {
617
+ // Write output files
618
+ await writeResultFiles ( result . outputFiles , result . assetFiles , normalizedOptions . outputPath ) ;
619
+
620
+ yield result . output ;
621
+ } else {
622
+ // Requires casting due to unneeded `JsonObject` requirement. Remove once fixed.
623
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
624
+ yield result . outputWithFiles as any ;
625
+ }
607
626
}
608
627
} finally {
609
628
// Stop the watcher
0 commit comments