UNPKG

@angular-devkit/build-angular

Version:
227 lines (226 loc) 11.1 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.execute = execute; const private_1 = require("@angular/build/private"); const architect_1 = require("@angular-devkit/architect"); const build_webpack_1 = require("@angular-devkit/build-webpack"); const promises_1 = require("node:fs/promises"); const path = __importStar(require("node:path")); const rxjs_1 = require("rxjs"); const webpack_1 = __importDefault(require("webpack")); const configs_1 = require("../../tools/webpack/configs"); const helpers_1 = require("../../tools/webpack/utils/helpers"); const stats_1 = require("../../tools/webpack/utils/stats"); const utils_1 = require("../../utils"); const color_1 = require("../../utils/color"); const copy_assets_1 = require("../../utils/copy-assets"); const error_1 = require("../../utils/error"); const i18n_inlining_1 = require("../../utils/i18n-inlining"); const output_paths_1 = require("../../utils/output-paths"); const spinner_1 = require("../../utils/spinner"); const webpack_browser_config_1 = require("../../utils/webpack-browser-config"); /** * @experimental Direct usage of this function is considered experimental. */ function execute(options, context, transforms = {}) { const root = context.workspaceRoot; // Check Angular version. (0, private_1.assertCompatibleAngularVersion)(root); const baseOutputPath = path.resolve(root, options.outputPath); let outputPaths; return (0, rxjs_1.from)(initialize(options, context, transforms.webpackConfiguration)).pipe((0, rxjs_1.concatMap)(({ config, i18n, projectRoot, projectSourceRoot }) => { return (0, build_webpack_1.runWebpack)(config, context, { webpackFactory: require('webpack'), logging: (stats, config) => { if (options.verbose && config.stats !== false) { const statsOptions = config.stats === true ? undefined : config.stats; context.logger.info(stats.toString(statsOptions)); } }, }).pipe((0, rxjs_1.concatMap)(async (output) => { const { emittedFiles = [], outputPath, webpackStats, success } = output; if (!webpackStats) { throw new Error('Webpack stats build result is required.'); } if (!success) { if ((0, stats_1.statsHasWarnings)(webpackStats)) { context.logger.warn((0, stats_1.statsWarningsToString)(webpackStats, { colors: true })); } if ((0, stats_1.statsHasErrors)(webpackStats)) { context.logger.error((0, stats_1.statsErrorsToString)(webpackStats, { colors: true })); } return output; } const spinner = new spinner_1.Spinner(); spinner.enabled = options.progress !== false; outputPaths = (0, output_paths_1.ensureOutputPaths)(baseOutputPath, i18n); // Copy assets if (!options.watch && options.assets?.length) { spinner.start('Copying assets...'); try { await (0, copy_assets_1.copyAssets)((0, utils_1.normalizeAssetPatterns)(options.assets, context.workspaceRoot, projectRoot, projectSourceRoot), Array.from(outputPaths.values()), context.workspaceRoot); spinner.succeed('Copying assets complete.'); } catch (err) { spinner.fail(color_1.colors.redBright('Copying of assets failed.')); (0, error_1.assertIsError)(err); return { ...output, success: false, error: 'Unable to copy assets: ' + err.message, }; } } if (i18n.shouldInline) { const success = await (0, i18n_inlining_1.i18nInlineEmittedFiles)(context, emittedFiles, i18n, baseOutputPath, Array.from(outputPaths.values()), [], outputPath, options.i18nMissingTranslation); if (!success) { return { ...output, success: false, }; } } (0, stats_1.webpackStatsLogger)(context.logger, webpackStats, config); return output; })); }), (0, rxjs_1.concatMap)(async (output) => { if (!output.success) { return output; } return { ...output, baseOutputPath, outputs: (outputPaths && [...outputPaths.entries()].map(([locale, path]) => ({ locale, path, }))) || { path: baseOutputPath, }, }; })); } exports.default = (0, architect_1.createBuilder)(execute); async function initialize(options, context, webpackConfigurationTransform) { // Purge old build disk cache. await (0, private_1.purgeStaleBuildCache)(context); await checkTsConfigForPreserveWhitespacesSetting(context, options.tsConfig); const browserslist = (await Promise.resolve().then(() => __importStar(require('browserslist')))).default; const originalOutputPath = options.outputPath; // Assets are processed directly by the builder except when watching const adjustedOptions = options.watch ? options : { ...options, assets: [] }; const { config, projectRoot, projectSourceRoot, i18n } = await (0, webpack_browser_config_1.generateI18nBrowserWebpackConfigFromContext)({ ...adjustedOptions, aot: true, platform: 'server', }, context, (wco) => { // We use the platform to determine the JavaScript syntax output. wco.buildOptions.supportedBrowsers ??= []; wco.buildOptions.supportedBrowsers.push(...browserslist('maintained node versions')); return [ getPlatformServerExportsConfig(wco), (0, configs_1.getCommonConfig)(wco), (0, configs_1.getStylesConfig)(wco), { plugins: [ new webpack_1.default.DefinePlugin({ 'ngServerMode': true, }), ], }, ]; }); if (options.deleteOutputPath) { await (0, utils_1.deleteOutputDir)(context.workspaceRoot, originalOutputPath); } const transformedConfig = (await webpackConfigurationTransform?.(config)) ?? config; return { config: transformedConfig, i18n, projectRoot, projectSourceRoot }; } async function checkTsConfigForPreserveWhitespacesSetting(context, tsConfigPath) { // We don't use the `readTsConfig` method on purpose here. // To only catch cases were `preserveWhitespaces` is set directly in the `tsconfig.server.json`, // which in the majority of cases will cause a mistmatch between client and server builds. // Technically we should check if `tsconfig.server.json` and `tsconfig.app.json` values match. // But: // 1. It is not guaranteed that `tsconfig.app.json` is used to build the client side of this app. // 2. There is no easy way to access the build build config from the server builder. // 4. This will no longer be an issue with a single compilation model were the same tsconfig is used for both browser and server builds. const content = await (0, promises_1.readFile)(path.join(context.workspaceRoot, tsConfigPath), 'utf-8'); const { parse } = await Promise.resolve().then(() => __importStar(require('jsonc-parser'))); const tsConfig = parse(content, [], { allowTrailingComma: true }); if (tsConfig.angularCompilerOptions?.preserveWhitespaces !== undefined) { context.logger.warn(`"preserveWhitespaces" was set in "${tsConfigPath}". ` + 'Make sure that this setting is set consistently in both "tsconfig.server.json" for your server side ' + 'and "tsconfig.app.json" for your client side. A mismatched value will cause hydration to break.\n' + 'For more information see: https://angular.dev/guide/hydration#preserve-whitespaces-configuration'); } } /** * Add `@angular/platform-server` exports. * This is needed so that DI tokens can be referenced and set at runtime outside of the bundle. */ function getPlatformServerExportsConfig(wco) { // Add `@angular/platform-server` exports. // This is needed so that DI tokens can be referenced and set at runtime outside of the bundle. // Only add `@angular/platform-server` exports when it is installed. // In some cases this builder is used when `@angular/platform-server` is not installed. // Example: when using `@nguniversal/common/clover` which does not need `@angular/platform-server`. return (0, helpers_1.isPackageInstalled)(wco.root, '@angular/platform-server') ? { module: { rules: [ { loader: require.resolve('./platform-server-exports-loader'), include: [path.resolve(wco.root, wco.buildOptions.main)], options: { angularSSRInstalled: (0, helpers_1.isPackageInstalled)(wco.root, '@angular/ssr'), }, }, ], }, } : {}; }