@@ -30,6 +30,7 @@ import { from, of } from 'rxjs';
30
30
import { bufferCount , catchError , concatMap , map , mergeScan , switchMap } from 'rxjs/operators' ;
31
31
import { ScriptTarget } from 'typescript' ;
32
32
import * as webpack from 'webpack' ;
33
+ import * as workerFarm from 'worker-farm' ;
33
34
import { NgBuildAnalyticsPlugin } from '../../plugins/webpack/analytics' ;
34
35
import { WebpackConfigOptions } from '../angular-cli-files/models/build-options' ;
35
36
import {
@@ -54,7 +55,12 @@ import {
54
55
statsWarningsToString ,
55
56
} from '../angular-cli-files/utilities/stats' ;
56
57
import { ExecutionTransformer } from '../transforms' ;
57
- import { BuildBrowserFeatures , deleteOutputDir } from '../utils' ;
58
+ import {
59
+ BuildBrowserFeatures ,
60
+ deleteOutputDir ,
61
+ fullDifferential ,
62
+ normalizeOptimization ,
63
+ } from '../utils' ;
58
64
import { assertCompatibleAngularVersion } from '../utils/version' ;
59
65
import {
60
66
generateBrowserWebpackConfigFromContext ,
@@ -168,6 +174,7 @@ async function initialize(
168
174
return { config : transformedConfig || config , workspace } ;
169
175
}
170
176
177
+ // tslint:disable-next-line: no-big-function
171
178
export function buildWebpackBrowser (
172
179
options : BrowserBuilderSchema ,
173
180
context : BuilderContext ,
@@ -187,6 +194,7 @@ export function buildWebpackBrowser(
187
194
transforms . logging || createBrowserLoggingCallback ( ! ! options . verbose , context . logger ) ;
188
195
189
196
return from ( initialize ( options , context , host , transforms . webpackConfiguration ) ) . pipe (
197
+ // tslint:disable-next-line: no-big-function
190
198
switchMap ( ( { workspace, config : configs } ) => {
191
199
const projectName = context . target
192
200
? context . target . project
@@ -231,50 +239,183 @@ export function buildWebpackBrowser(
231
239
1 ,
232
240
) ,
233
241
bufferCount ( configs . length ) ,
234
- switchMap ( buildEvents => {
242
+ switchMap ( async buildEvents => {
243
+ configs . length = 0 ;
235
244
const success = buildEvents . every ( r => r . success ) ;
236
- if ( success && options . index ) {
245
+ if ( success ) {
237
246
let noModuleFiles : EmittedFiles [ ] | undefined ;
238
247
let moduleFiles : EmittedFiles [ ] | undefined ;
239
248
let files : EmittedFiles [ ] | undefined ;
240
249
241
- const [ firstBuild , secondBuild ] = buildEvents ;
242
- if ( isDifferentialLoadingNeeded ) {
243
- const scriptsEntryPointName = normalizeExtraEntryPoints ( options . scripts || [ ] , 'scripts' )
244
- . map ( x => x . bundleName ) ;
250
+ const scriptsEntryPointName = normalizeExtraEntryPoints (
251
+ options . scripts || [ ] ,
252
+ 'scripts' ,
253
+ ) . map ( x => x . bundleName ) ;
245
254
255
+ const [ firstBuild , secondBuild ] = buildEvents ;
256
+ if ( isDifferentialLoadingNeeded && ( fullDifferential || options . watch ) ) {
246
257
moduleFiles = firstBuild . emittedFiles || [ ] ;
247
- files = moduleFiles . filter ( x => x . extension === '.css' || ( x . name && scriptsEntryPointName . includes ( x . name ) ) ) ;
258
+ files = moduleFiles . filter (
259
+ x => x . extension === '.css' || ( x . name && scriptsEntryPointName . includes ( x . name ) ) ,
260
+ ) ;
248
261
249
262
if ( buildEvents . length === 2 ) {
250
263
noModuleFiles = secondBuild . emittedFiles ;
251
264
}
265
+ } else if ( isDifferentialLoadingNeeded && ! fullDifferential ) {
266
+ const { emittedFiles = [ ] } = firstBuild ;
267
+ moduleFiles = [ ] ;
268
+ noModuleFiles = [ ] ;
269
+
270
+ // Common options for all bundle process actions
271
+ const actionOptions = {
272
+ optimize : normalizeOptimization ( options . optimization ) . scripts ,
273
+ sourceMaps :
274
+ options . sourceMap === true ||
275
+ ( typeof options . sourceMap === 'object' && options . sourceMap . scripts ) ,
276
+ } ;
277
+
278
+ const actions : { } [ ] = [ ] ;
279
+ const seen = new Set < string > ( ) ;
280
+ for ( const file of emittedFiles ) {
281
+ // Scripts and non-javascript files are not processed
282
+ if (
283
+ file . extension !== '.js' ||
284
+ ( file . name && scriptsEntryPointName . includes ( file . name ) )
285
+ ) {
286
+ if ( files === undefined ) {
287
+ files = [ ] ;
288
+ }
289
+ files . push ( file ) ;
290
+ continue ;
291
+ }
292
+
293
+ // Ignore already processed files; emittedFiles can contain duplicates
294
+ if ( seen . has ( file . file ) ) {
295
+ continue ;
296
+ }
297
+ seen . add ( file . file ) ;
298
+
299
+ // All files at this point except ES5 polyfills are module scripts
300
+ const es5Polyfills = file . file . startsWith ( 'polyfills-es5' ) ;
301
+ if ( ! es5Polyfills && ! file . file . startsWith ( 'polyfills-nomodule-es5' ) ) {
302
+ moduleFiles . push ( file ) ;
303
+ }
304
+ // If not optimizing then ES2015 polyfills do not need processing
305
+ // Unlike other module scripts, it is never downleveled
306
+ if ( ! actionOptions . optimize && file . file . startsWith ( 'polyfills-es2015' ) ) {
307
+ continue ;
308
+ }
309
+
310
+ // Retrieve the content/map for the file
311
+ // NOTE: Additional future optimizations will read directly from memory
312
+ let filename = path . resolve ( getSystemPath ( root ) , options . outputPath , file . file ) ;
313
+ const code = fs . readFileSync ( filename , 'utf8' ) ;
314
+ let map ;
315
+ if ( actionOptions . sourceMaps ) {
316
+ try {
317
+ map = fs . readFileSync ( filename + '.map' , 'utf8' ) ;
318
+ if ( es5Polyfills ) {
319
+ fs . unlinkSync ( filename + '.map' ) ;
320
+ }
321
+ } catch { }
322
+ }
323
+
324
+ if ( es5Polyfills ) {
325
+ fs . unlinkSync ( filename ) ;
326
+ filename = filename . replace ( '-es2015' , '' ) ;
327
+ }
328
+
329
+ // ES2015 polyfills are only optimized; optimization check was performed above
330
+ if ( file . file . startsWith ( 'polyfills-es2015' ) ) {
331
+ actions . push ( {
332
+ ...actionOptions ,
333
+ filename,
334
+ code,
335
+ map,
336
+ optimizeOnly : true ,
337
+ } ) ;
338
+
339
+ continue ;
340
+ }
341
+
342
+ // Record the bundle processing action
343
+ // The runtime chunk gets special processing for lazy loaded files
344
+ actions . push ( {
345
+ ...actionOptions ,
346
+ filename,
347
+ code,
348
+ map,
349
+ runtime : file . file . startsWith ( 'runtime' ) ,
350
+ } ) ;
351
+
352
+ // Add the newly created ES5 bundles to the index as nomodule scripts
353
+ const newFilename = es5Polyfills
354
+ ? file . file . replace ( '-es2015' , '' )
355
+ : file . file . replace ( 'es2015' , 'es5' ) ;
356
+ noModuleFiles . push ( { ...file , file : newFilename } ) ;
357
+ }
358
+
359
+ // Execute the bundle processing actions
360
+ await new Promise < void > ( ( resolve , reject ) => {
361
+ const workerFile = require . resolve ( '../utils/process-bundle' ) ;
362
+ const workers = workerFarm (
363
+ {
364
+ maxRetries : 1 ,
365
+ } ,
366
+ path . extname ( workerFile ) !== '.ts'
367
+ ? workerFile
368
+ : require . resolve ( '../utils/process-bundle-bootstrap' ) ,
369
+ [ 'process' ] ,
370
+ ) ;
371
+ let completed = 0 ;
372
+ const workCallback = ( error : Error | null ) => {
373
+ if ( error ) {
374
+ workerFarm . end ( workers ) ;
375
+ reject ( error ) ;
376
+ } else if ( ++ completed === actions . length ) {
377
+ workerFarm . end ( workers ) ;
378
+ resolve ( ) ;
379
+ }
380
+ } ;
381
+
382
+ actions . forEach ( action => workers [ 'process' ] ( action , workCallback ) ) ;
383
+ } ) ;
252
384
} else {
253
385
const { emittedFiles = [ ] } = firstBuild ;
254
386
files = emittedFiles . filter ( x => x . name !== 'polyfills-es5' ) ;
255
387
noModuleFiles = emittedFiles . filter ( x => x . name === 'polyfills-es5' ) ;
256
388
}
257
389
258
- return writeIndexHtml ( {
259
- host,
260
- outputPath : resolve ( root , join ( normalize ( options . outputPath ) , getIndexOutputFile ( options ) ) ) ,
261
- indexPath : join ( root , getIndexInputFile ( options ) ) ,
262
- files,
263
- noModuleFiles,
264
- moduleFiles,
265
- baseHref : options . baseHref ,
266
- deployUrl : options . deployUrl ,
267
- sri : options . subresourceIntegrity ,
268
- scripts : options . scripts ,
269
- styles : options . styles ,
270
- postTransform : transforms . indexHtml ,
271
- crossOrigin : options . crossOrigin ,
272
- } ) . pipe (
273
- map ( ( ) => ( { success : true } ) ) ,
274
- catchError ( error => of ( { success : false , error : mapErrorToMessage ( error ) } ) ) ,
275
- ) ;
390
+ if ( options . index ) {
391
+ return writeIndexHtml ( {
392
+ host,
393
+ outputPath : resolve (
394
+ root ,
395
+ join ( normalize ( options . outputPath ) , getIndexOutputFile ( options ) ) ,
396
+ ) ,
397
+ indexPath : join ( root , getIndexInputFile ( options ) ) ,
398
+ files,
399
+ noModuleFiles,
400
+ moduleFiles,
401
+ baseHref : options . baseHref ,
402
+ deployUrl : options . deployUrl ,
403
+ sri : options . subresourceIntegrity ,
404
+ scripts : options . scripts ,
405
+ styles : options . styles ,
406
+ postTransform : transforms . indexHtml ,
407
+ crossOrigin : options . crossOrigin ,
408
+ } )
409
+ . pipe (
410
+ map ( ( ) => ( { success : true } ) ) ,
411
+ catchError ( error => of ( { success : false , error : mapErrorToMessage ( error ) } ) ) ,
412
+ )
413
+ . toPromise ( ) ;
414
+ } else {
415
+ return { success } ;
416
+ }
276
417
} else {
277
- return of ( { success } ) ;
418
+ return { success } ;
278
419
}
279
420
} ) ,
280
421
concatMap ( buildEvent => {
0 commit comments