"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.io/license */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); }) : (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 (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __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 = void 0; const build_optimizer_1 = require("@angular-devkit/build-optimizer"); const compiler_cli_1 = require("@angular/compiler-cli"); const copy_webpack_plugin_1 = __importDefault(require("copy-webpack-plugin")); const crypto_1 = require("crypto"); const fs_1 = require("fs"); const path = __importStar(require("path")); const typescript_1 = require("typescript"); const webpack_1 = require("webpack"); const utils_1 = require("../../utils"); const cache_path_1 = require("../../utils/cache-path"); const environment_options_1 = require("../../utils/environment-options"); const find_up_1 = require("../../utils/find-up"); const spinner_1 = require("../../utils/spinner"); const webpack_diagnostics_1 = require("../../utils/webpack-diagnostics"); const plugins_1 = require("../plugins"); const javascript_optimizer_plugin_1 = require("../plugins/javascript-optimizer-plugin"); const helpers_1 = require("../utils/helpers"); // eslint-disable-next-line max-lines-per-function function getCommonConfig(wco) { var _a, _b; const { root, projectRoot, buildOptions, tsConfig } = wco; const { platform = 'browser', sourceMap: { styles: stylesSourceMap, scripts: scriptsSourceMap, vendor: vendorSourceMap }, optimization: { styles: stylesOptimization, scripts: scriptsOptimization }, } = buildOptions; const extraPlugins = []; const extraRules = []; const entryPoints = {}; // determine hashing format const hashFormat = helpers_1.getOutputHashFormat(buildOptions.outputHashing || 'none'); const buildBrowserFeatures = new utils_1.BuildBrowserFeatures(projectRoot); const targetInFileName = helpers_1.getEsVersionForFileName(tsConfig.options.target, buildOptions.differentialLoadingNeeded); if (buildOptions.progress) { const spinner = new spinner_1.Spinner(); spinner.start(`Generating ${platform} application bundles (phase: setup)...`); extraPlugins.push(new webpack_1.ProgressPlugin({ handler: (percentage, message) => { const phase = message ? ` (phase: ${message})` : ''; spinner.text = `Generating ${platform} application bundles${phase}...`; switch (percentage) { case 1: if (spinner.isSpinning) { spinner.succeed(`${platform.replace(/^\w/, (s) => s.toUpperCase())} application bundle generation complete.`); } break; case 0: if (!spinner.isSpinning) { spinner.start(); } break; } }, })); } if (buildOptions.main) { const mainPath = path.resolve(root, buildOptions.main); entryPoints['main'] = [mainPath]; } const differentialLoadingMode = buildOptions.differentialLoadingNeeded && !buildOptions.watch; if (platform !== 'server') { if (differentialLoadingMode || tsConfig.options.target === typescript_1.ScriptTarget.ES5) { if (buildBrowserFeatures.isEs5SupportNeeded()) { const polyfillsChunkName = 'polyfills-es5'; entryPoints[polyfillsChunkName] = [path.join(__dirname, '..', 'es5-polyfills.js')]; if (!buildOptions.aot) { if (differentialLoadingMode) { entryPoints[polyfillsChunkName].push(path.join(__dirname, '..', 'jit-polyfills.js')); } entryPoints[polyfillsChunkName].push(path.join(__dirname, '..', 'es5-jit-polyfills.js')); } // If not performing a full differential build the polyfills need to be added to ES5 bundle if (buildOptions.polyfills) { entryPoints[polyfillsChunkName].push(path.resolve(root, buildOptions.polyfills)); } } } if (buildOptions.polyfills) { const projectPolyfills = path.resolve(root, buildOptions.polyfills); if (entryPoints['polyfills']) { entryPoints['polyfills'].push(projectPolyfills); } else { entryPoints['polyfills'] = [projectPolyfills]; } } if (!buildOptions.aot) { const jitPolyfills = path.join(__dirname, '..', 'jit-polyfills.js'); if (entryPoints['polyfills']) { entryPoints['polyfills'].push(jitPolyfills); } else { entryPoints['polyfills'] = [jitPolyfills]; } } } if (environment_options_1.profilingEnabled) { extraPlugins.push(new webpack_1.debug.ProfilingPlugin({ outputPath: path.resolve(root, 'chrome-profiler-events.json'), })); } // process global scripts const globalScriptsByBundleName = helpers_1.normalizeExtraEntryPoints(buildOptions.scripts, 'scripts').reduce((prev, curr) => { const { bundleName, inject, input } = curr; let resolvedPath = path.resolve(root, input); if (!fs_1.existsSync(resolvedPath)) { try { resolvedPath = require.resolve(input, { paths: [root] }); } catch { throw new Error(`Script file ${input} does not exist.`); } } const existingEntry = prev.find((el) => el.bundleName === bundleName); if (existingEntry) { if (existingEntry.inject && !inject) { // All entries have to be lazy for the bundle to be lazy. throw new Error(`The ${bundleName} bundle is mixing injected and non-injected scripts.`); } existingEntry.paths.push(resolvedPath); } else { prev.push({ bundleName, inject, paths: [resolvedPath], }); } return prev; }, []); // Add a new asset for each entry. for (const script of globalScriptsByBundleName) { // Lazy scripts don't get a hash, otherwise they can't be loaded by name. const hash = script.inject ? hashFormat.script : ''; const bundleName = script.bundleName; extraPlugins.push(new plugins_1.ScriptsWebpackPlugin({ name: bundleName, sourceMap: scriptsSourceMap, filename: `${path.basename(bundleName)}${hash}.js`, scripts: script.paths, basePath: projectRoot, })); } // process asset entries if (buildOptions.assets.length) { const copyWebpackPluginPatterns = buildOptions.assets.map((asset, index) => { // Resolve input paths relative to workspace root and add slash at the end. // eslint-disable-next-line prefer-const let { input, output, ignore = [], glob } = asset; input = path.resolve(root, input).replace(/\\/g, '/'); input = input.endsWith('/') ? input : input + '/'; output = output.endsWith('/') ? output : output + '/'; if (output.startsWith('..')) { throw new Error('An asset cannot be written to a location outside of the output path.'); } return { context: input, // Now we remove starting slash to make Webpack place it from the output root. to: output.replace(/^\//, ''), from: glob, noErrorOnMissing: true, force: true, globOptions: { dot: true, followSymbolicLinks: !!asset.followSymlinks, ignore: [ '.gitkeep', '**/.DS_Store', '**/Thumbs.db', // Negate patterns needs to be absolute because copy-webpack-plugin uses absolute globs which // causes negate patterns not to match. // See: https://github.com/webpack-contrib/copy-webpack-plugin/issues/498#issuecomment-639327909 ...ignore, ].map((i) => path.posix.join(input, i)), }, priority: index, }; }); extraPlugins.push(new copy_webpack_plugin_1.default({ patterns: copyWebpackPluginPatterns, })); } if (buildOptions.showCircularDependencies) { const CircularDependencyPlugin = require('circular-dependency-plugin'); extraPlugins.push(new CircularDependencyPlugin({ exclude: /[\\\/]node_modules[\\\/]/, })); } 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 (buildOptions.statsJson) { extraPlugins.push(new (class { apply(compiler) { compiler.hooks.done.tapPromise('angular-cli-stats', async (stats) => { const { stringifyStream } = await Promise.resolve().then(() => __importStar(require('@discoveryjs/json-ext'))); const data = stats.toJson('verbose'); const statsOutputPath = path.resolve(root, buildOptions.outputPath, 'stats.json'); try { await fs_1.promises.mkdir(path.dirname(statsOutputPath), { recursive: true }); await new Promise((resolve, reject) => stringifyStream(data) .pipe(fs_1.createWriteStream(statsOutputPath)) .on('close', resolve) .on('error', reject)); } catch (error) { webpack_diagnostics_1.addError(stats.compilation, `Unable to write stats file: ${error.message || 'unknown error'}`); } }); } })()); } if (scriptsSourceMap || stylesSourceMap) { extraRules.push({ test: /\.m?js$/, 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'); }, }, }); } let buildOptimizerUseRule = []; if (buildOptions.buildOptimizer && wco.scriptTarget < typescript_1.ScriptTarget.ES2015) { extraPlugins.push(new build_optimizer_1.BuildOptimizerWebpackPlugin()); buildOptimizerUseRule = [ { loader: build_optimizer_1.buildOptimizerLoaderPath, options: { sourceMap: scriptsSourceMap }, }, ]; } const extraMinimizers = []; if (scriptsOptimization) { const globalScriptsNames = globalScriptsByBundleName.map((s) => s.bundleName); if (globalScriptsNames.length > 0) { // Script bundles are fully optimized here in one step since they are never downleveled. // They are shared between ES2015 & ES5 outputs so must support ES5. // The `terser-webpack-plugin` will add the minified flag to the asset which will prevent // additional optimizations by the next plugin. const TerserPlugin = require('terser-webpack-plugin'); extraMinimizers.push(new TerserPlugin({ parallel: environment_options_1.maxWorkers, extractComments: false, include: globalScriptsNames, terserOptions: { ecma: 5, compress: environment_options_1.allowMinify, output: { ascii_only: true, wrap_func_args: false, }, mangle: environment_options_1.allowMangle && platform !== 'server', }, })); } extraMinimizers.push(new javascript_optimizer_plugin_1.JavaScriptOptimizerPlugin({ define: buildOptions.aot ? compiler_cli_1.GLOBAL_DEFS_FOR_TERSER_WITH_AOT : compiler_cli_1.GLOBAL_DEFS_FOR_TERSER, sourcemap: scriptsSourceMap, target: wco.scriptTarget, keepNames: !environment_options_1.allowMangle || platform === 'server', removeLicenses: buildOptions.extractLicenses, advanced: buildOptions.buildOptimizer, })); } return { mode: scriptsOptimization || stylesOptimization.minify ? 'production' : 'development', devtool: false, target: [ platform === 'server' ? 'node' : 'web', tsConfig.options.target === typescript_1.ScriptTarget.ES5 || (platform !== 'server' && buildBrowserFeatures.isEs5SupportNeeded()) ? 'es5' : 'es2015', ], profile: buildOptions.statsJson, resolve: { roots: [projectRoot], extensions: ['.ts', '.tsx', '.mjs', '.js'], symlinks: !buildOptions.preserveSymlinks, modules: [tsConfig.options.baseUrl || projectRoot, 'node_modules'], }, resolveLoader: { symlinks: !buildOptions.preserveSymlinks, modules: [ // Allow loaders to be in a node_modules nested inside the devkit/build-angular package. // This is important in case loaders do not get hoisted. // If this file moves to another location, alter potentialNodeModules as well. 'node_modules', ...find_up_1.findAllNodeModules(__dirname, projectRoot), ], }, context: root, entry: entryPoints, output: { clean: (_a = buildOptions.deleteOutputPath) !== null && _a !== void 0 ? _a : true, path: path.resolve(root, buildOptions.outputPath), publicPath: (_b = buildOptions.deployUrl) !== null && _b !== void 0 ? _b : '', filename: ({ chunk }) => { if ((chunk === null || chunk === void 0 ? void 0 : chunk.name) === 'polyfills-es5') { return `polyfills-es5${hashFormat.chunk}.js`; } else { return `[name]${targetInFileName}${hashFormat.chunk}.js`; } }, chunkFilename: `[name]${targetInFileName}${hashFormat.chunk}.js`, }, watch: buildOptions.watch, watchOptions: helpers_1.getWatchOptions(buildOptions.poll), performance: { hints: false, }, ignoreWarnings: [ // Webpack 5+ has no facility to disable this warning. // System.import is used in @angular/core for deprecated string-form lazy routes /System.import\(\) is deprecated and will be removed soon/i, // 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/, ], module: { // Show an error for missing exports instead of a warning. strictExportPresence: true, rules: [ { // Mark files inside `@angular/core` as using SystemJS style dynamic imports. // Removing this will cause deprecation warnings to appear. test: /[\/\\]@angular[\/\\]core[\/\\].+\.js$/, parser: { system: true }, }, { // 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]?js$|\.tsx?$/, // The below is needed due to a bug in `@babel/runtime`. See: https://github.com/babel/babel/issues/12824 resolve: { fullySpecified: false }, exclude: [/[/\\](?:core-js|@babel|tslib|web-animations-js|web-streams-polyfill)[/\\]/], use: [ { loader: require.resolve('../../babel/webpack-loader'), options: { cacheDirectory: cache_path_1.findCachePath('babel-webpack'), scriptTarget: wco.scriptTarget, aot: buildOptions.aot, optimize: buildOptions.buildOptimizer && wco.scriptTarget >= typescript_1.ScriptTarget.ES2015, }, }, ...buildOptimizerUseRule, ], }, ...extraRules, ], }, experiments: { syncWebAssembly: true, asyncWebAssembly: true, }, cache: getCacheSettings(wco, buildBrowserFeatures.supportedBrowsers), optimization: { minimizer: extraMinimizers, moduleIds: 'deterministic', chunkIds: buildOptions.namedChunks ? 'named' : 'deterministic', emitOnErrors: false, }, plugins: [ // Always replace the context for the System.import in angular/core to prevent warnings. // https://github.com/angular/angular/issues/11580 new webpack_1.ContextReplacementPlugin(/\@angular(\\|\/)core(\\|\/)/, path.join(projectRoot, '$_lazy_route_resources'), {}), new plugins_1.DedupeModuleResolvePlugin({ verbose: buildOptions.verbose }), ...extraPlugins, ], }; } exports.getCommonConfig = getCommonConfig; function getCacheSettings(wco, supportedBrowsers) { if (environment_options_1.persistentBuildCacheEnabled) { const packageVersion = require('../../../package.json').version; return { type: 'filesystem', cacheDirectory: cache_path_1.findCachePath('angular-webpack'), maxMemoryGenerations: 1, // We use the versions and build options as the cache name. The Webpack configurations are too // dynamic and shared among different build types: test, build and serve. // None of which are "named". name: crypto_1.createHash('sha1') .update(compiler_cli_1.VERSION.full) .update(packageVersion) .update(wco.projectRoot) .update(JSON.stringify(wco.tsConfig)) .update(JSON.stringify({ ...wco.buildOptions, // Needed because outputPath changes on every build when using i18n extraction // https://github.com/angular/angular-cli/blob/736a5f89deaca85f487b78aec9ff66d4118ceb6a/packages/angular_devkit/build_angular/src/utils/i18n-options.ts#L264-L265 outputPath: undefined, })) .update(supportedBrowsers.join('')) .digest('hex'), }; } if (wco.buildOptions.watch && !environment_options_1.cachingDisabled) { return { type: 'memory', maxGenerations: 1, }; } return false; }