@angular/build
Version:
Official build system for Angular
153 lines (152 loc) • 6.68 kB
JavaScript
"use strict";
/**
* @license
* Copyright Google LLC 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.dev/license
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.JavaScriptTransformer = void 0;
const node_crypto_1 = require("node:crypto");
const promises_1 = require("node:fs/promises");
const utils_1 = require("../../utils/server-rendering/esm-in-memory-loader/utils");
const worker_pool_1 = require("../../utils/worker-pool");
/**
* A class that performs transformation of JavaScript files and raw data.
* A worker pool is used to distribute the transformation actions and allow
* parallel processing. Transformation behavior is based on the filename and
* data. Transformations may include: async downleveling, Angular linking,
* and advanced optimizations.
*/
class JavaScriptTransformer {
maxThreads;
cache;
#workerPool;
#commonOptions;
#fileCacheKeyBase;
constructor(options, maxThreads, cache) {
this.maxThreads = maxThreads;
this.cache = cache;
// Extract options to ensure only the named options are serialized and sent to the worker
const { sourcemap, thirdPartySourcemaps = false, advancedOptimizations = false, jit = false, } = options;
this.#commonOptions = {
sourcemap,
thirdPartySourcemaps,
advancedOptimizations,
jit,
};
this.#fileCacheKeyBase = Buffer.from(JSON.stringify(this.#commonOptions), 'utf-8');
}
#ensureWorkerPool() {
if (this.#workerPool) {
return this.#workerPool;
}
const workerPoolOptions = {
filename: require.resolve('./javascript-transformer-worker'),
maxThreads: this.maxThreads,
};
// Prevent passing SSR `--import` (loader-hooks) from parent to child worker.
const filteredExecArgv = process.execArgv.filter((v) => v !== utils_1.IMPORT_EXEC_ARGV);
if (process.execArgv.length !== filteredExecArgv.length) {
workerPoolOptions.execArgv = filteredExecArgv;
}
this.#workerPool = new worker_pool_1.WorkerPool(workerPoolOptions);
return this.#workerPool;
}
/**
* Performs JavaScript transformations on a file from the filesystem.
* If no transformations are required, the data for the original file will be returned.
* @param filename The full path to the file.
* @param skipLinker If true, bypass all Angular linker processing; if false, attempt linking.
* @param sideEffects If false, and `advancedOptimizations` is enabled tslib decorators are wrapped.
* @returns A promise that resolves to a UTF-8 encoded Uint8Array containing the result.
*/
async transformFile(filename, skipLinker, sideEffects, instrumentForCoverage) {
const data = await (0, promises_1.readFile)(filename);
let result;
let cacheKey;
if (this.cache) {
// Create a cache key from the file data and options that effect the output.
// NOTE: If additional options are added, this may need to be updated.
// TODO: Consider xxhash or similar instead of SHA256
const hash = (0, node_crypto_1.createHash)('sha256');
hash.update(`${!!skipLinker}--${!!sideEffects}`);
hash.update(data);
hash.update(this.#fileCacheKeyBase);
cacheKey = hash.digest('hex');
try {
result = await this.cache?.get(cacheKey);
}
catch {
// Failure to get the value should not fail the transform
}
}
if (result === undefined) {
// If there is no cache or no cached entry, process the file
result = (await this.#ensureWorkerPool().run({
filename,
data,
skipLinker,
sideEffects,
instrumentForCoverage,
...this.#commonOptions,
}, {
// The below is disable as with Yarn PNP this causes build failures with the below message
// `Unable to deserialize cloned data`.
transferList: process.versions.pnp ? undefined : [data.buffer],
}));
// If there is a cache then store the result
if (this.cache && cacheKey) {
try {
await this.cache.put(cacheKey, result);
}
catch {
// Failure to store the value in the cache should not fail the transform
}
}
}
return result;
}
/**
* Performs JavaScript transformations on the provided data of a file. The file does not need
* to exist on the filesystem.
* @param filename The full path of the file represented by the data.
* @param data The data of the file that should be transformed.
* @param skipLinker If true, bypass all Angular linker processing; if false, attempt linking.
* @param sideEffects If false, and `advancedOptimizations` is enabled tslib decorators are wrapped.
* @returns A promise that resolves to a UTF-8 encoded Uint8Array containing the result.
*/
async transformData(filename, data, skipLinker, sideEffects, instrumentForCoverage) {
// Perform a quick test to determine if the data needs any transformations.
// This allows directly returning the data without the worker communication overhead.
if (skipLinker && !this.#commonOptions.advancedOptimizations && !instrumentForCoverage) {
const keepSourcemap = this.#commonOptions.sourcemap &&
(!!this.#commonOptions.thirdPartySourcemaps || !/[\\/]node_modules[\\/]/.test(filename));
return Buffer.from(keepSourcemap ? data : data.replace(/^\/\/# sourceMappingURL=[^\r\n]*/gm, ''), 'utf-8');
}
return this.#ensureWorkerPool().run({
filename,
data,
skipLinker,
sideEffects,
instrumentForCoverage,
...this.#commonOptions,
});
}
/**
* Stops all active transformation tasks and shuts down all workers.
* @returns A void promise that resolves when closing is complete.
*/
async close() {
if (this.#workerPool) {
try {
await this.#workerPool.destroy();
}
finally {
this.#workerPool = undefined;
}
}
}
}
exports.JavaScriptTransformer = JavaScriptTransformer;