UNPKG

@angular-devkit/build-angular

Version:
501 lines (500 loc) 24.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getCommonConfig = void 0; /** * @license * Copyright Google Inc. 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 */ const build_optimizer_1 = require("@angular-devkit/build-optimizer"); const core_1 = require("@angular-devkit/core"); const CopyWebpackPlugin = require("copy-webpack-plugin"); const fs_1 = require("fs"); const path = require("path"); const typescript_1 = require("typescript"); const webpack_1 = require("webpack"); const webpack_sources_1 = require("webpack-sources"); const utils_1 = require("../../../utils"); const cache_path_1 = require("../../../utils/cache-path"); const environment_options_1 = require("../../../utils/environment-options"); const webpack_2 = require("../../plugins/webpack"); const find_up_1 = require("../../utilities/find-up"); const utils_2 = require("./utils"); const TerserPlugin = require('terser-webpack-plugin'); const PnpWebpackPlugin = require('pnp-webpack-plugin'); // tslint:disable-next-line:no-big-function function getCommonConfig(wco) { const { root, projectRoot, buildOptions, tsConfig } = wco; const { styles: stylesOptimization, scripts: scriptsOptimization } = buildOptions.optimization; const { styles: stylesSourceMap, scripts: scriptsSourceMap, vendor: vendorSourceMap, } = buildOptions.sourceMap; const extraPlugins = []; const extraRules = []; const entryPoints = {}; // determine hashing format const hashFormat = utils_2.getOutputHashFormat(buildOptions.outputHashing || 'none'); const targetInFileName = utils_2.getEsVersionForFileName(tsConfig.options.target, buildOptions.esVersionInFileName); if (buildOptions.main) { const mainPath = path.resolve(root, buildOptions.main); entryPoints['main'] = [mainPath]; if (buildOptions.experimentalRollupPass) { // NOTE: the following are known problems with experimentalRollupPass // - vendorChunk, commonChunk, namedChunks: these won't work, because by the time webpack // sees the chunks, the context of where they came from is lost. // - webWorkerTsConfig: workers must be imported via a root relative path (e.g. // `app/search/search.worker`) instead of a relative path (`/search.worker`) because // of the same reason as above. // - loadChildren string syntax: doesn't work because rollup cannot follow the imports. // Rollup options, except entry module, which is automatically inferred. const rollupOptions = {}; // Add rollup plugins/rules. extraRules.push({ test: mainPath, // Ensure rollup loader executes after other loaders. enforce: 'post', use: [{ loader: webpack_2.WebpackRollupLoader, options: rollupOptions, }], }); // Rollup bundles will include the dynamic System.import that was inside Angular and webpack // will emit warnings because it can't resolve it. We just ignore it. // TODO: maybe use https://webpack.js.org/configuration/stats/#statswarningsfilter instead. // Ignore all "Critical dependency: the request of a dependency is an expression" warnings. extraPlugins.push(new webpack_1.ContextReplacementPlugin(/./)); // Ignore "System.import() is deprecated" warnings for the main file and js files. // Might still get them if @angular/core gets split into a lazy module. extraRules.push({ test: mainPath, enforce: 'post', parser: { system: true }, }); extraRules.push({ test: /\.js$/, enforce: 'post', parser: { system: true }, }); } } const differentialLoadingMode = !!wco.differentialLoadingMode; if (wco.buildOptions.platform !== 'server') { if (differentialLoadingMode || tsConfig.options.target === typescript_1.ScriptTarget.ES5) { const buildBrowserFeatures = new utils_1.BuildBrowserFeatures(projectRoot, tsConfig.options.target || typescript_1.ScriptTarget.ES5); if (buildBrowserFeatures.isEs5SupportNeeded()) { const polyfillsChunkName = 'polyfills-es5'; entryPoints[polyfillsChunkName] = [path.join(__dirname, '..', 'es5-polyfills.js')]; if (differentialLoadingMode) { // Add zone.js legacy support to the es5 polyfills // This is a noop execution-wise if zone-evergreen is not used. entryPoints[polyfillsChunkName].push('zone.js/dist/zone-legacy'); // Since the chunkFileName option schema does not allow the function overload, add a plugin // that changes the name of the ES5 polyfills chunk to not include ES2015. extraPlugins.push({ apply(compiler) { compiler.hooks.compilation.tap('build-angular', compilation => { // Webpack typings do not contain MainTemplate assetPath hook // The webpack.Compilation assetPath hook is a noop in 4.x so the template must be used // tslint:disable-next-line: no-any compilation.mainTemplate.hooks.assetPath.tap('build-angular', (filename, data) => { const assetName = typeof filename === 'function' ? filename(data) : filename; const isMap = assetName && assetName.endsWith('.map'); return data.chunk && data.chunk.name === 'polyfills-es5' ? `polyfills-es5${hashFormat.chunk}.js${isMap ? '.map' : ''}` : assetName; }); }); }, }); } 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 = utils_2.normalizeExtraEntryPoints(buildOptions.scripts, 'scripts').reduce((prev, curr) => { const { bundleName, inject, input } = curr; const resolvedPath = path.resolve(root, input); if (!fs_1.existsSync(resolvedPath)) { 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 webpack_2.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) => { // Resolve input paths relative to workspace root and add slash at the end. // tslint: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, 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)), }, }; }); extraPlugins.push(new CopyWebpackPlugin({ patterns: copyWebpackPluginPatterns, })); } if (buildOptions.progress) { const ProgressPlugin = require('webpack/lib/ProgressPlugin'); extraPlugins.push(new ProgressPlugin({ profile: buildOptions.verbose })); } if (buildOptions.showCircularDependencies) { const CircularDependencyPlugin = require('circular-dependency-plugin'); extraPlugins.push(new CircularDependencyPlugin({ exclude: /([\\\/]node_modules[\\\/])|(ngfactory\.js$)/, })); } if (buildOptions.statsJson) { extraPlugins.push(new (class { apply(compiler) { compiler.hooks.emit.tap('angular-cli-stats', compilation => { const data = JSON.stringify(compilation.getStats().toJson('verbose'), undefined, 2); compilation.assets['stats.json'] = new webpack_sources_1.RawSource(data); }); } })()); } if (buildOptions.namedChunks) { extraPlugins.push(new webpack_2.NamedLazyChunksPlugin()); } if (!differentialLoadingMode) { // Budgets are computed after differential builds, not via a plugin. // https://github.com/angular/angular-cli/blob/master/packages/angular_devkit/build_angular/src/browser/index.ts extraPlugins.push(new webpack_2.BundleBudgetPlugin({ budgets: buildOptions.budgets })); } if ((scriptsSourceMap || stylesSourceMap) && vendorSourceMap) { extraRules.push({ test: /\.m?js$/, exclude: /(ngfactory|ngstyle)\.js$/, enforce: 'pre', loader: require.resolve('source-map-loader'), }); } let buildOptimizerUseRule = []; if (buildOptions.buildOptimizer) { extraPlugins.push(new build_optimizer_1.BuildOptimizerWebpackPlugin()); buildOptimizerUseRule = [ { loader: build_optimizer_1.buildOptimizerLoaderPath, options: { sourceMap: scriptsSourceMap }, }, ]; } const extraMinimizers = []; if (stylesOptimization) { extraMinimizers.push(new webpack_2.OptimizeCssWebpackPlugin({ sourceMap: stylesSourceMap, // component styles retain their original file name test: file => /\.(?:css|scss|sass|less|styl)$/.test(file), })); } if (scriptsOptimization) { let angularGlobalDefinitions = { ngDevMode: false, ngI18nClosureMode: false, }; // Try to load known global definitions from @angular/compiler-cli. const GLOBAL_DEFS_FOR_TERSER = require('@angular/compiler-cli').GLOBAL_DEFS_FOR_TERSER; if (GLOBAL_DEFS_FOR_TERSER) { angularGlobalDefinitions = GLOBAL_DEFS_FOR_TERSER; } if (buildOptions.aot) { // Also try to load AOT-only global definitions. const GLOBAL_DEFS_FOR_TERSER_WITH_AOT = require('@angular/compiler-cli') .GLOBAL_DEFS_FOR_TERSER_WITH_AOT; if (GLOBAL_DEFS_FOR_TERSER_WITH_AOT) { angularGlobalDefinitions = { ...angularGlobalDefinitions, ...GLOBAL_DEFS_FOR_TERSER_WITH_AOT, }; } } // TODO: Investigate why this fails for some packages: wco.supportES2015 ? 6 : 5; const terserEcma = 5; const terserOptions = { warnings: !!buildOptions.verbose, safari10: true, output: { ecma: terserEcma, // For differential loading, this is handled in the bundle processing. // This should also work with just true but the experimental rollup support breaks without this check. ascii_only: !differentialLoadingMode, // default behavior (undefined value) is to keep only important comments (licenses, etc.) comments: !buildOptions.extractLicenses && undefined, webkit: true, beautify: environment_options_1.shouldBeautify, }, // On server, we don't want to compress anything. We still set the ngDevMode = false for it // to remove dev code, and ngI18nClosureMode to remove Closure compiler i18n code compress: environment_options_1.allowMinify && (buildOptions.platform == 'server' ? { ecma: terserEcma, global_defs: angularGlobalDefinitions, keep_fnames: true, } : { ecma: terserEcma, pure_getters: buildOptions.buildOptimizer, // PURE comments work best with 3 passes. // See https://github.com/webpack/webpack/issues/2899#issuecomment-317425926. passes: buildOptions.buildOptimizer ? 3 : 1, global_defs: angularGlobalDefinitions, }), // We also want to avoid mangling on server. // Name mangling is handled within the browser builder mangle: environment_options_1.allowMangle && buildOptions.platform !== 'server' && !differentialLoadingMode, }; const globalScriptsNames = globalScriptsByBundleName.map(s => s.bundleName); extraMinimizers.push(new TerserPlugin({ sourceMap: scriptsSourceMap, parallel: utils_1.maxWorkers, cache: !environment_options_1.cachingDisabled && cache_path_1.findCachePath('terser-webpack'), extractComments: false, exclude: globalScriptsNames, terserOptions, }), // 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. new TerserPlugin({ sourceMap: scriptsSourceMap, parallel: utils_1.maxWorkers, cache: !environment_options_1.cachingDisabled && cache_path_1.findCachePath('terser-webpack'), extractComments: false, include: globalScriptsNames, terserOptions: { ...terserOptions, compress: environment_options_1.allowMinify && { ...terserOptions.compress, ecma: 5, }, output: { ...terserOptions.output, ecma: 5, }, mangle: environment_options_1.allowMangle && buildOptions.platform !== 'server', }, })); } if (wco.tsConfig.options.target !== undefined && wco.tsConfig.options.target >= typescript_1.ScriptTarget.ES2017) { wco.logger.warn(core_1.tags.stripIndent ` WARNING: Zone.js does not support native async/await in ES2017. These blocks are not intercepted by zone.js and will not triggering change detection. See: https://github.com/angular/zone.js/pull/1140 for more information. `); } return { mode: scriptsOptimization || stylesOptimization ? 'production' : 'development', devtool: false, profile: buildOptions.statsJson, resolve: { extensions: ['.ts', '.tsx', '.mjs', '.js'], symlinks: !buildOptions.preserveSymlinks, modules: [wco.tsConfig.options.baseUrl || projectRoot, 'node_modules'], plugins: [ PnpWebpackPlugin, ], }, 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), ], plugins: [PnpWebpackPlugin.moduleLoader(module)], }, context: projectRoot, entry: entryPoints, output: { futureEmitAssets: true, path: path.resolve(root, buildOptions.outputPath), publicPath: buildOptions.deployUrl, filename: `[name]${targetInFileName}${hashFormat.chunk}.js`, }, watch: buildOptions.watch, watchOptions: { poll: buildOptions.poll, ignored: buildOptions.poll === undefined ? undefined : /[\\\/]node_modules[\\\/]/, }, performance: { hints: false, }, module: { // Show an error for missing exports instead of a warning. strictExportPresence: true, rules: [ { test: /\.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)$/, loader: require.resolve('file-loader'), options: { name: `[name]${hashFormat.file}.[ext]`, // Re-use emitted files from browser builder on the server. emitFile: wco.buildOptions.platform !== 'server', }, }, { // 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: /\.m?js$/, exclude: [/[\/\\](?:core-js|\@babel|tslib)[\/\\]/, /(ngfactory|ngstyle)\.js$/], use: [ ...(wco.supportES2015 ? [] : [ { loader: require.resolve('babel-loader'), options: { babelrc: false, configFile: false, compact: false, cacheCompression: false, cacheDirectory: cache_path_1.findCachePath('babel-webpack'), cacheIdentifier: JSON.stringify({ buildAngular: require('../../../../package.json').version, }), sourceType: 'unambiguous', presets: [ [ require.resolve('@babel/preset-env'), { bugfixes: true, modules: false, // Comparable behavior to tsconfig target of ES5 targets: { ie: 9 }, exclude: ['transform-typeof-symbol'], }, ], ], plugins: [ [ require('@babel/plugin-transform-runtime').default, { useESModules: true, version: require('@babel/runtime/package.json').version, absoluteRuntime: path.dirname(require.resolve('@babel/runtime/package.json')), }, ], ], }, }, ]), ...buildOptimizerUseRule, ], }, ...extraRules, ], }, optimization: { minimizer: extraMinimizers, moduleIds: 'hashed', noEmitOnErrors: true, }, plugins: [ // Always replace the context for the System.import in angular/core to prevent warnings. // https://github.com/angular/angular/issues/11580 // With VE the correct context is added in @ngtools/webpack, but Ivy doesn't need it at all. new webpack_1.ContextReplacementPlugin(/\@angular(\\|\/)core(\\|\/)/), new webpack_2.DedupeModuleResolvePlugin({ verbose: buildOptions.verbose }), ...extraPlugins, ], }; } exports.getCommonConfig = getCommonConfig;