UNPKG

@angular-devkit/build-angular

Version:
297 lines (295 loc) 14.4 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; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.serveWebpackBrowser = serveWebpackBrowser; const private_1 = require("@angular/build/private"); const build_webpack_1 = require("@angular-devkit/build-webpack"); const core_1 = require("@angular-devkit/core"); const path = __importStar(require("node:path")); const url = __importStar(require("node:url")); const rxjs_1 = require("rxjs"); const configs_1 = require("../../tools/webpack/configs"); const index_html_webpack_plugin_1 = require("../../tools/webpack/plugins/index-html-webpack-plugin"); const service_worker_plugin_1 = require("../../tools/webpack/plugins/service-worker-plugin"); const stats_1 = require("../../tools/webpack/utils/stats"); const utils_1 = require("../../utils"); const color_1 = require("../../utils/color"); const i18n_webpack_1 = require("../../utils/i18n-webpack"); const load_esm_1 = require("../../utils/load-esm"); const package_chunk_sort_1 = require("../../utils/package-chunk-sort"); const webpack_browser_config_1 = require("../../utils/webpack-browser-config"); const webpack_diagnostics_1 = require("../../utils/webpack-diagnostics"); const schema_1 = require("../browser/schema"); /** * Reusable implementation of the Angular Webpack development server builder. * @param options Dev Server options. * @param builderName The name of the builder used to build the application. * @param context The build context. * @param transforms A map of transforms that can be used to hook into some logic (such as * transforming webpack configuration before passing it to webpack). */ // eslint-disable-next-line max-lines-per-function function serveWebpackBrowser(options, builderName, context, transforms = {}) { // Check Angular version. const { logger, workspaceRoot } = context; (0, private_1.assertCompatibleAngularVersion)(workspaceRoot); async function setup() { if (options.hmr) { logger.warn(core_1.tags.stripIndents `NOTICE: Hot Module Replacement (HMR) is enabled for the dev server. See https://webpack.js.org/guides/hot-module-replacement for information on working with HMR for Webpack.`); } // Get the browser configuration from the target name. const rawBrowserOptions = await context.getTargetOptions(options.buildTarget); if (rawBrowserOptions.outputHashing && rawBrowserOptions.outputHashing !== schema_1.OutputHashing.None) { // Disable output hashing for dev build as this can cause memory leaks // See: https://github.com/webpack/webpack-dev-server/issues/377#issuecomment-241258405 rawBrowserOptions.outputHashing = schema_1.OutputHashing.None; logger.warn(`Warning: 'outputHashing' option is disabled when using the dev-server.`); } // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion const browserOptions = (await context.validateOptions({ ...rawBrowserOptions, watch: options.watch, verbose: options.verbose, // In dev server we should not have budgets because of extra libs such as socks-js budgets: undefined, }, builderName)); const { styles, scripts } = (0, utils_1.normalizeOptimization)(browserOptions.optimization); if (scripts || styles.minify) { logger.error(core_1.tags.stripIndents ` **************************************************************************************** This is a simple server for use in testing or debugging Angular applications locally. It hasn't been reviewed for security issues. DON'T USE IT FOR PRODUCTION! **************************************************************************************** `); } const { config, i18n } = await (0, webpack_browser_config_1.generateI18nBrowserWebpackConfigFromContext)(browserOptions, context, (wco) => [(0, configs_1.getDevServerConfig)(wco), (0, configs_1.getCommonConfig)(wco), (0, configs_1.getStylesConfig)(wco)], options); if (!config.devServer) { throw new Error('Webpack Dev Server configuration was not set.'); } let locale; if (i18n.shouldInline) { // Dev-server only supports one locale locale = [...i18n.inlineLocales][0]; } else if (i18n.hasDefinedSourceLocale) { // use source locale if not localizing locale = i18n.sourceLocale; } let webpackConfig = config; // If a locale is defined, setup localization if (locale) { if (i18n.inlineLocales.size > 1) { throw new Error('The development server only supports localizing a single locale per build.'); } await setupLocalize(locale, i18n, browserOptions, webpackConfig, options.cacheOptions, context); } if (transforms.webpackConfiguration) { webpackConfig = await transforms.webpackConfiguration(webpackConfig); } webpackConfig.plugins ??= []; if (browserOptions.index) { const { scripts = [], styles = [], baseHref } = browserOptions; const entrypoints = (0, package_chunk_sort_1.generateEntryPoints)({ scripts, styles, // The below is needed as otherwise HMR for CSS will break. // styles.js and runtime.js needs to be loaded as a non-module scripts as otherwise `document.currentScript` will be null. // https://github.com/webpack-contrib/mini-css-extract-plugin/blob/90445dd1d81da0c10b9b0e8a17b417d0651816b8/src/hmr/hotModuleReplacement.js#L39 isHMREnabled: !!webpackConfig.devServer?.hot, }); webpackConfig.plugins.push(new index_html_webpack_plugin_1.IndexHtmlWebpackPlugin({ indexPath: path.resolve(workspaceRoot, (0, webpack_browser_config_1.getIndexInputFile)(browserOptions.index)), outputPath: (0, webpack_browser_config_1.getIndexOutputFile)(browserOptions.index), baseHref, entrypoints, deployUrl: browserOptions.deployUrl, sri: browserOptions.subresourceIntegrity, cache: options.cacheOptions, postTransform: transforms.indexHtml, optimization: (0, utils_1.normalizeOptimization)(browserOptions.optimization), crossOrigin: browserOptions.crossOrigin, lang: locale, })); } if (browserOptions.serviceWorker) { webpackConfig.plugins.push(new service_worker_plugin_1.ServiceWorkerPlugin({ baseHref: browserOptions.baseHref, root: context.workspaceRoot, projectRoot: options.projectRoot, ngswConfigPath: browserOptions.ngswConfigPath, })); } return { browserOptions, webpackConfig, }; } return (0, rxjs_1.from)(setup()).pipe((0, rxjs_1.switchMap)(({ browserOptions, webpackConfig }) => { return (0, build_webpack_1.runWebpackDevServer)(webpackConfig, context, { logging: transforms.logging || (0, stats_1.createWebpackLoggingCallback)(browserOptions, logger), webpackFactory: require('webpack'), webpackDevServerFactory: require('webpack-dev-server'), }).pipe((0, rxjs_1.concatMap)(async (buildEvent, index) => { const webpackRawStats = buildEvent.webpackStats; if (!webpackRawStats) { throw new Error('Webpack stats build result is required.'); } // Resolve serve address. const publicPath = webpackConfig.devServer?.devMiddleware?.publicPath; const serverAddress = url.format({ protocol: options.ssl ? 'https' : 'http', hostname: options.host === '0.0.0.0' ? 'localhost' : options.host, port: buildEvent.port, pathname: typeof publicPath === 'string' ? publicPath : undefined, }); if (index === 0) { logger.info('\n' + core_1.tags.oneLine ` ** Angular Live Development Server is listening on ${options.host}:${buildEvent.port}, open your browser on ${serverAddress} ** ` + '\n'); if (options.open) { const open = (await (0, load_esm_1.loadEsmModule)('open')).default; await open(serverAddress); } } if (buildEvent.success) { logger.info(`\n${color_1.colors.greenBright(color_1.colors.symbols.check)} Compiled successfully.`); } else { logger.info(`\n${color_1.colors.redBright(color_1.colors.symbols.cross)} Failed to compile.`); } return { ...buildEvent, baseUrl: serverAddress, stats: (0, stats_1.generateBuildEventStats)(webpackRawStats, browserOptions), }; })); })); } async function setupLocalize(locale, i18n, browserOptions, webpackConfig, cacheOptions, context) { const localeDescription = i18n.locales[locale]; // Modify main entrypoint to include locale data if (localeDescription?.dataPath && typeof webpackConfig.entry === 'object' && !Array.isArray(webpackConfig.entry) && webpackConfig.entry['main']) { if (Array.isArray(webpackConfig.entry['main'])) { webpackConfig.entry['main'].unshift(localeDescription.dataPath); } else { webpackConfig.entry['main'] = [ localeDescription.dataPath, webpackConfig.entry['main'], ]; } } let missingTranslationBehavior = browserOptions.i18nMissingTranslation || 'ignore'; let translation = localeDescription?.translation || {}; if (locale === i18n.sourceLocale) { missingTranslationBehavior = 'ignore'; translation = {}; } const i18nLoaderOptions = { locale, missingTranslationBehavior, translation: i18n.shouldInline ? translation : undefined, translationFiles: localeDescription?.files.map((file) => path.resolve(context.workspaceRoot, file.path)), }; const i18nRule = { test: /\.[cm]?[tj]sx?$/, enforce: 'post', use: [ { loader: require.resolve('../../tools/babel/webpack-loader'), options: { cacheDirectory: (cacheOptions.enabled && path.join(cacheOptions.path, 'babel-dev-server-i18n')) || false, cacheIdentifier: JSON.stringify({ locale, translationIntegrity: localeDescription?.files.map((file) => file.integrity), }), i18n: i18nLoaderOptions, }, }, ], }; // Get the rules and ensure the Webpack configuration is setup properly const rules = webpackConfig.module?.rules || []; if (!webpackConfig.module) { webpackConfig.module = { rules }; } else if (!webpackConfig.module.rules) { webpackConfig.module.rules = rules; } rules.push(i18nRule); // Add a plugin to reload translation files on rebuilds const loader = await (0, private_1.createTranslationLoader)(); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion webpackConfig.plugins.push({ apply: (compiler) => { compiler.hooks.thisCompilation.tap('build-angular', (compilation) => { if (i18n.shouldInline && i18nLoaderOptions.translation === undefined) { // Reload translations (0, i18n_webpack_1.loadTranslations)(locale, localeDescription, context.workspaceRoot, loader, { warn(message) { (0, webpack_diagnostics_1.addWarning)(compilation, message); }, error(message) { (0, webpack_diagnostics_1.addError)(compilation, message); }, }, undefined, browserOptions.i18nDuplicateTranslation); i18nLoaderOptions.translation = localeDescription.translation ?? {}; } compilation.hooks.finishModules.tap('build-angular', () => { // After loaders are finished, clear out the now unneeded translations i18nLoaderOptions.translation = undefined; }); }); }, }); }