7
7
*/
8
8
9
9
import { BuilderContext , BuilderOutput , createBuilder } from '@angular-devkit/architect' ;
10
- import * as assert from 'assert ' ;
11
- import type { BuildInvalidate , BuildOptions , Message , OutputFile } from 'esbuild ' ;
12
- import * as fs from 'fs/promises' ;
13
- import * as path from 'path' ;
10
+ import type { BuildInvalidate , BuildOptions , OutputFile } from 'esbuild ' ;
11
+ import assert from 'node:assert ' ;
12
+ import * as fs from 'node: fs/promises' ;
13
+ import * as path from 'node: path' ;
14
14
import { deleteOutputDir } from '../../utils' ;
15
15
import { copyAssets } from '../../utils/copy-assets' ;
16
16
import { assertIsError } from '../../utils/error' ;
@@ -25,11 +25,12 @@ import { logExperimentalWarnings } from './experimental-warnings';
25
25
import { NormalizedBrowserOptions , normalizeOptions } from './options' ;
26
26
import { shutdownSassWorkerPool } from './sass-plugin' ;
27
27
import { Schema as BrowserBuilderOptions } from './schema' ;
28
- import { bundleStylesheetText } from './stylesheets' ;
28
+ import { createStylesheetBundleOptions } from './stylesheets' ;
29
29
import { ChangedFiles , createWatcher } from './watcher' ;
30
30
31
31
interface RebuildState {
32
32
codeRebuild ?: BuildInvalidate ;
33
+ globalStylesRebuild ?: BuildInvalidate ;
33
34
codeBundleCache ?: SourceFileCache ;
34
35
fileChanges : ChangedFiles ;
35
36
}
@@ -41,6 +42,7 @@ class ExecutionResult {
41
42
constructor (
42
43
private success : boolean ,
43
44
private codeRebuild ?: BuildInvalidate ,
45
+ private globalStylesRebuild ?: BuildInvalidate ,
44
46
private codeBundleCache ?: SourceFileCache ,
45
47
) { }
46
48
@@ -55,6 +57,7 @@ class ExecutionResult {
55
57
56
58
return {
57
59
codeRebuild : this . codeRebuild ,
60
+ globalStylesRebuild : this . globalStylesRebuild ,
58
61
codeBundleCache : this . codeBundleCache ,
59
62
fileChanges,
60
63
} ;
@@ -97,7 +100,10 @@ async function execute(
97
100
rebuildState ?. codeRebuild ?? createCodeBundleOptions ( options , target , codeBundleCache ) ,
98
101
) ,
99
102
// Execute esbuild to bundle the global stylesheets
100
- bundleGlobalStylesheets ( options , target ) ,
103
+ bundle (
104
+ workspaceRoot ,
105
+ rebuildState ?. globalStylesRebuild ?? createGlobalStylesBundleOptions ( options , target ) ,
106
+ ) ,
101
107
] ) ;
102
108
103
109
// Log all warnings and errors generated during bundling
@@ -108,18 +114,33 @@ async function execute(
108
114
109
115
// Return if the bundling failed to generate output files or there are errors
110
116
if ( ! codeResults . outputFiles || codeResults . errors . length ) {
111
- return new ExecutionResult ( false , rebuildState ?. codeRebuild , codeBundleCache ) ;
117
+ return new ExecutionResult (
118
+ false ,
119
+ rebuildState ?. codeRebuild ,
120
+ ( styleResults . outputFiles && styleResults . rebuild ) ?? rebuildState ?. globalStylesRebuild ,
121
+ codeBundleCache ,
122
+ ) ;
123
+ }
124
+
125
+ // Return if the global stylesheet bundling has errors
126
+ if ( ! styleResults . outputFiles || styleResults . errors . length ) {
127
+ return new ExecutionResult (
128
+ false ,
129
+ codeResults . rebuild ,
130
+ rebuildState ?. globalStylesRebuild ,
131
+ codeBundleCache ,
132
+ ) ;
112
133
}
113
134
135
+ // Filter global stylesheet initial files
136
+ styleResults . initialFiles = styleResults . initialFiles . filter (
137
+ ( { name } ) => options . globalStyles . find ( ( style ) => style . name === name ) ?. initial ,
138
+ ) ;
139
+
114
140
// Combine the bundling output files
115
141
const initialFiles : FileInfo [ ] = [ ...codeResults . initialFiles , ...styleResults . initialFiles ] ;
116
142
const outputFiles : OutputFile [ ] = [ ...codeResults . outputFiles , ...styleResults . outputFiles ] ;
117
143
118
- // Return if the global stylesheet bundling has errors
119
- if ( styleResults . errors . length ) {
120
- return new ExecutionResult ( false , codeResults . rebuild , codeBundleCache ) ;
121
- }
122
-
123
144
// Generate index HTML file
124
145
if ( indexHtmlOptions ) {
125
146
// Create an index HTML generator that reads from the in-memory output files
@@ -184,14 +205,14 @@ async function execute(
184
205
} catch ( error ) {
185
206
context . logger . error ( error instanceof Error ? error . message : `${ error } ` ) ;
186
207
187
- return new ExecutionResult ( false , codeResults . rebuild , codeBundleCache ) ;
208
+ return new ExecutionResult ( false , codeResults . rebuild , styleResults . rebuild , codeBundleCache ) ;
188
209
}
189
210
}
190
211
191
212
const buildTime = Number ( process . hrtime . bigint ( ) - startTime ) / 10 ** 9 ;
192
213
context . logger . info ( `Complete. [${ buildTime . toFixed ( 3 ) } seconds]` ) ;
193
214
194
- return new ExecutionResult ( true , codeResults . rebuild , codeBundleCache ) ;
215
+ return new ExecutionResult ( true , codeResults . rebuild , styleResults . rebuild , codeBundleCache ) ;
195
216
}
196
217
197
218
function createOutputFileFromText ( path : string , text : string ) : OutputFile {
@@ -293,7 +314,10 @@ function createCodeBundleOptions(
293
314
} ;
294
315
}
295
316
296
- async function bundleGlobalStylesheets ( options : NormalizedBrowserOptions , target : string [ ] ) {
317
+ function createGlobalStylesBundleOptions (
318
+ options : NormalizedBrowserOptions ,
319
+ target : string [ ] ,
320
+ ) : BuildOptions {
297
321
const {
298
322
workspaceRoot,
299
323
optimizationOptions,
@@ -303,70 +327,54 @@ async function bundleGlobalStylesheets(options: NormalizedBrowserOptions, target
303
327
preserveSymlinks,
304
328
externalDependencies,
305
329
stylePreprocessorOptions,
330
+ watch,
306
331
} = options ;
307
332
308
- const outputFiles : OutputFile [ ] = [ ] ;
309
- const initialFiles : FileInfo [ ] = [ ] ;
310
- const errors : Message [ ] = [ ] ;
311
- const warnings : Message [ ] = [ ] ;
312
-
313
- for ( const { name, files, initial } of globalStyles ) {
314
- const virtualEntryData = files
315
- . map ( ( file ) => `@import '${ file . replace ( / \\ / g, '/' ) } ';` )
316
- . join ( '\n' ) ;
317
- const sheetResult = await bundleStylesheetText (
318
- virtualEntryData ,
319
- { virtualName : `angular:style/global;${ name } ` , resolvePath : workspaceRoot } ,
320
- {
321
- workspaceRoot,
322
- optimization : ! ! optimizationOptions . styles . minify ,
323
- sourcemap : ! ! sourcemapOptions . styles && ( sourcemapOptions . hidden ? 'external' : true ) ,
324
- outputNames : initial ? outputNames : { media : outputNames . media } ,
325
- includePaths : stylePreprocessorOptions ?. includePaths ,
326
- preserveSymlinks,
327
- externalDependencies,
328
- target,
329
- } ,
330
- ) ;
331
-
332
- errors . push ( ...sheetResult . errors ) ;
333
- warnings . push ( ...sheetResult . warnings ) ;
334
-
335
- if ( ! sheetResult . path ) {
336
- // Failed to process the stylesheet
337
- assert . ok (
338
- sheetResult . errors . length ,
339
- `Global stylesheet processing for '${ name } ' failed with no errors.` ,
340
- ) ;
341
-
342
- continue ;
343
- }
333
+ const buildOptions = createStylesheetBundleOptions ( {
334
+ workspaceRoot,
335
+ optimization : ! ! optimizationOptions . styles . minify ,
336
+ sourcemap : ! ! sourcemapOptions . styles ,
337
+ preserveSymlinks,
338
+ target,
339
+ externalDependencies,
340
+ outputNames,
341
+ includePaths : stylePreprocessorOptions ?. includePaths ,
342
+ } ) ;
343
+ buildOptions . incremental = watch ;
344
344
345
- // The virtual stylesheets will be named `stdin` by esbuild. This must be replaced
346
- // with the actual name of the global style and the leading directory separator must
347
- // also be removed to make the path relative.
348
- const sheetPath = sheetResult . path . replace ( 'stdin' , name ) ;
349
- let sheetContents = sheetResult . contents ;
350
- if ( sheetResult . map ) {
351
- outputFiles . push ( createOutputFileFromText ( sheetPath + '.map' , sheetResult . map ) ) ;
352
- sheetContents = sheetContents . replace (
353
- 'sourceMappingURL=stdin.css.map' ,
354
- `sourceMappingURL=${ name } .css.map` ,
355
- ) ;
356
- }
357
- outputFiles . push ( createOutputFileFromText ( sheetPath , sheetContents ) ) ;
345
+ const namespace = 'angular:styles/global' ;
346
+ buildOptions . entryPoints = { } ;
347
+ for ( const { name } of globalStyles ) {
348
+ buildOptions . entryPoints [ name ] = `${ namespace } ;${ name } ` ;
349
+ }
358
350
359
- if ( initial ) {
360
- initialFiles . push ( {
361
- file : sheetPath ,
362
- name,
363
- extension : '.css' ,
351
+ buildOptions . plugins . unshift ( {
352
+ name : 'angular-global-styles' ,
353
+ setup ( build ) {
354
+ build . onResolve ( { filter : / ^ a n g u l a r : s t y l e s \/ g l o b a l ; / } , ( args ) => {
355
+ if ( args . kind !== 'entry-point' ) {
356
+ return null ;
357
+ }
358
+
359
+ return {
360
+ path : args . path . split ( ';' , 2 ) [ 1 ] ,
361
+ namespace,
362
+ } ;
364
363
} ) ;
365
- }
366
- outputFiles . push ( ...sheetResult . resourceFiles ) ;
367
- }
364
+ build . onLoad ( { filter : / ./ , namespace } , ( args ) => {
365
+ const files = globalStyles . find ( ( { name } ) => name === args . path ) ?. files ;
366
+ assert ( files , `global style name should always be found [${ args . path } ]` ) ;
367
+
368
+ return {
369
+ contents : files . map ( ( file ) => `@import '${ file . replace ( / \\ / g, '/' ) } ';` ) . join ( '\n' ) ,
370
+ loader : 'css' ,
371
+ resolveDir : workspaceRoot ,
372
+ } ;
373
+ } ) ;
374
+ } ,
375
+ } ) ;
368
376
369
- return { outputFiles , initialFiles , errors , warnings } ;
377
+ return buildOptions ;
370
378
}
371
379
372
380
/**
0 commit comments