-
Notifications
You must be signed in to change notification settings - Fork 12k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor(@angular-devkit/build-angular): reorganize bundle processing…
… for browser builder (#15776)
- Loading branch information
Showing
3 changed files
with
296 additions
and
232 deletions.
There are no files selected for viewing
191 changes: 191 additions & 0 deletions
191
packages/angular_devkit/build_angular/src/browser/action-cache.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,191 @@ | ||
/** | ||
* @license | ||
* Copyright Google Inc. All Rights Reserved. | ||
* | ||
* Use of this source code is governed by an MIT-style license that can be | ||
* found in the LICENSE file at https://angular.io/license | ||
*/ | ||
import { createHash } from 'crypto'; | ||
import * as findCacheDirectory from 'find-cache-dir'; | ||
import * as fs from 'fs'; | ||
import { manglingDisabled } from '../utils/mangle-options'; | ||
import { CacheKey, ProcessBundleOptions, ProcessBundleResult } from '../utils/process-bundle'; | ||
|
||
const cacache = require('cacache'); | ||
const cacheDownlevelPath = findCacheDirectory({ name: 'angular-build-dl' }); | ||
const packageVersion = require('../../package.json').version; | ||
|
||
// Workaround Node.js issue prior to 10.16 with copyFile on macOS | ||
// https://github.com/angular/angular-cli/issues/15544 & https://github.com/nodejs/node/pull/27241 | ||
let copyFileWorkaround = false; | ||
if (process.platform === 'darwin') { | ||
const version = process.versions.node.split('.').map(part => Number(part)); | ||
if (version[0] < 10 || version[0] === 11 || (version[0] === 10 && version[1] < 16)) { | ||
copyFileWorkaround = true; | ||
} | ||
} | ||
|
||
export interface CacheEntry { | ||
path: string; | ||
size: number; | ||
integrity?: string; | ||
} | ||
|
||
export class BundleActionCache { | ||
constructor(private readonly integrityAlgorithm?: string) {} | ||
|
||
static copyEntryContent(entry: CacheEntry | string, dest: fs.PathLike): void { | ||
if (copyFileWorkaround) { | ||
try { | ||
fs.unlinkSync(dest); | ||
} catch {} | ||
} | ||
|
||
fs.copyFileSync( | ||
typeof entry === 'string' ? entry : entry.path, | ||
dest, | ||
fs.constants.COPYFILE_FICLONE, | ||
); | ||
if (process.platform !== 'win32') { | ||
// The cache writes entries as readonly and when using copyFile the permissions will also be copied. | ||
// See: https://github.com/npm/cacache/blob/073fbe1a9f789ba42d9a41de7b8429c93cf61579/lib/util/move-file.js#L36 | ||
fs.chmodSync(dest, 0o644); | ||
} | ||
} | ||
|
||
generateBaseCacheKey(content: string): string { | ||
// Create base cache key with elements: | ||
// * package version - different build-angular versions cause different final outputs | ||
// * code length/hash - ensure cached version matches the same input code | ||
const algorithm = this.integrityAlgorithm || 'sha1'; | ||
const codeHash = createHash(algorithm) | ||
.update(content) | ||
.digest('base64'); | ||
let baseCacheKey = `${packageVersion}|${content.length}|${algorithm}-${codeHash}`; | ||
if (manglingDisabled) { | ||
baseCacheKey += '|MD'; | ||
} | ||
|
||
return baseCacheKey; | ||
} | ||
|
||
generateCacheKeys(action: ProcessBundleOptions): string[] { | ||
const baseCacheKey = this.generateBaseCacheKey(action.code); | ||
|
||
// Postfix added to sourcemap cache keys when vendor sourcemaps are present | ||
// Allows non-destructive caching of both variants | ||
const SourceMapVendorPostfix = !!action.sourceMaps && action.vendorSourceMaps ? '|vendor' : ''; | ||
|
||
// Determine cache entries required based on build settings | ||
const cacheKeys = []; | ||
|
||
// If optimizing and the original is not ignored, add original as required | ||
if ((action.optimize || action.optimizeOnly) && !action.ignoreOriginal) { | ||
cacheKeys[CacheKey.OriginalCode] = baseCacheKey + '|orig'; | ||
|
||
// If sourcemaps are enabled, add original sourcemap as required | ||
if (action.sourceMaps) { | ||
cacheKeys[CacheKey.OriginalMap] = baseCacheKey + SourceMapVendorPostfix + '|orig-map'; | ||
} | ||
} | ||
// If not only optimizing, add downlevel as required | ||
if (!action.optimizeOnly) { | ||
cacheKeys[CacheKey.DownlevelCode] = baseCacheKey + '|dl'; | ||
|
||
// If sourcemaps are enabled, add downlevel sourcemap as required | ||
if (action.sourceMaps) { | ||
cacheKeys[CacheKey.DownlevelMap] = baseCacheKey + SourceMapVendorPostfix + '|dl-map'; | ||
} | ||
} | ||
|
||
return cacheKeys; | ||
} | ||
|
||
async getCacheEntries(cacheKeys: (string | null)[]): Promise<(CacheEntry | null)[] | false> { | ||
// Attempt to get required cache entries | ||
const cacheEntries = []; | ||
for (const key of cacheKeys) { | ||
if (key) { | ||
const entry = await cacache.get.info(cacheDownlevelPath, key); | ||
if (!entry) { | ||
return false; | ||
} | ||
cacheEntries.push({ | ||
path: entry.path, | ||
size: entry.size, | ||
integrity: entry.metadata && entry.metadata.integrity, | ||
}); | ||
} else { | ||
cacheEntries.push(null); | ||
} | ||
} | ||
|
||
return cacheEntries; | ||
} | ||
|
||
async getCachedBundleResult(action: ProcessBundleOptions): Promise<ProcessBundleResult | null> { | ||
const entries = action.cacheKeys && await this.getCacheEntries(action.cacheKeys); | ||
if (!entries) { | ||
return null; | ||
} | ||
|
||
const result: ProcessBundleResult = { name: action.name }; | ||
|
||
let cacheEntry = entries[CacheKey.OriginalCode]; | ||
if (cacheEntry) { | ||
result.original = { | ||
filename: action.filename, | ||
size: cacheEntry.size, | ||
integrity: cacheEntry.integrity, | ||
}; | ||
|
||
BundleActionCache.copyEntryContent(cacheEntry, result.original.filename); | ||
|
||
cacheEntry = entries[CacheKey.OriginalMap]; | ||
if (cacheEntry) { | ||
result.original.map = { | ||
filename: action.filename + '.map', | ||
size: cacheEntry.size, | ||
}; | ||
|
||
BundleActionCache.copyEntryContent(cacheEntry, result.original.filename + '.map'); | ||
} | ||
} else if (!action.ignoreOriginal) { | ||
// If the original wasn't processed (and therefore not cached), add info | ||
result.original = { | ||
filename: action.filename, | ||
size: Buffer.byteLength(action.code, 'utf8'), | ||
map: | ||
action.map === undefined | ||
? undefined | ||
: { | ||
filename: action.filename + '.map', | ||
size: Buffer.byteLength(action.map, 'utf8'), | ||
}, | ||
}; | ||
} | ||
|
||
cacheEntry = entries[CacheKey.DownlevelCode]; | ||
if (cacheEntry) { | ||
result.downlevel = { | ||
filename: action.filename.replace('es2015', 'es5'), | ||
size: cacheEntry.size, | ||
integrity: cacheEntry.integrity, | ||
}; | ||
|
||
BundleActionCache.copyEntryContent(cacheEntry, result.downlevel.filename); | ||
|
||
cacheEntry = entries[CacheKey.DownlevelMap]; | ||
if (cacheEntry) { | ||
result.downlevel.map = { | ||
filename: action.filename.replace('es2015', 'es5') + '.map', | ||
size: cacheEntry.size, | ||
}; | ||
|
||
BundleActionCache.copyEntryContent(cacheEntry, result.downlevel.filename + '.map'); | ||
} | ||
} | ||
|
||
return result; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.