66 * found in the LICENSE file at https://angular.dev/license
77 */
88
9- import { OutputFile } from 'esbuild' ;
109import assert from 'node:assert' ;
1110import { createHash } from 'node:crypto' ;
1211import path from 'node:path' ;
13- import { BuildOutputFileType , BundleContextResult , BundlerContext } from '../bundler-context' ;
12+ import {
13+ BuildOutputFile ,
14+ BuildOutputFileType ,
15+ BundleContextResult ,
16+ BundlerContext ,
17+ } from '../bundler-context' ;
1418import { MemoryCache } from '../cache' ;
1519import {
1620 BundleStylesheetOptions ,
@@ -37,7 +41,14 @@ export class ComponentStylesheetBundler {
3741 private readonly incremental : boolean ,
3842 ) { }
3943
40- async bundleFile ( entry : string , externalId ?: string | boolean ) {
44+ /**
45+ * Bundle a file-based component stylesheet for use within an AOT compiled Angular application.
46+ * @param entry The file path of the stylesheet.
47+ * @param externalId Either an external identifier string for initial bundling or a boolean for rebuilds, if external.
48+ * @param direct If true, the output will be used directly by the builder; false if used inside the compiler plugin.
49+ * @returns A component bundle result object.
50+ */
51+ async bundleFile ( entry : string , externalId ?: string | boolean , direct ?: boolean ) {
4152 const bundlerContext = await this . #fileContexts. getOrCreate ( entry , ( ) => {
4253 return new BundlerContext ( this . options . workspaceRoot , this . incremental , ( loadCache ) => {
4354 const buildOptions = createStylesheetBundleOptions ( this . options , loadCache ) ;
@@ -62,6 +73,7 @@ export class ComponentStylesheetBundler {
6273 await bundlerContext . bundle ( ) ,
6374 bundlerContext . watchFiles ,
6475 ! ! externalId ,
76+ ! ! direct ,
6577 ) ;
6678 }
6779
@@ -127,6 +139,7 @@ export class ComponentStylesheetBundler {
127139 await bundlerContext . bundle ( ) ,
128140 bundlerContext . watchFiles ,
129141 ! ! externalId ,
142+ false ,
130143 ) ;
131144 }
132145
@@ -156,6 +169,15 @@ export class ComponentStylesheetBundler {
156169 return entries ;
157170 }
158171
172+ collectReferencedFiles ( ) : string [ ] {
173+ const files = [ ] ;
174+ for ( const context of this . #fileContexts. values ( ) ) {
175+ files . push ( ...context . watchFiles ) ;
176+ }
177+
178+ return files ;
179+ }
180+
159181 async dispose ( ) : Promise < void > {
160182 const contexts = [ ...this . #fileContexts. values ( ) , ...this . #inlineContexts. values ( ) ] ;
161183 this . #fileContexts. clear ( ) ;
@@ -168,61 +190,70 @@ export class ComponentStylesheetBundler {
168190 result : BundleContextResult ,
169191 referencedFiles : Set < string > | undefined ,
170192 external : boolean ,
193+ direct : boolean ,
171194 ) {
172195 let contents = '' ;
173- let metafile ;
174- const outputFiles : OutputFile [ ] = [ ] ;
196+ const outputFiles : BuildOutputFile [ ] = [ ] ;
175197
176- if ( ! result . errors ) {
177- for ( const outputFile of result . outputFiles ) {
178- const filename = path . basename ( outputFile . path ) ;
198+ const { errors, warnings } = result ;
199+ if ( errors ) {
200+ return { errors, warnings, referencedFiles } ;
201+ }
179202
180- if ( outputFile . type === BuildOutputFileType . Media || filename . endsWith ( '.css.map' ) ) {
181- // The output files could also contain resources (images/fonts/etc.) that were referenced and the map files.
203+ for ( const outputFile of result . outputFiles ) {
204+ const filename = path . basename ( outputFile . path ) ;
182205
183- // Clone the output file to avoid amending the original path which would causes problems during rebuild.
184- const clonedOutputFile = outputFile . clone ( ) ;
206+ if ( outputFile . type === BuildOutputFileType . Media || filename . endsWith ( '.css.map' ) ) {
207+ // The output files could also contain resources (images/fonts/etc.) that were referenced and the map files.
208+
209+ // Clone the output file to avoid amending the original path which would causes problems during rebuild.
210+ const clonedOutputFile = outputFile . clone ( ) ;
185211
186- // Needed for Bazel as otherwise the files will not be written in the correct place,
187- // this is because esbuild will resolve the output file from the outdir which is currently set to `workspaceRoot` twice,
188- // once in the stylesheet and the other in the application code bundler.
189- // Ex: `../../../../../app.component.css.map`.
212+ // Needed for Bazel as otherwise the files will not be written in the correct place,
213+ // this is because esbuild will resolve the output file from the outdir which is currently set to `workspaceRoot` twice,
214+ // once in the stylesheet and the other in the application code bundler.
215+ // Ex: `../../../../../app.component.css.map`.
216+ if ( ! direct ) {
190217 clonedOutputFile . path = path . join ( this . options . workspaceRoot , outputFile . path ) ;
218+ }
191219
192- outputFiles . push ( clonedOutputFile ) ;
193- } else if ( filename . endsWith ( '.css' ) ) {
194- if ( external ) {
195- const clonedOutputFile = outputFile . clone ( ) ;
220+ outputFiles . push ( clonedOutputFile ) ;
221+ } else if ( filename . endsWith ( '.css' ) ) {
222+ if ( external ) {
223+ const clonedOutputFile = outputFile . clone ( ) ;
224+ if ( ! direct ) {
196225 clonedOutputFile . path = path . join ( this . options . workspaceRoot , outputFile . path ) ;
197- outputFiles . push ( clonedOutputFile ) ;
198- contents = path . posix . join ( this . options . publicPath ?? '' , filename ) ;
199- } else {
200- contents = outputFile . text ;
201226 }
227+ outputFiles . push ( clonedOutputFile ) ;
228+ contents = path . posix . join ( this . options . publicPath ?? '' , filename ) ;
202229 } else {
203- throw new Error (
204- `Unexpected non CSS/Media file "${ filename } " outputted during component stylesheet processing.` ,
205- ) ;
230+ contents = outputFile . text ;
206231 }
232+ } else {
233+ throw new Error (
234+ `Unexpected non CSS/Media file "${ filename } " outputted during component stylesheet processing.` ,
235+ ) ;
207236 }
208-
209- metafile = result . metafile ;
210- // Remove entryPoint fields from outputs to prevent the internal component styles from being
211- // treated as initial files. Also mark the entry as a component resource for stat reporting.
212- Object . values ( metafile . outputs ) . forEach ( ( output ) => {
213- delete output . entryPoint ;
214- // eslint-disable-next-line @typescript-eslint/no-explicit-any
215- ( output as any ) [ 'ng-component' ] = true ;
216- } ) ;
217237 }
218238
239+ const metafile = result . metafile ;
240+ // Remove entryPoint fields from outputs to prevent the internal component styles from being
241+ // treated as initial files. Also mark the entry as a component resource for stat reporting.
242+ Object . values ( metafile . outputs ) . forEach ( ( output ) => {
243+ delete output . entryPoint ;
244+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
245+ ( output as any ) [ 'ng-component' ] = true ;
246+ } ) ;
247+
219248 return {
220- errors : result . errors ,
221- warnings : result . warnings ,
249+ errors,
250+ warnings,
222251 contents,
223252 outputFiles,
224253 metafile,
225254 referencedFiles,
255+ externalImports : result . externalImports ,
256+ initialFiles : new Map ( ) ,
226257 } ;
227258 }
228259}
0 commit comments