@angular-devkit/build-angular
Version:
Angular Webpack Build Facade
227 lines (226 loc) • 11.1 kB
JavaScript
/**
* @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'),
},
},
],
},
}
: {};
}
;