77 */
88
99import { 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' ;
1414import { deleteOutputDir } from '../../utils' ;
1515import { copyAssets } from '../../utils/copy-assets' ;
1616import { assertIsError } from '../../utils/error' ;
@@ -25,11 +25,12 @@ import { logExperimentalWarnings } from './experimental-warnings';
2525import { NormalizedBrowserOptions , normalizeOptions } from './options' ;
2626import { shutdownSassWorkerPool } from './sass-plugin' ;
2727import { Schema as BrowserBuilderOptions } from './schema' ;
28- import { bundleStylesheetText } from './stylesheets' ;
28+ import { createStylesheetBundleOptions } from './stylesheets' ;
2929import { ChangedFiles , createWatcher } from './watcher' ;
3030
3131interface RebuildState {
3232 codeRebuild ?: BuildInvalidate ;
33+ globalStylesRebuild ?: BuildInvalidate ;
3334 codeBundleCache ?: SourceFileCache ;
3435 fileChanges : ChangedFiles ;
3536}
@@ -41,6 +42,7 @@ class ExecutionResult {
4142 constructor (
4243 private success : boolean ,
4344 private codeRebuild ?: BuildInvalidate ,
45+ private globalStylesRebuild ?: BuildInvalidate ,
4446 private codeBundleCache ?: SourceFileCache ,
4547 ) { }
4648
@@ -55,6 +57,7 @@ class ExecutionResult {
5557
5658 return {
5759 codeRebuild : this . codeRebuild ,
60+ globalStylesRebuild : this . globalStylesRebuild ,
5861 codeBundleCache : this . codeBundleCache ,
5962 fileChanges,
6063 } ;
@@ -97,7 +100,10 @@ async function execute(
97100 rebuildState ?. codeRebuild ?? createCodeBundleOptions ( options , target , codeBundleCache ) ,
98101 ) ,
99102 // Execute esbuild to bundle the global stylesheets
100- bundleGlobalStylesheets ( options , target ) ,
103+ bundle (
104+ workspaceRoot ,
105+ rebuildState ?. globalStylesRebuild ?? createGlobalStylesBundleOptions ( options , target ) ,
106+ ) ,
101107 ] ) ;
102108
103109 // Log all warnings and errors generated during bundling
@@ -108,18 +114,33 @@ async function execute(
108114
109115 // Return if the bundling failed to generate output files or there are errors
110116 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+ ) ;
112133 }
113134
135+ // Filter global stylesheet initial files
136+ styleResults . initialFiles = styleResults . initialFiles . filter (
137+ ( { name } ) => options . globalStyles . find ( ( style ) => style . name === name ) ?. initial ,
138+ ) ;
139+
114140 // Combine the bundling output files
115141 const initialFiles : FileInfo [ ] = [ ...codeResults . initialFiles , ...styleResults . initialFiles ] ;
116142 const outputFiles : OutputFile [ ] = [ ...codeResults . outputFiles , ...styleResults . outputFiles ] ;
117143
118- // Return if the global stylesheet bundling has errors
119- if ( styleResults . errors . length ) {
120- return new ExecutionResult ( false , codeResults . rebuild , codeBundleCache ) ;
121- }
122-
123144 // Generate index HTML file
124145 if ( indexHtmlOptions ) {
125146 // Create an index HTML generator that reads from the in-memory output files
@@ -184,14 +205,14 @@ async function execute(
184205 } catch ( error ) {
185206 context . logger . error ( error instanceof Error ? error . message : `${ error } ` ) ;
186207
187- return new ExecutionResult ( false , codeResults . rebuild , codeBundleCache ) ;
208+ return new ExecutionResult ( false , codeResults . rebuild , styleResults . rebuild , codeBundleCache ) ;
188209 }
189210 }
190211
191212 const buildTime = Number ( process . hrtime . bigint ( ) - startTime ) / 10 ** 9 ;
192213 context . logger . info ( `Complete. [${ buildTime . toFixed ( 3 ) } seconds]` ) ;
193214
194- return new ExecutionResult ( true , codeResults . rebuild , codeBundleCache ) ;
215+ return new ExecutionResult ( true , codeResults . rebuild , styleResults . rebuild , codeBundleCache ) ;
195216}
196217
197218function createOutputFileFromText ( path : string , text : string ) : OutputFile {
@@ -293,7 +314,10 @@ function createCodeBundleOptions(
293314 } ;
294315}
295316
296- async function bundleGlobalStylesheets ( options : NormalizedBrowserOptions , target : string [ ] ) {
317+ function createGlobalStylesBundleOptions (
318+ options : NormalizedBrowserOptions ,
319+ target : string [ ] ,
320+ ) : BuildOptions {
297321 const {
298322 workspaceRoot,
299323 optimizationOptions,
@@ -303,70 +327,54 @@ async function bundleGlobalStylesheets(options: NormalizedBrowserOptions, target
303327 preserveSymlinks,
304328 externalDependencies,
305329 stylePreprocessorOptions,
330+ watch,
306331 } = options ;
307332
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 ;
344344
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+ }
358350
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+ } ;
364363 } ) ;
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+ } ) ;
368376
369- return { outputFiles , initialFiles , errors , warnings } ;
377+ return buildOptions ;
370378}
371379
372380/**
0 commit comments