@@ -24,7 +24,10 @@ import {
24
24
virtualFs ,
25
25
} from '@angular-devkit/core' ;
26
26
import { NodeJsSyncHost } from '@angular-devkit/core/node' ;
27
+ import { createHash } from 'crypto' ;
28
+ import * as findCacheDirectory from 'find-cache-dir' ;
27
29
import * as fs from 'fs' ;
30
+ import * as os from 'os' ;
28
31
import * as path from 'path' ;
29
32
import { from , of } from 'rxjs' ;
30
33
import { bufferCount , catchError , concatMap , map , mergeScan , switchMap } from 'rxjs/operators' ;
@@ -62,6 +65,7 @@ import {
62
65
normalizeOptimization ,
63
66
normalizeSourceMaps ,
64
67
} from '../utils' ;
68
+ import { CacheKey , ProcessBundleOptions } from '../utils/process-bundle' ;
65
69
import { assertCompatibleAngularVersion } from '../utils/version' ;
66
70
import {
67
71
generateBrowserWebpackConfigFromContext ,
@@ -70,6 +74,10 @@ import {
70
74
} from '../utils/webpack-browser-config' ;
71
75
import { Schema as BrowserBuilderSchema } from './schema' ;
72
76
77
+ const cacache = require ( 'cacache' ) ;
78
+ const cacheDownlevelPath = findCacheDirectory ( { name : 'angular-build-dl' } ) ;
79
+ const packageVersion = require ( '../../package.json' ) . version ;
80
+
73
81
export type BrowserBuilderOutput = json . JsonObject &
74
82
BuilderOutput & {
75
83
outputPath : string ;
@@ -240,6 +248,7 @@ export function buildWebpackBrowser(
240
248
1 ,
241
249
) ,
242
250
bufferCount ( configs . length ) ,
251
+ // tslint:disable-next-line: no-big-function
243
252
switchMap ( async buildEvents => {
244
253
configs . length = 0 ;
245
254
const success = buildEvents . every ( r => r . success ) ;
@@ -274,9 +283,10 @@ export function buildWebpackBrowser(
274
283
optimize : normalizeOptimization ( options . optimization ) . scripts ,
275
284
sourceMaps : sourceMapOptions . scripts ,
276
285
hiddenSourceMaps : sourceMapOptions . hidden ,
286
+ vendorSourceMaps : sourceMapOptions . vendor ,
277
287
} ;
278
288
279
- const actions : { } [ ] = [ ] ;
289
+ const actions : ProcessBundleOptions [ ] = [ ] ;
280
290
const seen = new Set < string > ( ) ;
281
291
for ( const file of emittedFiles ) {
282
292
// Scripts and non-javascript files are not processed
@@ -348,6 +358,7 @@ export function buildWebpackBrowser(
348
358
code,
349
359
map,
350
360
runtime : file . file . startsWith ( 'runtime' ) ,
361
+ ignoreOriginal : es5Polyfills ,
351
362
} ) ;
352
363
353
364
// Add the newly created ES5 bundles to the index as nomodule scripts
@@ -359,30 +370,133 @@ export function buildWebpackBrowser(
359
370
360
371
// Execute the bundle processing actions
361
372
context . logger . info ( 'Generating ES5 bundles for differential loading...' ) ;
362
- await new Promise < void > ( ( resolve , reject ) => {
363
- const workerFile = require . resolve ( '../utils/process-bundle' ) ;
364
- const workers = workerFarm (
365
- {
366
- maxRetries : 1 ,
367
- } ,
368
- path . extname ( workerFile ) !== '.ts'
369
- ? workerFile
370
- : require . resolve ( '../utils/process-bundle-bootstrap' ) ,
371
- [ 'process' ] ,
372
- ) ;
373
- let completed = 0 ;
374
- const workCallback = ( error : Error | null ) => {
375
- if ( error ) {
376
- workerFarm . end ( workers ) ;
377
- reject ( error ) ;
378
- } else if ( ++ completed === actions . length ) {
379
- workerFarm . end ( workers ) ;
380
- resolve ( ) ;
373
+
374
+ const processActions : typeof actions = [ ] ;
375
+ const cacheActions : { src : string ; dest : string } [ ] = [ ] ;
376
+ for ( const action of actions ) {
377
+ // Create base cache key with elements:
378
+ // * package version - different build-angular versions cause different final outputs
379
+ // * code length/hash - ensure cached version matches the same input code
380
+ const codeHash = createHash ( 'sha1' )
381
+ . update ( action . code )
382
+ . digest ( 'hex' ) ;
383
+ const baseCacheKey = `${ packageVersion } |${ action . code . length } |${ codeHash } ` ;
384
+
385
+ // Postfix added to sourcemap cache keys when vendor sourcemaps are present
386
+ // Allows non-destructive caching of both variants
387
+ const SourceMapVendorPostfix =
388
+ ! ! action . sourceMaps && action . vendorSourceMaps ? '|vendor' : '' ;
389
+
390
+ // Determine cache entries required based on build settings
391
+ const cacheKeys = [ ] ;
392
+
393
+ // If optimizing and the original is not ignored, add original as required
394
+ if ( ( action . optimize || action . optimizeOnly ) && ! action . ignoreOriginal ) {
395
+ cacheKeys [ CacheKey . OriginalCode ] = baseCacheKey + '|orig' ;
396
+
397
+ // If sourcemaps are enabled, add original sourcemap as required
398
+ if ( action . sourceMaps ) {
399
+ cacheKeys [ CacheKey . OriginalMap ] =
400
+ baseCacheKey + SourceMapVendorPostfix + '|orig-map' ;
401
+ }
402
+ }
403
+ // If not only optimizing, add downlevel as required
404
+ if ( ! action . optimizeOnly ) {
405
+ cacheKeys [ CacheKey . DownlevelCode ] = baseCacheKey + '|dl' ;
406
+
407
+ // If sourcemaps are enabled, add downlevel sourcemap as required
408
+ if ( action . sourceMaps ) {
409
+ cacheKeys [ CacheKey . DownlevelMap ] =
410
+ baseCacheKey + SourceMapVendorPostfix + '|dl-map' ;
411
+ }
412
+ }
413
+
414
+ // Attempt to get required cache entries
415
+ const cacheEntries = [ ] ;
416
+ for ( const key of cacheKeys ) {
417
+ if ( key ) {
418
+ cacheEntries . push ( await cacache . get . info ( cacheDownlevelPath , key ) ) ;
419
+ } else {
420
+ cacheEntries . push ( null ) ;
421
+ }
422
+ }
423
+
424
+ // Check if required cache entries are present
425
+ let cached = cacheKeys . length > 0 ;
426
+ for ( let i = 0 ; i < cacheKeys . length ; ++ i ) {
427
+ if ( cacheKeys [ i ] && ! cacheEntries [ i ] ) {
428
+ cached = false ;
429
+ break ;
430
+ }
431
+ }
432
+
433
+ // If all required cached entries are present, use the cached entries
434
+ // Otherwise process the files
435
+ if ( cached ) {
436
+ if ( cacheEntries [ CacheKey . OriginalCode ] ) {
437
+ cacheActions . push ( {
438
+ src : cacheEntries [ CacheKey . OriginalCode ] . path ,
439
+ dest : action . filename ,
440
+ } ) ;
381
441
}
382
- } ;
442
+ if ( cacheEntries [ CacheKey . OriginalMap ] ) {
443
+ cacheActions . push ( {
444
+ src : cacheEntries [ CacheKey . OriginalMap ] . path ,
445
+ dest : action . filename + '.map' ,
446
+ } ) ;
447
+ }
448
+ if ( cacheEntries [ CacheKey . DownlevelCode ] ) {
449
+ cacheActions . push ( {
450
+ src : cacheEntries [ CacheKey . DownlevelCode ] . path ,
451
+ dest : action . filename . replace ( 'es2015' , 'es5' ) ,
452
+ } ) ;
453
+ }
454
+ if ( cacheEntries [ CacheKey . DownlevelMap ] ) {
455
+ cacheActions . push ( {
456
+ src : cacheEntries [ CacheKey . DownlevelMap ] . path ,
457
+ dest : action . filename . replace ( 'es2015' , 'es5' ) + '.map' ,
458
+ } ) ;
459
+ }
460
+ } else {
461
+ processActions . push ( {
462
+ ...action ,
463
+ cacheKeys,
464
+ cachePath : cacheDownlevelPath || undefined ,
465
+ } ) ;
466
+ }
467
+ }
468
+
469
+ for ( const action of cacheActions ) {
470
+ fs . copyFileSync ( action . src , action . dest , fs . constants . COPYFILE_FICLONE ) ;
471
+ }
472
+
473
+ if ( processActions . length > 0 ) {
474
+ await new Promise < void > ( ( resolve , reject ) => {
475
+ const workerFile = require . resolve ( '../utils/process-bundle' ) ;
476
+ const workers = workerFarm (
477
+ {
478
+ maxRetries : 1 ,
479
+ } ,
480
+ path . extname ( workerFile ) !== '.ts'
481
+ ? workerFile
482
+ : require . resolve ( '../utils/process-bundle-bootstrap' ) ,
483
+ [ 'process' ] ,
484
+ ) ;
485
+ let completed = 0 ;
486
+ const workCallback = ( error : Error | null ) => {
487
+ if ( error ) {
488
+ workerFarm . end ( workers ) ;
489
+ reject ( error ) ;
490
+ } else if ( ++ completed === processActions . length ) {
491
+ workerFarm . end ( workers ) ;
492
+ resolve ( ) ;
493
+ }
494
+ } ;
495
+
496
+ processActions . forEach ( action => workers [ 'process' ] ( action , workCallback ) ) ;
497
+ } ) ;
498
+ }
383
499
384
- actions . forEach ( action => workers [ 'process' ] ( action , workCallback ) ) ;
385
- } ) ;
386
500
context . logger . info ( 'ES5 bundle generation complete.' ) ;
387
501
} else {
388
502
const { emittedFiles = [ ] } = firstBuild ;
0 commit comments