UNPKG

@angular-devkit/build-angular

Version:
434 lines (433 loc) 20.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; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.getCommonConfig = getCommonConfig; const webpack_1 = require("@ngtools/webpack"); const copy_webpack_plugin_1 = __importDefault(require("copy-webpack-plugin")); const path = __importStar(require("node:path")); const webpack_2 = require("webpack"); const webpack_subresource_integrity_1 = require("webpack-subresource-integrity"); const environment_options_1 = require("../../../utils/environment-options"); const load_esm_1 = require("../../../utils/load-esm"); const plugins_1 = require("../plugins"); const devtools_ignore_plugin_1 = require("../plugins/devtools-ignore-plugin"); const named_chunks_plugin_1 = require("../plugins/named-chunks-plugin"); const occurrences_plugin_1 = require("../plugins/occurrences-plugin"); const progress_plugin_1 = require("../plugins/progress-plugin"); const transfer_size_plugin_1 = require("../plugins/transfer-size-plugin"); const typescript_1 = require("../plugins/typescript"); const watch_files_logs_plugin_1 = require("../plugins/watch-files-logs-plugin"); const helpers_1 = require("../utils/helpers"); const VENDORS_TEST = /[\\/]node_modules[\\/]/; // eslint-disable-next-line max-lines-per-function async function getCommonConfig(wco) { const { root, projectRoot, buildOptions, tsConfig, projectName, sourceRoot, tsConfigPath } = wco; const { cache, codeCoverage, crossOrigin = 'none', platform = 'browser', aot = true, codeCoverageExclude = [], main, sourceMap: { styles: stylesSourceMap, scripts: scriptsSourceMap, vendor: vendorSourceMap, hidden: hiddenSourceMap, }, optimization: { styles: stylesOptimization, scripts: scriptsOptimization }, commonChunk, vendorChunk, subresourceIntegrity, verbose, poll, webWorkerTsConfig, externalDependencies = [], allowedCommonJsDependencies, } = buildOptions; const isPlatformServer = buildOptions.platform === 'server'; const extraPlugins = []; const extraRules = []; const entryPoints = {}; // Load ESM `@angular/compiler-cli` using the TypeScript dynamic import workaround. // Once TypeScript provides support for keeping the dynamic import this workaround can be // changed to a direct dynamic import. const { VERSION: NG_VERSION } = await (0, load_esm_1.loadEsmModule)('@angular/compiler-cli'); const { GLOBAL_DEFS_FOR_TERSER, GLOBAL_DEFS_FOR_TERSER_WITH_AOT } = await (0, load_esm_1.loadEsmModule)('@angular/compiler-cli/private/tooling'); // determine hashing format const hashFormat = (0, helpers_1.getOutputHashFormat)(buildOptions.outputHashing); if (buildOptions.progress) { extraPlugins.push(new progress_plugin_1.ProgressPlugin(platform)); } const localizePackageInitEntryPoint = '@angular/localize/init'; const hasLocalizeType = tsConfig.options.types?.some((t) => t === '@angular/localize' || t === localizePackageInitEntryPoint); if (hasLocalizeType) { entryPoints['main'] = [localizePackageInitEntryPoint]; } if (buildOptions.main) { const mainPath = path.resolve(root, buildOptions.main); if (Array.isArray(entryPoints['main'])) { entryPoints['main'].push(mainPath); } else { entryPoints['main'] = [mainPath]; } } if (isPlatformServer) { // Fixes Critical dependency: the request of a dependency is an expression extraPlugins.push(new webpack_2.ContextReplacementPlugin(/@?hapi|express[\\/]/)); if ((0, helpers_1.isPackageInstalled)(wco.root, '@angular/platform-server') && Array.isArray(entryPoints['main'])) { // This import must come before any imports (direct or transitive) that rely on DOM built-ins being // available, such as `@angular/elements`. entryPoints['main'].unshift('@angular/platform-server/init'); } } const polyfills = [...buildOptions.polyfills]; if (!aot) { polyfills.push('@angular/compiler'); } if (polyfills.length) { // `zone.js/testing` is a **special** polyfill because when not imported in the main it fails with the below errors: // `Error: Expected to be running in 'ProxyZone', but it was not found.` // This was also the reason why previously it was imported in `test.ts` as the first module. // From Jia li: // This is because the jasmine functions such as beforeEach/it will not be patched by zone.js since // jasmine will not be loaded yet, so the ProxyZone will not be there. We have to load zone-testing.js after // jasmine is ready. // We could force loading 'zone.js/testing' prior to jasmine by changing the order of scripts in 'karma-context.html'. // But this has it's own problems as zone.js needs to be loaded prior to jasmine due to patching of timing functions // See: https://github.com/jasmine/jasmine/issues/1944 // Thus the correct order is zone.js -> jasmine -> zone.js/testing. const zoneTestingEntryPoint = 'zone.js/testing'; const polyfillsExludingZoneTesting = polyfills.filter((p) => p !== zoneTestingEntryPoint); if (Array.isArray(entryPoints['polyfills'])) { entryPoints['polyfills'].push(...polyfillsExludingZoneTesting); } else { entryPoints['polyfills'] = polyfillsExludingZoneTesting; } if (polyfillsExludingZoneTesting.length !== polyfills.length) { if (Array.isArray(entryPoints['main'])) { entryPoints['main'].unshift(zoneTestingEntryPoint); } else { entryPoints['main'] = [zoneTestingEntryPoint]; } } } if (allowedCommonJsDependencies) { // When this is not defined it means the builder doesn't support showing common js usages. // When it does it will be an array. extraPlugins.push(new plugins_1.CommonJsUsageWarnPlugin({ allowedDependencies: allowedCommonJsDependencies, })); } // process global scripts // Add a new asset for each entry. for (const { bundleName, inject, paths } of (0, helpers_1.globalScriptsByBundleName)(buildOptions.scripts)) { // Lazy scripts don't get a hash, otherwise they can't be loaded by name. const hash = inject ? hashFormat.script : ''; extraPlugins.push(new plugins_1.ScriptsWebpackPlugin({ name: bundleName, sourceMap: scriptsSourceMap, scripts: paths, filename: `${path.basename(bundleName)}${hash}.js`, basePath: root, })); } // process asset entries if (buildOptions.assets.length) { extraPlugins.push(new copy_webpack_plugin_1.default({ patterns: (0, helpers_1.assetPatterns)(root, buildOptions.assets), })); } if (buildOptions.extractLicenses) { const LicenseWebpackPlugin = require('license-webpack-plugin').LicenseWebpackPlugin; extraPlugins.push(new LicenseWebpackPlugin({ stats: { warnings: false, errors: false, }, perChunkOutput: false, outputFilename: '3rdpartylicenses.txt', skipChildCompilers: true, })); } if (scriptsSourceMap || stylesSourceMap) { const include = []; if (scriptsSourceMap) { include.push(/js$/); } if (stylesSourceMap) { include.push(/css$/); } extraPlugins.push(new devtools_ignore_plugin_1.DevToolsIgnorePlugin()); extraPlugins.push(new webpack_2.SourceMapDevToolPlugin({ filename: '[file].map', include, // We want to set sourceRoot to `webpack:///` for non // inline sourcemaps as otherwise paths to sourcemaps will be broken in browser // `webpack:///` is needed for Visual Studio breakpoints to work properly as currently // there is no way to set the 'webRoot' sourceRoot: 'webpack:///', moduleFilenameTemplate: '[resource-path]', append: hiddenSourceMap ? false : undefined, })); } if (verbose) { extraPlugins.push(new watch_files_logs_plugin_1.WatchFilesLogsPlugin()); } if (buildOptions.statsJson) { extraPlugins.push(new plugins_1.JsonStatsPlugin(path.resolve(root, buildOptions.outputPath, 'stats.json'))); } if (subresourceIntegrity) { extraPlugins.push(new webpack_subresource_integrity_1.SubresourceIntegrityPlugin({ hashFuncNames: ['sha384'], })); } if (scriptsSourceMap || stylesSourceMap) { extraRules.push({ test: /\.[cm]?jsx?$/, enforce: 'pre', loader: require.resolve('source-map-loader'), options: { filterSourceMappingUrl: (_mapUri, resourcePath) => { if (vendorSourceMap) { // Consume all sourcemaps when vendor option is enabled. return true; } // Don't consume sourcemaps in node_modules when vendor is disabled. // But, do consume local libraries sourcemaps. return !resourcePath.includes('node_modules'); }, }, }); } if (main || polyfills) { extraRules.push({ test: tsConfig.options.allowJs ? /\.[cm]?[tj]sx?$/ : /\.[cm]?tsx?$/, loader: webpack_1.AngularWebpackLoaderPath, // The below are known paths that are not part of the TypeScript compilation even when allowJs is enabled. exclude: [ /[\\/]node_modules[/\\](?:css-loader|mini-css-extract-plugin|webpack-dev-server|webpack)[/\\]/, ], }); extraPlugins.push((0, typescript_1.createIvyPlugin)(wco, aot, tsConfigPath)); } if (webWorkerTsConfig) { extraPlugins.push((0, typescript_1.createIvyPlugin)(wco, false, path.resolve(wco.root, webWorkerTsConfig))); } const extraMinimizers = []; if (scriptsOptimization) { extraMinimizers.push(new plugins_1.JavaScriptOptimizerPlugin({ define: { ...(buildOptions.aot ? GLOBAL_DEFS_FOR_TERSER_WITH_AOT : GLOBAL_DEFS_FOR_TERSER), 'ngServerMode': isPlatformServer, }, sourcemap: scriptsSourceMap, supportedBrowsers: buildOptions.supportedBrowsers, keepIdentifierNames: !environment_options_1.allowMangle || isPlatformServer, removeLicenses: buildOptions.extractLicenses, advanced: buildOptions.buildOptimizer, })); } if (platform === 'browser' && (scriptsOptimization || stylesOptimization.minify)) { extraMinimizers.push(new transfer_size_plugin_1.TransferSizePlugin()); } let crossOriginLoading = false; if (subresourceIntegrity && crossOrigin === 'none') { crossOriginLoading = 'anonymous'; } else if (crossOrigin !== 'none') { crossOriginLoading = crossOrigin; } return { mode: scriptsOptimization || stylesOptimization.minify ? 'production' : 'development', devtool: false, target: [isPlatformServer ? 'node' : 'web', 'es2015'], profile: buildOptions.statsJson, resolve: { roots: [projectRoot], extensions: ['.ts', '.tsx', '.mjs', '.js'], symlinks: !buildOptions.preserveSymlinks, modules: [tsConfig.options.baseUrl || projectRoot, 'node_modules'], mainFields: isPlatformServer ? ['es2020', 'es2015', 'module', 'main'] : ['es2020', 'es2015', 'browser', 'module', 'main'], conditionNames: ['es2020', 'es2015', '...'], }, resolveLoader: { symlinks: !buildOptions.preserveSymlinks, }, context: root, entry: entryPoints, externals: externalDependencies, output: { uniqueName: projectName, hashFunction: 'xxhash64', // todo: remove in webpack 6. This is part of `futureDefaults`. clean: buildOptions.deleteOutputPath ?? true, path: path.resolve(root, buildOptions.outputPath), publicPath: buildOptions.deployUrl ?? '', filename: `[name]${hashFormat.chunk}.js`, chunkFilename: `[name]${hashFormat.chunk}.js`, libraryTarget: isPlatformServer ? 'commonjs' : undefined, crossOriginLoading, trustedTypes: 'angular#bundler', scriptType: 'module', }, watch: buildOptions.watch, watchOptions: { poll, // The below is needed as when preserveSymlinks is enabled we disable `resolve.symlinks`. followSymlinks: buildOptions.preserveSymlinks, ignored: poll === undefined ? undefined : '**/node_modules/**', }, snapshot: { module: { // Use hash of content instead of timestamp because the timestamp of the symlink will be used // instead of the referenced files which causes changes in symlinks not to be picked up. hash: buildOptions.preserveSymlinks, }, }, performance: { hints: false, }, ignoreWarnings: [ // https://github.com/webpack-contrib/source-map-loader/blob/b2de4249c7431dd8432da607e08f0f65e9d64219/src/index.js#L83 /Failed to parse source map from/, // https://github.com/webpack-contrib/postcss-loader/blob/bd261875fdf9c596af4ffb3a1a73fe3c549befda/src/index.js#L153-L158 /Add postcss as project dependency/, // esbuild will issue a warning, while still hoists the @charset at the very top. // This is caused by a bug in css-loader https://github.com/webpack-contrib/css-loader/issues/1212 /"@charset" must be the first rule in the file/, ], module: { // Show an error for missing exports instead of a warning. strictExportPresence: true, parser: { javascript: { requireContext: false, // Disable auto URL asset module creation. This doesn't effect `new Worker(new URL(...))` // https://webpack.js.org/guides/asset-modules/#url-assets url: false, worker: !!webWorkerTsConfig, }, }, rules: [ { test: /\.?(svg|html)$/, // Only process HTML and SVG which are known Angular component resources. resourceQuery: /\?ngResource/, type: 'asset/source', }, { // Mark files inside `rxjs/add` as containing side effects. // If this is fixed upstream and the fixed version becomes the minimum // supported version, this can be removed. test: /[/\\]rxjs[/\\]add[/\\].+\.js$/, sideEffects: true, }, { test: /\.[cm]?[tj]sx?$/, // The below is needed due to a bug in `@babel/runtime`. See: https://github.com/babel/babel/issues/12824 resolve: { fullySpecified: false }, exclude: [ /[\\/]node_modules[/\\](?:core-js|@babel|tslib|web-animations-js|web-streams-polyfill|whatwg-url)[/\\]/, ], use: [ { loader: require.resolve('../../babel/webpack-loader'), options: { cacheDirectory: (cache.enabled && path.join(cache.path, 'babel-webpack')) || false, aot: buildOptions.aot, optimize: buildOptions.buildOptimizer, supportedBrowsers: buildOptions.supportedBrowsers, instrumentCode: codeCoverage ? { includedBasePath: sourceRoot ?? projectRoot, excludedPaths: (0, helpers_1.getInstrumentationExcludedPaths)(root, codeCoverageExclude), } : undefined, }, }, ], }, ...extraRules, ], }, experiments: { backCompat: false, syncWebAssembly: true, asyncWebAssembly: true, topLevelAwait: false, }, infrastructureLogging: { debug: verbose, level: verbose ? 'verbose' : 'error', }, stats: (0, helpers_1.getStatsOptions)(verbose), cache: (0, helpers_1.getCacheSettings)(wco, NG_VERSION.full), optimization: { minimizer: extraMinimizers, moduleIds: 'deterministic', chunkIds: buildOptions.namedChunks ? 'named' : 'deterministic', emitOnErrors: false, runtimeChunk: isPlatformServer ? false : 'single', splitChunks: { maxAsyncRequests: Infinity, cacheGroups: { default: !!commonChunk && { chunks: 'async', minChunks: 2, priority: 10, }, common: !!commonChunk && { name: 'common', chunks: 'async', minChunks: 2, enforce: true, priority: 5, }, vendors: false, defaultVendors: !!vendorChunk && { name: 'vendor', chunks: (chunk) => chunk.name === 'main', enforce: true, test: VENDORS_TEST, }, }, }, }, plugins: [ new named_chunks_plugin_1.NamedChunksPlugin(), new occurrences_plugin_1.OccurrencesPlugin({ aot, scriptsOptimization, }), new plugins_1.DedupeModuleResolvePlugin({ verbose }), ...extraPlugins, ], node: false, }; }