@@ -27,6 +27,7 @@ import { ExecutionTransformer } from '../../transforms';
2727import { OutputHashing } from '../browser-esbuild/schema' ;
2828import { findTests } from './find-tests' ;
2929import { Schema as KarmaBuilderOptions } from './schema' ;
30+ import { IncomingMessage , ServerResponse } from 'http' ;
3031
3132interface BuildOptions extends ApplicationBuilderInternalOptions {
3233 // We know that it's always a string since we set it.
@@ -40,6 +41,68 @@ class ApplicationBuildError extends Error {
4041 }
4142}
4243
44+ interface ServeFileFunction {
45+ (
46+ filepath : string ,
47+ rangeHeader : string | string [ ] | undefined ,
48+ response : ServerResponse ,
49+ transform ?: ( c : string | Uint8Array ) => string | Uint8Array ,
50+ content ?: string | Uint8Array ,
51+ doNotCache ?: boolean ,
52+ ) : void ;
53+ }
54+
55+ interface LatestBuildFiles {
56+ files : Record < string , ResultFile | undefined > ;
57+ }
58+
59+ const LATEST_BUILD_FILES_TOKEN = 'angularLatestBuildFiles' ;
60+
61+ class AngularAssetsMiddleware {
62+ static readonly $inject = [ 'serveFile' , LATEST_BUILD_FILES_TOKEN ] ;
63+
64+ static readonly NAME = 'angular-test-assets' ;
65+
66+ constructor (
67+ private readonly serveFile : ServeFileFunction ,
68+ private readonly latestBuildFiles : LatestBuildFiles ,
69+ ) { }
70+
71+ handle ( req : IncomingMessage , res : ServerResponse , next : ( err ?: unknown ) => unknown ) {
72+ let err = null ;
73+ try {
74+ const url = new URL ( `http://${ req . headers [ 'host' ] } ${ req . url } ` ) ;
75+ const file = this . latestBuildFiles . files [ url . pathname . slice ( 1 ) ] ;
76+
77+ if ( file ?. origin === 'disk' ) {
78+ this . serveFile ( file . inputPath , undefined , res ) ;
79+ return ;
80+ } else if ( file ?. origin === 'memory' ) {
81+ // Include pathname to help with Content-Type headers.
82+ this . serveFile ( `/unused/${ url . pathname } ` , undefined , res , undefined , file . contents , true ) ;
83+ return ;
84+ }
85+ } catch ( e ) {
86+ err = e ;
87+ }
88+ next ( err ) ;
89+ }
90+
91+ static createPlugin ( initialFiles : LatestBuildFiles ) : InlinePluginDef {
92+ return {
93+ [ LATEST_BUILD_FILES_TOKEN ] : [ 'value' , { files : { ...initialFiles . files } } ] ,
94+
95+ [ `middleware:${ AngularAssetsMiddleware . NAME } ` ] : [
96+ 'factory' ,
97+ Object . assign ( ( ...args : ConstructorParameters < typeof AngularAssetsMiddleware > ) => {
98+ const inst = new AngularAssetsMiddleware ( ...args ) ;
99+ return inst . handle . bind ( inst ) ;
100+ } , AngularAssetsMiddleware ) ,
101+ ] ,
102+ } ;
103+ }
104+ }
105+
43106function injectKarmaReporter (
44107 context : BuilderContext ,
45108 buildOptions : BuildOptions ,
@@ -58,9 +121,12 @@ function injectKarmaReporter(
58121 }
59122
60123 class ProgressNotifierReporter {
61- static $inject = [ 'emitter' ] ;
124+ static $inject = [ 'emitter' , LATEST_BUILD_FILES_TOKEN ] ;
62125
63- constructor ( private readonly emitter : KarmaEmitter ) {
126+ constructor (
127+ private readonly emitter : KarmaEmitter ,
128+ private readonly latestBuildFiles : LatestBuildFiles ,
129+ ) {
64130 this . startWatchingBuild ( ) ;
65131 }
66132
@@ -81,6 +147,14 @@ function injectKarmaReporter(
81147 buildOutput . kind === ResultKind . Incremental ||
82148 buildOutput . kind === ResultKind . Full
83149 ) {
150+ if ( buildOutput . kind === ResultKind . Full ) {
151+ this . latestBuildFiles . files = buildOutput . files ;
152+ } else {
153+ this . latestBuildFiles . files = {
154+ ...this . latestBuildFiles . files ,
155+ ...buildOutput . files ,
156+ } ;
157+ }
84158 await writeTestFiles ( buildOutput . files , buildOptions . outputPath ) ;
85159 this . emitter . refreshFiles ( ) ;
86160 }
@@ -237,6 +311,7 @@ async function initializeApplication(
237311 : undefined ;
238312
239313 const buildOptions : BuildOptions = {
314+ assets : options . assets ,
240315 entryPoints,
241316 tsConfig : options . tsConfig ,
242317 outputPath,
@@ -293,7 +368,6 @@ async function initializeApplication(
293368 } ,
294369 ) ;
295370 }
296-
297371 karmaOptions . files . push (
298372 // Serve remaining JS on page load, these are the test entrypoints.
299373 { pattern : `${ outputPath } /*.js` , type : 'module' , watched : false } ,
@@ -313,8 +387,9 @@ async function initializeApplication(
313387 // Remove the webpack plugin/framework:
314388 // Alternative would be to make the Karma plugin "smart" but that's a tall order
315389 // with managing unneeded imports etc..
316- const pluginLengthBefore = ( parsedKarmaConfig . plugins ?? [ ] ) . length ;
317- parsedKarmaConfig . plugins = ( parsedKarmaConfig . plugins ?? [ ] ) . filter (
390+ parsedKarmaConfig . plugins ??= [ ] ;
391+ const pluginLengthBefore = parsedKarmaConfig . plugins . length ;
392+ parsedKarmaConfig . plugins = parsedKarmaConfig . plugins . filter (
318393 ( plugin : string | InlinePluginDef ) => {
319394 if ( typeof plugin === 'string' ) {
320395 return plugin !== 'framework:@angular-devkit/build-angular' ;
@@ -323,16 +398,21 @@ async function initializeApplication(
323398 return ! plugin [ 'framework:@angular-devkit/build-angular' ] ;
324399 } ,
325400 ) ;
326- parsedKarmaConfig . frameworks = parsedKarmaConfig . frameworks ?. filter (
401+ parsedKarmaConfig . frameworks ??= [ ] ;
402+ parsedKarmaConfig . frameworks = parsedKarmaConfig . frameworks . filter (
327403 ( framework : string ) => framework !== '@angular-devkit/build-angular' ,
328404 ) ;
329- const pluginLengthAfter = ( parsedKarmaConfig . plugins ?? [ ] ) . length ;
405+ const pluginLengthAfter = parsedKarmaConfig . plugins . length ;
330406 if ( pluginLengthBefore !== pluginLengthAfter ) {
331407 context . logger . warn (
332408 `Ignoring framework "@angular-devkit/build-angular" from karma config file because it's not compatible with the application builder.` ,
333409 ) ;
334410 }
335411
412+ parsedKarmaConfig . plugins . push ( AngularAssetsMiddleware . createPlugin ( buildOutput ) ) ;
413+ parsedKarmaConfig . middleware ??= [ ] ;
414+ parsedKarmaConfig . middleware . push ( AngularAssetsMiddleware . NAME ) ;
415+
336416 // When using code-coverage, auto-add karma-coverage.
337417 // This was done as part of the karma plugin for webpack.
338418 if (
0 commit comments