UNPKG

@angular/build

Version:

Official build system for Angular

253 lines 12.3 kB
"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 */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.buildApplicationInternal = buildApplicationInternal; exports.buildApplication = buildApplication; const architect_1 = require("@angular-devkit/architect"); const node_assert_1 = __importDefault(require("node:assert")); const promises_1 = __importDefault(require("node:fs/promises")); const node_path_1 = __importDefault(require("node:path")); const bundler_context_1 = require("../../tools/esbuild/bundler-context"); const utils_1 = require("../../tools/esbuild/utils"); const color_1 = require("../../utils/color"); const delete_output_dir_1 = require("../../utils/delete-output-dir"); const environment_options_1 = require("../../utils/environment-options"); const purge_cache_1 = require("../../utils/purge-cache"); const version_1 = require("../../utils/version"); const build_action_1 = require("./build-action"); const execute_build_1 = require("./execute-build"); const options_1 = require("./options"); const results_1 = require("./results"); const isNodeV22orHigher = Number(process.versions.node.split('.', 1)[0]) >= 22; async function* buildApplicationInternal(options, // TODO: Integrate abort signal support into builder system context, extensions) { const { workspaceRoot, logger, target } = context; // Check Angular version. (0, version_1.assertCompatibleAngularVersion)(workspaceRoot); // Purge old build disk cache. await (0, purge_cache_1.purgeStaleBuildCache)(context); // Determine project name from builder context target const projectName = target?.project; if (!projectName) { context.logger.error(`The 'application' builder requires a target to be specified.`); // Only the vite-based dev server current uses the errors value yield { kind: results_1.ResultKind.Failure, errors: [] }; return; } if (environment_options_1.bazelEsbuildPluginPath) { extensions ??= {}; extensions.codePlugins ??= []; const { default: bazelEsbuildPlugin } = await Promise.resolve(`${environment_options_1.bazelEsbuildPluginPath}`).then(s => __importStar(require(s))); extensions.codePlugins.push(bazelEsbuildPlugin); } const normalizedOptions = await (0, options_1.normalizeOptions)(context, projectName, options, extensions); if (!normalizedOptions.outputOptions.ignoreServer) { const { browser, server } = normalizedOptions.outputOptions; if (browser === '') { context.logger.error(`'outputPath.browser' cannot be configured to an empty string when SSR is enabled.`); yield { kind: results_1.ResultKind.Failure, errors: [] }; return; } if (browser === server) { context.logger.error(`'outputPath.browser' and 'outputPath.server' cannot be configured to the same value.`); yield { kind: results_1.ResultKind.Failure, errors: [] }; return; } } // Setup an abort controller with a builder teardown if no signal is present let signal = context.signal; if (!signal) { const controller = new AbortController(); signal = controller.signal; context.addTeardown(() => controller.abort('builder-teardown')); } yield* (0, build_action_1.runEsBuildBuildAction)(async (rebuildState) => { const { serverEntryPoint, jsonLogs, partialSSRBuild } = normalizedOptions; const startTime = process.hrtime.bigint(); const result = await (0, execute_build_1.executeBuild)(normalizedOptions, context, rebuildState); if (jsonLogs) { result.addLog(await (0, utils_1.createJsonBuildManifest)(result, normalizedOptions)); } else { if (serverEntryPoint && !partialSSRBuild) { const prerenderedRoutesLength = Object.keys(result.prerenderedRoutes).length; let prerenderMsg = `Prerendered ${prerenderedRoutesLength} static route`; prerenderMsg += prerenderedRoutesLength !== 1 ? 's.' : '.'; result.addLog(color_1.colors.magenta(prerenderMsg)); } const buildTime = Number(process.hrtime.bigint() - startTime) / 10 ** 9; const hasError = result.errors.length > 0; result.addLog(`Application bundle generation ${hasError ? 'failed' : 'complete'}.` + ` [${buildTime.toFixed(3)} seconds] - ${new Date().toISOString()}\n`); } return result; }, { watch: normalizedOptions.watch, preserveSymlinks: normalizedOptions.preserveSymlinks, poll: normalizedOptions.poll, cacheOptions: normalizedOptions.cacheOptions, outputOptions: normalizedOptions.outputOptions, verbose: normalizedOptions.verbose, projectRoot: normalizedOptions.projectRoot, workspaceRoot: normalizedOptions.workspaceRoot, progress: normalizedOptions.progress, clearScreen: normalizedOptions.clearScreen, colors: normalizedOptions.colors, jsonLogs: normalizedOptions.jsonLogs, incrementalResults: normalizedOptions.incrementalResults, logger, signal, }); } /** * Builds an application using the `application` builder with the provided * options. * * Usage of the `extensions` parameter is NOT supported and may cause unexpected * build output or build failures. * * @experimental Direct usage of this function is considered experimental. * * @param options The options defined by the builder's schema to use. * @param context An Architect builder context instance. * @param extensions An object contain extension points for the build. * @returns The build output results of the build. */ async function* buildApplication(options, context, extensions) { let initial = true; const internalOptions = { ...options, incrementalResults: true }; for await (const result of buildApplicationInternal(internalOptions, context, extensions)) { const outputOptions = result.detail?.['outputOptions']; if (initial) { initial = false; // Clean the output location if requested. // Output options may not be present if the build failed. if (outputOptions?.clean) { await (0, delete_output_dir_1.deleteOutputDir)(context.workspaceRoot, outputOptions.base, [ outputOptions.browser, outputOptions.server, ]); } } if (result.kind === results_1.ResultKind.Failure) { yield { success: false }; continue; } (0, node_assert_1.default)(outputOptions, 'Application output options are required for builder usage.'); (0, node_assert_1.default)(result.kind === results_1.ResultKind.Full || result.kind === results_1.ResultKind.Incremental, 'Application build did not provide a file result output.'); // TODO: Restructure output logging to better handle stdout JSON piping if (!environment_options_1.useJSONBuildLogs) { context.logger.info(`Output location: ${outputOptions.base}\n`); } // Writes the output files to disk and ensures the containing directories are present const directoryExists = new Set(); await (0, utils_1.emitFilesToDisk)(Object.entries(result.files), async ([filePath, file]) => { if (outputOptions.ignoreServer && (file.type === bundler_context_1.BuildOutputFileType.ServerApplication || file.type === bundler_context_1.BuildOutputFileType.ServerRoot)) { return; } const fullFilePath = generateFullPath(filePath, file.type, outputOptions); // Ensure output subdirectories exist const fileBasePath = node_path_1.default.dirname(fullFilePath); if (fileBasePath && !directoryExists.has(fileBasePath)) { await promises_1.default.mkdir(fileBasePath, { recursive: true }); directoryExists.add(fileBasePath); } if (file.origin === 'memory') { // Write file contents await promises_1.default.writeFile(fullFilePath, file.contents); } else { // Copy file contents if (isNodeV22orHigher) { // Use newer `cp` API on Node.js 22+ (minimum v22 for CLI is 22.11) await promises_1.default.cp(file.inputPath, fullFilePath, { mode: promises_1.default.constants.COPYFILE_FICLONE, preserveTimestamps: true, }); } else { // For Node.js 20 use `copyFile` (`cp` is not stable for v20) // TODO: Remove when Node.js 20 is no longer supported await promises_1.default.copyFile(file.inputPath, fullFilePath, promises_1.default.constants.COPYFILE_FICLONE); } } }); // Delete any removed files if incremental if (result.kind === results_1.ResultKind.Incremental && result.removed?.length) { await Promise.all(result.removed.map((file) => { const fullFilePath = generateFullPath(file.path, file.type, outputOptions); return promises_1.default.rm(fullFilePath, { force: true, maxRetries: 3 }); })); } yield { success: true }; } } function generateFullPath(filePath, type, outputOptions) { let typeDirectory; switch (type) { case bundler_context_1.BuildOutputFileType.Browser: case bundler_context_1.BuildOutputFileType.Media: typeDirectory = outputOptions.browser; break; case bundler_context_1.BuildOutputFileType.ServerApplication: case bundler_context_1.BuildOutputFileType.ServerRoot: typeDirectory = outputOptions.server; break; case bundler_context_1.BuildOutputFileType.Root: typeDirectory = ''; break; default: throw new Error(`Unhandled write for file "${filePath}" with type "${bundler_context_1.BuildOutputFileType[type]}".`); } // NOTE: 'base' is a fully resolved path at this point const fullFilePath = node_path_1.default.join(outputOptions.base, typeDirectory, filePath); return fullFilePath; } const builder = (0, architect_1.createBuilder)(buildApplication); exports.default = builder; //# sourceMappingURL=index.js.map