UNPKG

ng-packagr

Version:

Compile and package Angular libraries in Angular Package Format (APF)

204 lines 7.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.BundlerContext = exports.BuildOutputFileType = void 0; const esbuild_1 = require("esbuild"); const node_path_1 = require("node:path"); const load_result_cache_1 = require("./load-result-cache"); var BuildOutputFileType; (function (BuildOutputFileType) { BuildOutputFileType[BuildOutputFileType["Browser"] = 0] = "Browser"; BuildOutputFileType[BuildOutputFileType["Media"] = 1] = "Media"; BuildOutputFileType[BuildOutputFileType["ServerApplication"] = 2] = "ServerApplication"; BuildOutputFileType[BuildOutputFileType["ServerRoot"] = 3] = "ServerRoot"; BuildOutputFileType[BuildOutputFileType["Root"] = 4] = "Root"; })(BuildOutputFileType || (exports.BuildOutputFileType = BuildOutputFileType = {})); /** * Determines if an unknown value is an esbuild BuildFailure error object thrown by esbuild. * @param value A potential esbuild BuildFailure error object. * @returns `true` if the object is determined to be a BuildFailure object; otherwise, `false`. */ function isEsBuildFailure(value) { return !!value && typeof value === 'object' && 'errors' in value && 'warnings' in value; } class BundlerContext { workspaceRoot; incremental; #esbuildContext; #esbuildOptions; #esbuildResult; #optionsFactory; #shouldCacheResult; #loadCache; watchFiles = new Set(); constructor(workspaceRoot, incremental, options) { this.workspaceRoot = workspaceRoot; this.incremental = incremental; // To cache the results an option factory is needed to capture the full set of dependencies this.#shouldCacheResult = incremental && typeof options === 'function'; this.#optionsFactory = (...args) => { const baseOptions = typeof options === 'function' ? options(...args) : options; return { ...baseOptions, metafile: true, write: false, }; }; } /** * Executes the esbuild build function and normalizes the build result in the event of a * build failure that results in no output being generated. * All builds use the `write` option with a value of `false` to allow for the output files * build result array to be populated. * * @returns If output files are generated, the full esbuild BuildResult; if not, the * warnings and errors for the attempted build. */ async bundle() { // Return existing result if present if (this.#esbuildResult) { return this.#esbuildResult; } const result = await this.#performBundle(); if (this.#shouldCacheResult) { this.#esbuildResult = result; } return result; } // eslint-disable-next-line max-lines-per-function async #performBundle() { // Create esbuild options if not present if (this.#esbuildOptions === undefined) { if (this.incremental) { this.#loadCache = new load_result_cache_1.MemoryLoadResultCache(); } this.#esbuildOptions = this.#optionsFactory(this.#loadCache); } if (this.incremental) { this.watchFiles.clear(); } let result; try { if (this.#esbuildContext) { // Rebuild using the existing incremental build context result = await this.#esbuildContext.rebuild(); } else if (this.incremental) { // Create an incremental build context and perform the first build. // Context creation does not perform a build. this.#esbuildContext = await (0, esbuild_1.context)(this.#esbuildOptions); result = await this.#esbuildContext.rebuild(); } else { // For non-incremental builds, perform a single build result = await (0, esbuild_1.build)(this.#esbuildOptions); } } catch (failure) { // Build failures will throw an exception which contains errors/warnings if (isEsBuildFailure(failure)) { this.#addErrorsToWatch(failure); return failure; } else { throw failure; } } finally { if (this.incremental) { // When incremental always add any files from the load result cache if (this.#loadCache) { for (const file of this.#loadCache.watchFiles) { if (!isInternalAngularFile(file)) { // watch files are fully resolved paths this.watchFiles.add(file); } } } } } // Update files that should be watched. // While this should technically not be linked to incremental mode, incremental is only // currently enabled with watch mode where watch files are needed. if (this.incremental) { // Add input files except virtual angular files which do not exist on disk for (const input of Object.keys(result.metafile.inputs)) { if (!isInternalAngularFile(input)) { // input file paths are always relative to the workspace root this.watchFiles.add((0, node_path_1.join)(this.workspaceRoot, input)); } } } // Return if the build encountered any errors if (result.errors.length) { this.#addErrorsToWatch(result); return { errors: result.errors, warnings: result.warnings, }; } // Return the successful build results return { ...result, errors: undefined, }; } #addErrorsToWatch(result) { for (const error of result.errors) { let file = error.location?.file; if (file && !isInternalAngularFile(file)) { this.watchFiles.add((0, node_path_1.join)(this.workspaceRoot, file)); } for (const note of error.notes) { file = note.location?.file; if (file && !isInternalAngularFile(file)) { this.watchFiles.add((0, node_path_1.join)(this.workspaceRoot, file)); } } } } /** * Invalidate a stored bundler result based on the previous watch files * and a list of changed files. * The context must be created with incremental mode enabled for results * to be stored. * @returns True, if the result was invalidated; False, otherwise. */ invalidate(files) { if (!this.incremental) { return false; } let invalid = false; for (const file of files) { if (this.#loadCache?.invalidate(file)) { invalid = true; continue; } invalid ||= this.watchFiles.has(file); } if (invalid) { this.#esbuildResult = undefined; } return invalid; } /** * Disposes incremental build resources present in the context. * * @returns A promise that resolves when disposal is complete. */ async dispose() { try { this.#esbuildOptions = undefined; this.#esbuildResult = undefined; this.#loadCache = undefined; await this.#esbuildContext?.dispose(); } finally { this.#esbuildContext = undefined; } } } exports.BundlerContext = BundlerContext; function isInternalAngularFile(file) { return file.startsWith('angular:'); } //# sourceMappingURL=bundler-context.js.map