chore(nx): format new files

This commit is contained in:
Brandon Roberts 2019-11-05 09:44:20 -06:00 committed by Victor Savkin
parent c2ed286d5a
commit a24c31dd20
56 changed files with 2176 additions and 1543 deletions

View File

@ -81,16 +81,14 @@ export function getBaseWebpackPartial(
} }
if (options.extractLicenses) { if (options.extractLicenses) {
extraPlugins.push( extraPlugins.push((new LicenseWebpackPlugin({
new LicenseWebpackPlugin({
stats: { stats: {
warnings: false, warnings: false,
errors: false, errors: false
}, },
perChunkOutput: false, perChunkOutput: false,
outputFilename: `3rdpartylicenses.txt`, outputFilename: `3rdpartylicenses.txt`
}) as unknown as webpack.Plugin }) as unknown) as webpack.Plugin);
);
} }
// process asset entries // process asset entries

View File

@ -16,7 +16,7 @@ import {
Budget, Budget,
ExtraEntryPoint, ExtraEntryPoint,
OptimizationClass, OptimizationClass,
SourceMapClass, SourceMapClass
} from '../../browser/schema'; } from '../../browser/schema';
import { NormalizedFileReplacement } from '../../utils/normalize-file-replacements'; import { NormalizedFileReplacement } from '../../utils/normalize-file-replacements';

View File

@ -1,19 +1,23 @@
(function () { (function() {
var check = document.createElement('script'); var check = document.createElement('script');
if (!('noModule' in check) && 'onbeforeload' in check) { if (!('noModule' in check) && 'onbeforeload' in check) {
var support = false; var support = false;
document.addEventListener('beforeload', function (e) { document.addEventListener(
'beforeload',
function(e) {
if (e.target === check) { if (e.target === check) {
support = true; support = true;
} else if (!e.target.hasAttribute('nomodule') || !support) { } else if (!e.target.hasAttribute('nomodule') || !support) {
return; return;
} }
e.preventDefault(); e.preventDefault();
}, true); },
true
);
check.type = 'module'; check.type = 'module';
check.src = '.'; check.src = '.';
document.head.appendChild(check); document.head.appendChild(check);
check.remove(); check.remove();
} }
}()); })();

View File

@ -8,70 +8,92 @@
import { LicenseWebpackPlugin } from 'license-webpack-plugin'; import { LicenseWebpackPlugin } from 'license-webpack-plugin';
import * as webpack from 'webpack'; import * as webpack from 'webpack';
import { WebpackConfigOptions } from '../build-options'; import { WebpackConfigOptions } from '../build-options';
import { getSourceMapDevTool, isPolyfillsEntry, normalizeExtraEntryPoints } from './utils'; import {
getSourceMapDevTool,
isPolyfillsEntry,
normalizeExtraEntryPoints
} from './utils';
const SubresourceIntegrityPlugin = require('webpack-subresource-integrity'); const SubresourceIntegrityPlugin = require('webpack-subresource-integrity');
export function getBrowserConfig(
export function getBrowserConfig(wco: WebpackConfigOptions): webpack.Configuration { wco: WebpackConfigOptions
): webpack.Configuration {
const { buildOptions } = wco; const { buildOptions } = wco;
const extraPlugins = []; const extraPlugins = [];
let isEval = false; let isEval = false;
const { styles: stylesOptimization, scripts: scriptsOptimization } = buildOptions.optimization; const {
styles: stylesOptimization,
scripts: scriptsOptimization
} = buildOptions.optimization;
const { const {
styles: stylesSourceMap, styles: stylesSourceMap,
scripts: scriptsSourceMap, scripts: scriptsSourceMap,
hidden: hiddenSourceMap, hidden: hiddenSourceMap
} = buildOptions.sourceMap; } = buildOptions.sourceMap;
// See https://webpack.js.org/configuration/devtool/ for sourcemap types. // See https://webpack.js.org/configuration/devtool/ for sourcemap types.
if ((stylesSourceMap || scriptsSourceMap) && if (
(stylesSourceMap || scriptsSourceMap) &&
buildOptions.evalSourceMap && buildOptions.evalSourceMap &&
!stylesOptimization && !stylesOptimization &&
!scriptsOptimization) { !scriptsOptimization
) {
// Produce eval sourcemaps for development with serve, which are faster. // Produce eval sourcemaps for development with serve, which are faster.
isEval = true; isEval = true;
} }
if (buildOptions.subresourceIntegrity) { if (buildOptions.subresourceIntegrity) {
extraPlugins.push(new SubresourceIntegrityPlugin({ extraPlugins.push(
hashFuncNames: ['sha384'], new SubresourceIntegrityPlugin({
})); hashFuncNames: ['sha384']
})
);
} }
if (buildOptions.extractLicenses) { if (buildOptions.extractLicenses) {
extraPlugins.push(new LicenseWebpackPlugin({ extraPlugins.push(
new LicenseWebpackPlugin({
stats: { stats: {
warnings: false, warnings: false,
errors: false, errors: false
}, },
perChunkOutput: false, perChunkOutput: false,
outputFilename: `3rdpartylicenses.txt`, outputFilename: `3rdpartylicenses.txt`
})); })
);
} }
if (!isEval && (scriptsSourceMap || stylesSourceMap)) { if (!isEval && (scriptsSourceMap || stylesSourceMap)) {
extraPlugins.push(getSourceMapDevTool( extraPlugins.push(
getSourceMapDevTool(
!!scriptsSourceMap, !!scriptsSourceMap,
!!stylesSourceMap, !!stylesSourceMap,
hiddenSourceMap, hiddenSourceMap
)); )
);
} }
const globalStylesBundleNames = normalizeExtraEntryPoints(buildOptions.styles, 'styles') const globalStylesBundleNames = normalizeExtraEntryPoints(
.map(style => style.bundleName); buildOptions.styles,
'styles'
).map(style => style.bundleName);
return { return {
devtool: isEval ? 'eval' : false, devtool: isEval ? 'eval' : false,
resolve: { resolve: {
mainFields: [ mainFields: [
...(wco.supportES2015 ? ['es2015'] : []), ...(wco.supportES2015 ? ['es2015'] : []),
'browser', 'module', 'main', 'browser',
], 'module',
'main'
]
}, },
output: { output: {
crossOriginLoading: buildOptions.subresourceIntegrity ? 'anonymous' : false, crossOriginLoading: buildOptions.subresourceIntegrity
? 'anonymous'
: false
}, },
optimization: { optimization: {
runtimeChunk: 'single', runtimeChunk: 'single',
@ -81,32 +103,42 @@ export function getBrowserConfig(wco: WebpackConfigOptions): webpack.Configurati
default: !!buildOptions.commonChunk && { default: !!buildOptions.commonChunk && {
chunks: 'async', chunks: 'async',
minChunks: 2, minChunks: 2,
priority: 10, priority: 10
}, },
common: !!buildOptions.commonChunk && { common: !!buildOptions.commonChunk && {
name: 'common', name: 'common',
chunks: 'async', chunks: 'async',
minChunks: 2, minChunks: 2,
enforce: true, enforce: true,
priority: 5, priority: 5
}, },
vendors: false, vendors: false,
vendor: !!buildOptions.vendorChunk && { vendor: !!buildOptions.vendorChunk && {
name: 'vendor', name: 'vendor',
chunks: 'initial', chunks: 'initial',
enforce: true, enforce: true,
test: (module: { nameForCondition?: Function }, chunks: Array<{ name: string }>) => { test: (
const moduleName = module.nameForCondition ? module.nameForCondition() : ''; module: { nameForCondition?: Function },
chunks: Array<{ name: string }>
) => {
const moduleName = module.nameForCondition
? module.nameForCondition()
: '';
return /[\\/]node_modules[\\/]/.test(moduleName) return (
&& !chunks.some(({ name }) => isPolyfillsEntry(name) /[\\/]node_modules[\\/]/.test(moduleName) &&
|| globalStylesBundleNames.includes(name)); !chunks.some(
}, ({ name }) =>
}, isPolyfillsEntry(name) ||
}, globalStylesBundleNames.includes(name)
}, )
);
}
}
}
}
}, },
plugins: extraPlugins, plugins: extraPlugins,
node: false, node: false
}; };
} }

View File

@ -7,7 +7,7 @@
*/ */
import { import {
BuildOptimizerWebpackPlugin, BuildOptimizerWebpackPlugin,
buildOptimizerLoaderPath, buildOptimizerLoaderPath
} from '@angular-devkit/build-optimizer'; } from '@angular-devkit/build-optimizer';
import { tags } from '@angular-devkit/core'; import { tags } from '@angular-devkit/core';
import * as CopyWebpackPlugin from 'copy-webpack-plugin'; import * as CopyWebpackPlugin from 'copy-webpack-plugin';
@ -19,7 +19,7 @@ import {
ContextReplacementPlugin, ContextReplacementPlugin,
HashedModuleIdsPlugin, HashedModuleIdsPlugin,
compilation, compilation,
debug, debug
} from 'webpack'; } from 'webpack';
import { RawSource } from 'webpack-sources'; import { RawSource } from 'webpack-sources';
import { AssetPatternClass, ExtraEntryPoint } from '../../../browser/schema'; import { AssetPatternClass, ExtraEntryPoint } from '../../../browser/schema';
@ -31,7 +31,11 @@ import { NamedLazyChunksPlugin } from '../../plugins/named-chunks-plugin';
import { ScriptsWebpackPlugin } from '../../plugins/scripts-webpack-plugin'; import { ScriptsWebpackPlugin } from '../../plugins/scripts-webpack-plugin';
import { findAllNodeModules, findUp } from '../../utilities/find-up'; import { findAllNodeModules, findUp } from '../../utilities/find-up';
import { WebpackConfigOptions } from '../build-options'; import { WebpackConfigOptions } from '../build-options';
import { getEsVersionForFileName, getOutputHashFormat, normalizeExtraEntryPoints } from './utils'; import {
getEsVersionForFileName,
getOutputHashFormat,
normalizeExtraEntryPoints
} from './utils';
const ProgressPlugin = require('webpack/lib/ProgressPlugin'); const ProgressPlugin = require('webpack/lib/ProgressPlugin');
const CircularDependencyPlugin = require('circular-dependency-plugin'); const CircularDependencyPlugin = require('circular-dependency-plugin');
@ -43,11 +47,14 @@ const g: any = typeof global !== 'undefined' ? global : {};
// tslint:disable-next-line:no-big-function // tslint:disable-next-line:no-big-function
export function getCommonConfig(wco: WebpackConfigOptions): Configuration { export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
const { root, projectRoot, buildOptions, tsConfig } = wco; const { root, projectRoot, buildOptions, tsConfig } = wco;
const { styles: stylesOptimization, scripts: scriptsOptimization } = buildOptions.optimization; const {
styles: stylesOptimization,
scripts: scriptsOptimization
} = buildOptions.optimization;
const { const {
styles: stylesSourceMap, styles: stylesSourceMap,
scripts: scriptsSourceMap, scripts: scriptsSourceMap,
vendor: vendorSourceMap, vendor: vendorSourceMap
} = buildOptions.sourceMap; } = buildOptions.sourceMap;
const nodeModules = findUp('node_modules', projectRoot); const nodeModules = findUp('node_modules', projectRoot);
@ -60,8 +67,10 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
const entryPoints: { [key: string]: string[] } = {}; const entryPoints: { [key: string]: string[] } = {};
const targetInFileName = getEsVersionForFileName( const targetInFileName = getEsVersionForFileName(
fullDifferential ? buildOptions.scriptTargetOverride : tsConfig.options.target, fullDifferential
buildOptions.esVersionInFileName, ? buildOptions.scriptTargetOverride
: tsConfig.options.target,
buildOptions.esVersionInFileName
); );
if (buildOptions.main) { if (buildOptions.main) {
@ -72,15 +81,19 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
if (wco.buildOptions.platform !== 'server') { if (wco.buildOptions.platform !== 'server') {
const buildBrowserFeatures = new BuildBrowserFeatures( const buildBrowserFeatures = new BuildBrowserFeatures(
projectRoot, projectRoot,
tsConfig.options.target || ScriptTarget.ES5, tsConfig.options.target || ScriptTarget.ES5
); );
differentialLoadingNeeded = buildBrowserFeatures.isDifferentialLoadingNeeded(); differentialLoadingNeeded = buildBrowserFeatures.isDifferentialLoadingNeeded();
if ((buildOptions.scriptTargetOverride || tsConfig.options.target) === ScriptTarget.ES5) { if (
(buildOptions.scriptTargetOverride || tsConfig.options.target) ===
ScriptTarget.ES5
) {
if ( if (
buildOptions.es5BrowserSupport || buildOptions.es5BrowserSupport ||
(buildOptions.es5BrowserSupport === undefined && buildBrowserFeatures.isEs5SupportNeeded()) (buildOptions.es5BrowserSupport === undefined &&
buildBrowserFeatures.isEs5SupportNeeded())
) { ) {
// The nomodule polyfill needs to be inject prior to any script and be // The nomodule polyfill needs to be inject prior to any script and be
// outside of webpack compilation because otherwise webpack will cause the // outside of webpack compilation because otherwise webpack will cause the
@ -88,7 +101,7 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
if (buildBrowserFeatures.isNoModulePolyfillNeeded()) { if (buildBrowserFeatures.isNoModulePolyfillNeeded()) {
const noModuleScript: ExtraEntryPoint = { const noModuleScript: ExtraEntryPoint = {
bundleName: 'polyfills-nomodule-es5', bundleName: 'polyfills-nomodule-es5',
input: path.join(__dirname, '..', 'safari-nomodule.js'), input: path.join(__dirname, '..', 'safari-nomodule.js')
}; };
buildOptions.scripts = buildOptions.scripts buildOptions.scripts = buildOptions.scripts
? [...buildOptions.scripts, noModuleScript] ? [...buildOptions.scripts, noModuleScript]
@ -98,9 +111,13 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
// For full build differential loading we don't need to generate a seperate polyfill file // For full build differential loading we don't need to generate a seperate polyfill file
// because they will be loaded exclusivly based on module and nomodule // because they will be loaded exclusivly based on module and nomodule
const polyfillsChunkName = const polyfillsChunkName =
fullDifferential && differentialLoadingNeeded ? 'polyfills' : 'polyfills-es5'; fullDifferential && differentialLoadingNeeded
? 'polyfills'
: 'polyfills-es5';
entryPoints[polyfillsChunkName] = [path.join(__dirname, '..', 'es5-polyfills.js')]; entryPoints[polyfillsChunkName] = [
path.join(__dirname, '..', 'es5-polyfills.js')
];
if (!fullDifferential && differentialLoadingNeeded) { if (!fullDifferential && differentialLoadingNeeded) {
// Add zone.js legacy support to the es5 polyfills // Add zone.js legacy support to the es5 polyfills
// This is a noop execution-wise if zone-evergreen is not used. // This is a noop execution-wise if zone-evergreen is not used.
@ -109,13 +126,19 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
if (!buildOptions.aot) { if (!buildOptions.aot) {
// If not performing a full differential build the JIT polyfills need to be added to ES5 // If not performing a full differential build the JIT polyfills need to be added to ES5
if (!fullDifferential && differentialLoadingNeeded) { if (!fullDifferential && differentialLoadingNeeded) {
entryPoints[polyfillsChunkName].push(path.join(__dirname, '..', 'jit-polyfills.js')); entryPoints[polyfillsChunkName].push(
path.join(__dirname, '..', 'jit-polyfills.js')
);
} }
entryPoints[polyfillsChunkName].push(path.join(__dirname, '..', 'es5-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 not performing a full differential build the polyfills need to be added to ES5 bundle
if (!fullDifferential && buildOptions.polyfills) { if (!fullDifferential && buildOptions.polyfills) {
entryPoints[polyfillsChunkName].push(path.resolve(root, buildOptions.polyfills)); entryPoints[polyfillsChunkName].push(
path.resolve(root, buildOptions.polyfills)
);
} }
} }
} }
@ -123,14 +146,14 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
if (buildOptions.polyfills) { if (buildOptions.polyfills) {
entryPoints['polyfills'] = [ entryPoints['polyfills'] = [
...(entryPoints['polyfills'] || []), ...(entryPoints['polyfills'] || []),
path.resolve(root, buildOptions.polyfills), path.resolve(root, buildOptions.polyfills)
]; ];
} }
if (!buildOptions.aot) { if (!buildOptions.aot) {
entryPoints['polyfills'] = [ entryPoints['polyfills'] = [
...(entryPoints['polyfills'] || []), ...(entryPoints['polyfills'] || []),
path.join(__dirname, '..', 'jit-polyfills.js'), path.join(__dirname, '..', 'jit-polyfills.js')
]; ];
} }
} }
@ -138,8 +161,11 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
if (buildOptions.profile || process.env['NG_BUILD_PROFILING']) { if (buildOptions.profile || process.env['NG_BUILD_PROFILING']) {
extraPlugins.push( extraPlugins.push(
new debug.ProfilingPlugin({ new debug.ProfilingPlugin({
outputPath: path.resolve(root, `chrome-profiler-events${targetInFileName}.json`), outputPath: path.resolve(
}), root,
`chrome-profiler-events${targetInFileName}.json`
)
})
); );
} }
@ -149,8 +175,12 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
// process global scripts // process global scripts
const globalScriptsByBundleName = normalizeExtraEntryPoints( const globalScriptsByBundleName = normalizeExtraEntryPoints(
buildOptions.scripts, buildOptions.scripts,
'scripts', 'scripts'
).reduce((prev: { bundleName: string; paths: string[]; inject: boolean }[], curr) => { ).reduce(
(
prev: { bundleName: string; paths: string[]; inject: boolean }[],
curr
) => {
const bundleName = curr.bundleName; const bundleName = curr.bundleName;
const resolvedPath = path.resolve(root, curr.input); const resolvedPath = path.resolve(root, curr.input);
const existingEntry = prev.find(el => el.bundleName === bundleName); const existingEntry = prev.find(el => el.bundleName === bundleName);
@ -158,7 +188,7 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
if (existingEntry.inject && !curr.inject) { if (existingEntry.inject && !curr.inject) {
// All entries have to be lazy for the bundle to be lazy. // All entries have to be lazy for the bundle to be lazy.
throw new Error( throw new Error(
`The ${curr.bundleName} bundle is mixing injected and non-injected scripts.`, `The ${curr.bundleName} bundle is mixing injected and non-injected scripts.`
); );
} }
@ -167,12 +197,14 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
prev.push({ prev.push({
bundleName, bundleName,
paths: [resolvedPath], paths: [resolvedPath],
inject: curr.inject, inject: curr.inject
}); });
} }
return prev; return prev;
}, []); },
[]
);
if (globalScriptsByBundleName.length > 0) { if (globalScriptsByBundleName.length > 0) {
// Add a new asset for each entry. // Add a new asset for each entry.
@ -187,22 +219,28 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
sourceMap: scriptsSourceMap, sourceMap: scriptsSourceMap,
filename: `${path.basename(bundleName)}${hash}.js`, filename: `${path.basename(bundleName)}${hash}.js`,
scripts: script.paths, scripts: script.paths,
basePath: projectRoot, basePath: projectRoot
}), })
); );
}); });
} }
// process asset entries // process asset entries
if (buildOptions.assets) { if (buildOptions.assets) {
const copyWebpackPluginPatterns = buildOptions.assets.map((asset: AssetPatternClass) => { const copyWebpackPluginPatterns = buildOptions.assets.map(
(asset: AssetPatternClass) => {
// Resolve input paths relative to workspace root and add slash at the end. // Resolve input paths relative to workspace root and add slash at the end.
asset.input = path.resolve(root, asset.input).replace(/\\/g, '/'); asset.input = path.resolve(root, asset.input).replace(/\\/g, '/');
asset.input = asset.input.endsWith('/') ? asset.input : asset.input + '/'; asset.input = asset.input.endsWith('/')
asset.output = asset.output.endsWith('/') ? asset.output : asset.output + '/'; ? asset.input
: asset.input + '/';
asset.output = asset.output.endsWith('/')
? asset.output
: asset.output + '/';
if (asset.output.startsWith('..')) { if (asset.output.startsWith('..')) {
const message = 'An asset cannot be written to a location outside of the output path.'; const message =
'An asset cannot be written to a location outside of the output path.';
throw new Error(message); throw new Error(message);
} }
@ -213,16 +251,19 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
ignore: asset.ignore, ignore: asset.ignore,
from: { from: {
glob: asset.glob, glob: asset.glob,
dot: true, dot: true
}, }
}; };
}); }
);
const copyWebpackPluginOptions = { ignore: ['.gitkeep', '**/.DS_Store', '**/Thumbs.db'] }; const copyWebpackPluginOptions = {
ignore: ['.gitkeep', '**/.DS_Store', '**/Thumbs.db']
};
const copyWebpackPluginInstance = new CopyWebpackPlugin( const copyWebpackPluginInstance = new CopyWebpackPlugin(
copyWebpackPluginPatterns, copyWebpackPluginPatterns,
copyWebpackPluginOptions, copyWebpackPluginOptions
); );
extraPlugins.push(copyWebpackPluginInstance); extraPlugins.push(copyWebpackPluginInstance);
} }
@ -234,8 +275,8 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
if (buildOptions.showCircularDependencies) { if (buildOptions.showCircularDependencies) {
extraPlugins.push( extraPlugins.push(
new CircularDependencyPlugin({ new CircularDependencyPlugin({
exclude: /([\\\/]node_modules[\\\/])|(ngfactory\.js$)/, exclude: /([\\\/]node_modules[\\\/])|(ngfactory\.js$)/
}), })
); );
} }
@ -244,11 +285,15 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
new (class { new (class {
apply(compiler: Compiler) { apply(compiler: Compiler) {
compiler.hooks.emit.tap('angular-cli-stats', compilation => { compiler.hooks.emit.tap('angular-cli-stats', compilation => {
const data = JSON.stringify(compilation.getStats().toJson('verbose')); const data = JSON.stringify(
compilation.assets[`stats${targetInFileName}.json`] = new RawSource(data); compilation.getStats().toJson('verbose')
);
compilation.assets[`stats${targetInFileName}.json`] = new RawSource(
data
);
}); });
} }
})(), })()
); );
} }
@ -261,9 +306,9 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
sourceMapUseRule = { sourceMapUseRule = {
use: [ use: [
{ {
loader: 'source-map-loader', loader: 'source-map-loader'
}, }
], ]
}; };
} }
@ -274,9 +319,9 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
use: [ use: [
{ {
loader: buildOptimizerLoaderPath, loader: buildOptimizerLoaderPath,
options: { sourceMap: scriptsSourceMap }, options: { sourceMap: scriptsSourceMap }
}, }
], ]
}; };
} }
@ -293,7 +338,9 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
const rxjsPathMappingImport = wco.supportES2015 const rxjsPathMappingImport = wco.supportES2015
? 'rxjs/_esm2015/path-mapping' ? 'rxjs/_esm2015/path-mapping'
: 'rxjs/_esm5/path-mapping'; : 'rxjs/_esm5/path-mapping';
const rxPaths = require(require.resolve(rxjsPathMappingImport, { paths: [projectRoot] })); const rxPaths = require(require.resolve(rxjsPathMappingImport, {
paths: [projectRoot]
}));
alias = rxPaths(nodeModules); alias = rxPaths(nodeModules);
} catch {} } catch {}
@ -303,19 +350,20 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
new CleanCssWebpackPlugin({ new CleanCssWebpackPlugin({
sourceMap: stylesSourceMap, sourceMap: stylesSourceMap,
// component styles retain their original file name // component styles retain their original file name
test: file => /\.(?:css|scss|sass|less|styl)$/.test(file), test: file => /\.(?:css|scss|sass|less|styl)$/.test(file)
}), })
); );
} }
if (scriptsOptimization) { if (scriptsOptimization) {
let angularGlobalDefinitions = { let angularGlobalDefinitions = {
ngDevMode: false, ngDevMode: false,
ngI18nClosureMode: false, ngI18nClosureMode: false
}; };
// Try to load known global definitions from @angular/compiler-cli. // Try to load known global definitions from @angular/compiler-cli.
const GLOBAL_DEFS_FOR_TERSER = require('@angular/compiler-cli').GLOBAL_DEFS_FOR_TERSER; const GLOBAL_DEFS_FOR_TERSER = require('@angular/compiler-cli')
.GLOBAL_DEFS_FOR_TERSER;
if (GLOBAL_DEFS_FOR_TERSER) { if (GLOBAL_DEFS_FOR_TERSER) {
angularGlobalDefinitions = GLOBAL_DEFS_FOR_TERSER; angularGlobalDefinitions = GLOBAL_DEFS_FOR_TERSER;
} }
@ -327,7 +375,7 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
if (GLOBAL_DEFS_FOR_TERSER_WITH_AOT) { if (GLOBAL_DEFS_FOR_TERSER_WITH_AOT) {
angularGlobalDefinitions = { angularGlobalDefinitions = {
...angularGlobalDefinitions, ...angularGlobalDefinitions,
...GLOBAL_DEFS_FOR_TERSER_WITH_AOT, ...GLOBAL_DEFS_FOR_TERSER_WITH_AOT
}; };
} }
} }
@ -341,7 +389,7 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
output: { output: {
ecma: terserEcma, ecma: terserEcma,
comments: false, comments: false,
webkit: true, webkit: true
}, },
// On server, we don't want to compress anything. We still set the ngDevMode = false for it // 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 // to remove dev code, and ngI18nClosureMode to remove Closure compiler i18n code
@ -350,7 +398,7 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
? { ? {
ecma: terserEcma, ecma: terserEcma,
global_defs: angularGlobalDefinitions, global_defs: angularGlobalDefinitions,
keep_fnames: true, keep_fnames: true
} }
: { : {
ecma: terserEcma, ecma: terserEcma,
@ -358,14 +406,15 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
// PURE comments work best with 3 passes. // PURE comments work best with 3 passes.
// See https://github.com/webpack/webpack/issues/2899#issuecomment-317425926. // See https://github.com/webpack/webpack/issues/2899#issuecomment-317425926.
passes: buildOptions.buildOptimizer ? 3 : 1, passes: buildOptions.buildOptimizer ? 3 : 1,
global_defs: angularGlobalDefinitions, global_defs: angularGlobalDefinitions
}, },
// We also want to avoid mangling on server. // We also want to avoid mangling on server.
// Name mangling is handled within the browser builder // Name mangling is handled within the browser builder
mangle: mangle:
!manglingDisabled && !manglingDisabled &&
buildOptions.platform !== 'server' && buildOptions.platform !== 'server' &&
(!differentialLoadingNeeded || (differentialLoadingNeeded && fullDifferential)), (!differentialLoadingNeeded ||
(differentialLoadingNeeded && fullDifferential))
}; };
extraMinimizers.push( extraMinimizers.push(
@ -375,7 +424,7 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
cache: true, cache: true,
chunkFilter: (chunk: compilation.Chunk) => chunkFilter: (chunk: compilation.Chunk) =>
!globalScriptsByBundleName.some(s => s.bundleName === chunk.name), !globalScriptsByBundleName.some(s => s.bundleName === chunk.name),
terserOptions, terserOptions
}), }),
// Script bundles are fully optimized here in one step since they are never downleveled. // 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. // They are shared between ES2015 & ES5 outputs so must support ES5.
@ -389,15 +438,15 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
...terserOptions, ...terserOptions,
compress: { compress: {
...terserOptions.compress, ...terserOptions.compress,
ecma: 5, ecma: 5
}, },
output: { output: {
...terserOptions.output, ...terserOptions.output,
ecma: 5, ecma: 5
}, },
mangle: !manglingDisabled && buildOptions.platform !== 'server', mangle: !manglingDisabled && buildOptions.platform !== 'server'
}, }
}), })
); );
} }
@ -413,17 +462,18 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
} }
return { return {
mode: scriptsOptimization || stylesOptimization ? 'production' : 'development', mode:
scriptsOptimization || stylesOptimization ? 'production' : 'development',
devtool: false, devtool: false,
profile: buildOptions.statsJson, profile: buildOptions.statsJson,
resolve: { resolve: {
extensions: ['.ts', '.tsx', '.mjs', '.js'], extensions: ['.ts', '.tsx', '.mjs', '.js'],
symlinks: !buildOptions.preserveSymlinks, symlinks: !buildOptions.preserveSymlinks,
modules: [wco.tsConfig.options.baseUrl || projectRoot, 'node_modules'], modules: [wco.tsConfig.options.baseUrl || projectRoot, 'node_modules'],
alias, alias
}, },
resolveLoader: { resolveLoader: {
modules: loaderNodeModules, modules: loaderNodeModules
}, },
context: projectRoot, context: projectRoot,
entry: entryPoints, entry: entryPoints,
@ -431,14 +481,14 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
futureEmitAssets: true, futureEmitAssets: true,
path: path.resolve(root, buildOptions.outputPath as string), path: path.resolve(root, buildOptions.outputPath as string),
publicPath: buildOptions.deployUrl, publicPath: buildOptions.deployUrl,
filename: `[name]${targetInFileName}${hashFormat.chunk}.js`, filename: `[name]${targetInFileName}${hashFormat.chunk}.js`
}, },
watch: buildOptions.watch, watch: buildOptions.watch,
watchOptions: { watchOptions: {
poll: buildOptions.poll, poll: buildOptions.poll
}, },
performance: { performance: {
hints: false, hints: false
}, },
module: { module: {
// Show an error for missing exports instead of a warning. // Show an error for missing exports instead of a warning.
@ -448,36 +498,36 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
test: /\.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)$/, test: /\.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)$/,
loader: 'file-loader', loader: 'file-loader',
options: { options: {
name: `[name]${hashFormat.file}.[ext]`, name: `[name]${hashFormat.file}.[ext]`
}, }
}, },
{ {
// Mark files inside `@angular/core` as using SystemJS style dynamic imports. // Mark files inside `@angular/core` as using SystemJS style dynamic imports.
// Removing this will cause deprecation warnings to appear. // Removing this will cause deprecation warnings to appear.
test: /[\/\\]@angular[\/\\]core[\/\\].+\.js$/, test: /[\/\\]@angular[\/\\]core[\/\\].+\.js$/,
parser: { system: true }, parser: { system: true }
}, },
{ {
test: /[\/\\]hot[\/\\]emitter\.js$/, test: /[\/\\]hot[\/\\]emitter\.js$/,
parser: { node: { events: true } }, parser: { node: { events: true } }
}, },
{ {
test: /[\/\\]webpack-dev-server[\/\\]client[\/\\]utils[\/\\]createSocketUrl\.js$/, test: /[\/\\]webpack-dev-server[\/\\]client[\/\\]utils[\/\\]createSocketUrl\.js$/,
parser: { node: { querystring: true } }, parser: { node: { querystring: true } }
}, },
{ {
test: /\.js$/, test: /\.js$/,
// Factory files are processed by BO in the rules added in typescript.ts. // Factory files are processed by BO in the rules added in typescript.ts.
exclude: /(ngfactory|ngstyle)\.js$/, exclude: /(ngfactory|ngstyle)\.js$/,
...buildOptimizerUseRule, ...buildOptimizerUseRule
}, },
{ {
test: /\.js$/, test: /\.js$/,
exclude: /(ngfactory|ngstyle)\.js$/, exclude: /(ngfactory|ngstyle)\.js$/,
enforce: 'pre', enforce: 'pre',
...sourceMapUseRule, ...sourceMapUseRule
}, }
], ]
}, },
optimization: { optimization: {
noEmitOnErrors: true, noEmitOnErrors: true,
@ -485,15 +535,15 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
new HashedModuleIdsPlugin(), new HashedModuleIdsPlugin(),
// TODO: check with Mike what this feature needs. // TODO: check with Mike what this feature needs.
new BundleBudgetPlugin({ budgets: buildOptions.budgets }), new BundleBudgetPlugin({ budgets: buildOptions.budgets }),
...extraMinimizers, ...extraMinimizers
], ]
}, },
plugins: [ plugins: [
// Always replace the context for the System.import in angular/core to prevent warnings. // Always replace the context for the System.import in angular/core to prevent warnings.
// https://github.com/angular/angular/issues/11580 // 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. // With VE the correct context is added in @ngtools/webpack, but Ivy doesn't need it at all.
new ContextReplacementPlugin(/\@angular(\\|\/)core(\\|\/)/), new ContextReplacementPlugin(/\@angular(\\|\/)core(\\|\/)/),
...extraPlugins, ...extraPlugins
], ]
}; };
} }

View File

@ -19,25 +19,31 @@ export function getServerConfig(wco: WebpackConfigOptions): Configuration {
if (wco.buildOptions.sourceMap) { if (wco.buildOptions.sourceMap) {
const { scripts, styles, hidden } = wco.buildOptions.sourceMap; const { scripts, styles, hidden } = wco.buildOptions.sourceMap;
extraPlugins.push(getSourceMapDevTool(scripts || false, styles || false, hidden || false)); extraPlugins.push(
getSourceMapDevTool(scripts || false, styles || false, hidden || false)
);
} }
const config: Configuration = { const config: Configuration = {
resolve: { resolve: {
mainFields: [...(wco.supportES2015 ? ['es2015'] : []), 'main', 'module'], mainFields: [...(wco.supportES2015 ? ['es2015'] : []), 'main', 'module']
}, },
target: 'node', target: 'node',
output: { output: {
libraryTarget: 'commonjs', libraryTarget: 'commonjs'
}, },
plugins: extraPlugins, plugins: extraPlugins,
node: false, node: false
}; };
if (wco.buildOptions.bundleDependencies == 'none') { if (wco.buildOptions.bundleDependencies == 'none') {
config.externals = [ config.externals = [
/^@angular/, /^@angular/,
(context: string, request: string, callback: (error?: null, result?: string) => void) => { (
context: string,
request: string,
callback: (error?: null, result?: string) => void
) => {
// Absolute & Relative paths are not externals // Absolute & Relative paths are not externals
if (/^\.{0,2}\//.test(request) || isAbsolute(request)) { if (/^\.{0,2}\//.test(request) || isAbsolute(request)) {
return callback(); return callback();
@ -50,7 +56,7 @@ export function getServerConfig(wco: WebpackConfigOptions): Configuration {
// Node couldn't find it, so it must be user-aliased // Node couldn't find it, so it must be user-aliased
callback(); callback();
} }
}, }
]; ];
} }

View File

@ -22,7 +22,7 @@ const webpackOutputOptions = {
assets: true, // required by custom stat output assets: true, // required by custom stat output
version: false, version: false,
errorDetails: false, errorDetails: false,
moduleTrace: false, moduleTrace: false
}; };
const verboseWebpackOutputOptions = { const verboseWebpackOutputOptions = {
@ -37,7 +37,7 @@ const verboseWebpackOutputOptions = {
version: true, version: true,
chunkModules: true, chunkModules: true,
errorDetails: true, errorDetails: true,
moduleTrace: true, moduleTrace: true
}; };
export function getWebpackStatsConfig(verbose = false) { export function getWebpackStatsConfig(verbose = false) {

View File

@ -12,7 +12,7 @@ import {
PostcssCliResources, PostcssCliResources,
RawCssLoader, RawCssLoader,
RemoveHashPlugin, RemoveHashPlugin,
SuppressExtractedTextChunksWebpackPlugin, SuppressExtractedTextChunksWebpackPlugin
} from '../../plugins/webpack'; } from '../../plugins/webpack';
import { WebpackConfigOptions } from '../build-options'; import { WebpackConfigOptions } from '../build-options';
import { getOutputHashFormat, normalizeExtraEntryPoints } from './utils'; import { getOutputHashFormat, normalizeExtraEntryPoints } from './utils';
@ -63,7 +63,7 @@ export function getStylesConfig(wco: WebpackConfigOptions) {
resolve(content); resolve(content);
}); });
}); });
}, }
}), }),
PostcssCliResources({ PostcssCliResources({
baseHref: buildOptions.baseHref, baseHref: buildOptions.baseHref,
@ -71,9 +71,9 @@ export function getStylesConfig(wco: WebpackConfigOptions) {
resourcesOutputPath: buildOptions.resourcesOutputPath, resourcesOutputPath: buildOptions.resourcesOutputPath,
loader, loader,
rebaseRootRelative: buildOptions.rebaseRootRelativeCssUrls, rebaseRootRelative: buildOptions.rebaseRootRelativeCssUrls,
filename: `[name]${hashFormat.file}.[ext]`, filename: `[name]${hashFormat.file}.[ext]`
}), }),
autoprefixer(), autoprefixer()
]; ];
}; };
@ -86,11 +86,12 @@ export function getStylesConfig(wco: WebpackConfigOptions) {
buildOptions.stylePreprocessorOptions.includePaths && buildOptions.stylePreprocessorOptions.includePaths &&
buildOptions.stylePreprocessorOptions.includePaths.length > 0 buildOptions.stylePreprocessorOptions.includePaths.length > 0
) { ) {
buildOptions.stylePreprocessorOptions.includePaths.forEach((includePath: string) => buildOptions.stylePreprocessorOptions.includePaths.forEach(
includePaths.push(path.resolve(root, includePath)), (includePath: string) =>
includePaths.push(path.resolve(root, includePath))
); );
lessPathOptions = { lessPathOptions = {
paths: includePaths, paths: includePaths
}; };
} }
@ -150,10 +151,10 @@ export function getStylesConfig(wco: WebpackConfigOptions) {
sourceMap: cssSourceMap, sourceMap: cssSourceMap,
// bootstrap-sass requires a minimum precision of 8 // bootstrap-sass requires a minimum precision of 8
precision: 8, precision: 8,
includePaths, includePaths
}, }
}, }
], ]
}, },
{ {
test: /\.less$/, test: /\.less$/,
@ -163,10 +164,10 @@ export function getStylesConfig(wco: WebpackConfigOptions) {
options: { options: {
sourceMap: cssSourceMap, sourceMap: cssSourceMap,
javascriptEnabled: true, javascriptEnabled: true,
...lessPathOptions, ...lessPathOptions
}, }
}, }
], ]
}, },
{ {
test: /\.styl$/, test: /\.styl$/,
@ -175,11 +176,11 @@ export function getStylesConfig(wco: WebpackConfigOptions) {
loader: 'stylus-loader', loader: 'stylus-loader',
options: { options: {
sourceMap: cssSourceMap, sourceMap: cssSourceMap,
paths: includePaths, paths: includePaths
}, }
}, }
], ]
}, }
]; ];
// load component css as raw strings // load component css as raw strings
@ -193,17 +194,20 @@ export function getStylesConfig(wco: WebpackConfigOptions) {
options: { options: {
ident: 'embedded', ident: 'embedded',
plugins: postcssPluginCreator, plugins: postcssPluginCreator,
sourceMap: cssSourceMap sourceMap:
cssSourceMap &&
// Never use component css sourcemap when style optimizations are on. // Never use component css sourcemap when style optimizations are on.
// It will just increase bundle size without offering good debug experience. // It will just increase bundle size without offering good debug experience.
&& !buildOptions.optimization.styles !buildOptions.optimization.styles &&
// Inline all sourcemap types except hidden ones, which are the same as no sourcemaps // Inline all sourcemap types except hidden ones, which are the same as no sourcemaps
// for component css. // for component css.
&& !buildOptions.sourceMap.hidden ? 'inline' : false, !buildOptions.sourceMap.hidden
? 'inline'
: false
}
}, },
}, ...(use as webpack.Loader[])
...(use as webpack.Loader[]), ]
],
})); }));
// load global css as css files // load global css as css files
@ -214,7 +218,9 @@ export function getStylesConfig(wco: WebpackConfigOptions) {
include: globalStylePaths, include: globalStylePaths,
test, test,
use: [ use: [
buildOptions.extractCss ? MiniCssExtractPlugin.loader : 'style-loader', buildOptions.extractCss
? MiniCssExtractPlugin.loader
: 'style-loader',
RawCssLoader, RawCssLoader,
{ {
loader: 'postcss-loader', loader: 'postcss-loader',
@ -222,15 +228,17 @@ export function getStylesConfig(wco: WebpackConfigOptions) {
ident: buildOptions.extractCss ? 'extracted' : 'embedded', ident: buildOptions.extractCss ? 'extracted' : 'embedded',
plugins: postcssPluginCreator, plugins: postcssPluginCreator,
sourceMap: sourceMap:
cssSourceMap && !buildOptions.extractCss && !buildOptions.sourceMap.hidden cssSourceMap &&
!buildOptions.extractCss &&
!buildOptions.sourceMap.hidden
? 'inline' ? 'inline'
: cssSourceMap, : cssSourceMap
}
}, },
}, ...(use as webpack.Loader[])
...(use as webpack.Loader[]), ]
],
}; };
}), })
); );
} }
@ -239,13 +247,13 @@ export function getStylesConfig(wco: WebpackConfigOptions) {
// extract global css from js files into own css file // extract global css from js files into own css file
new MiniCssExtractPlugin({ filename: `[name]${hashFormat.extract}.css` }), new MiniCssExtractPlugin({ filename: `[name]${hashFormat.extract}.css` }),
// suppress empty .js files in css only entry points // suppress empty .js files in css only entry points
new SuppressExtractedTextChunksWebpackPlugin(), new SuppressExtractedTextChunksWebpackPlugin()
); );
} }
return { return {
entry: entryPoints, entry: entryPoints,
module: { rules }, module: { rules },
plugins: extraPlugins, plugins: extraPlugins
}; };
} }

View File

@ -12,7 +12,6 @@ import * as webpack from 'webpack';
import { WebpackConfigOptions, WebpackTestOptions } from '../build-options'; import { WebpackConfigOptions, WebpackTestOptions } from '../build-options';
import { getSourceMapDevTool, isPolyfillsEntry } from './utils'; import { getSourceMapDevTool, isPolyfillsEntry } from './utils';
/** /**
* Enumerate loaders and their dependencies from this file to let the dependency validator * Enumerate loaders and their dependencies from this file to let the dependency validator
* know they are used. * know they are used.
@ -22,7 +21,7 @@ import { getSourceMapDevTool, isPolyfillsEntry } from './utils';
*/ */
export function getTestConfig( export function getTestConfig(
wco: WebpackConfigOptions<WebpackTestOptions>, wco: WebpackConfigOptions<WebpackTestOptions>
): webpack.Configuration { ): webpack.Configuration {
const { root, buildOptions, sourceRoot: include } = wco; const { root, buildOptions, sourceRoot: include } = wco;
@ -32,10 +31,7 @@ export function getTestConfig(
// if (buildOptions.codeCoverage && CliConfig.fromProject()) { // if (buildOptions.codeCoverage && CliConfig.fromProject()) {
if (buildOptions.codeCoverage) { if (buildOptions.codeCoverage) {
const codeCoverageExclude = buildOptions.codeCoverageExclude; const codeCoverageExclude = buildOptions.codeCoverageExclude;
const exclude: (string | RegExp)[] = [ const exclude: (string | RegExp)[] = [/\.(e2e|spec)\.ts$/, /node_modules/];
/\.(e2e|spec)\.ts$/,
/node_modules/,
];
if (codeCoverageExclude) { if (codeCoverageExclude) {
codeCoverageExclude.forEach((excludeGlob: string) => { codeCoverageExclude.forEach((excludeGlob: string) => {
@ -52,19 +48,16 @@ export function getTestConfig(
options: { esModules: true }, options: { esModules: true },
enforce: 'post', enforce: 'post',
exclude, exclude,
include, include
}); });
} }
if (wco.buildOptions.sourceMap) { if (wco.buildOptions.sourceMap) {
const { styles, scripts } = wco.buildOptions.sourceMap; const { styles, scripts } = wco.buildOptions.sourceMap;
extraPlugins.push(getSourceMapDevTool( extraPlugins.push(
scripts || false, getSourceMapDevTool(scripts || false, styles || false, false, true)
styles || false, );
false,
true,
));
} }
return { return {
@ -72,34 +65,43 @@ export function getTestConfig(
resolve: { resolve: {
mainFields: [ mainFields: [
...(wco.supportES2015 ? ['es2015'] : []), ...(wco.supportES2015 ? ['es2015'] : []),
'browser', 'module', 'main', 'browser',
], 'module',
'main'
]
}, },
devtool: buildOptions.sourceMap ? false : 'eval', devtool: buildOptions.sourceMap ? false : 'eval',
entry: { entry: {
main: path.resolve(root, buildOptions.main), main: path.resolve(root, buildOptions.main)
}, },
module: { module: {
rules: extraRules, rules: extraRules
}, },
plugins: extraPlugins, plugins: extraPlugins,
optimization: { optimization: {
splitChunks: { splitChunks: {
chunks: ((chunk: { name: string }) => !isPolyfillsEntry(chunk.name)), chunks: (chunk: { name: string }) => !isPolyfillsEntry(chunk.name),
cacheGroups: { cacheGroups: {
vendors: false, vendors: false,
vendor: { vendor: {
name: 'vendor', name: 'vendor',
chunks: 'initial', chunks: 'initial',
test: (module: { nameForCondition?: () => string }, chunks: { name: string }[]) => { test: (
const moduleName = module.nameForCondition ? module.nameForCondition() : ''; module: { nameForCondition?: () => string },
chunks: { name: string }[]
) => {
const moduleName = module.nameForCondition
? module.nameForCondition()
: '';
return /[\\/]node_modules[\\/]/.test(moduleName) return (
&& !chunks.some(({ name }) => isPolyfillsEntry(name)); /[\\/]node_modules[\\/]/.test(moduleName) &&
}, !chunks.some(({ name }) => isPolyfillsEntry(name))
}, );
}, }
}, }
}, }
}
}
}; };
} }

View File

@ -24,7 +24,7 @@ function _pluginOptionsOverrides(
): AngularCompilerPluginOptions { ): AngularCompilerPluginOptions {
const compilerOptions = { const compilerOptions = {
...(pluginOptions.compilerOptions || {}) ...(pluginOptions.compilerOptions || {})
} };
const hostReplacementPaths: { [replace: string]: string } = {}; const hostReplacementPaths: { [replace: string]: string } = {};
if (buildOptions.fileReplacements) { if (buildOptions.fileReplacements) {
@ -51,7 +51,7 @@ function _pluginOptionsOverrides(
function _createAotPlugin( function _createAotPlugin(
wco: WebpackConfigOptions, wco: WebpackConfigOptions,
options: AngularCompilerPluginOptions, options: AngularCompilerPluginOptions,
i18nExtract = false, i18nExtract = false
) { ) {
const { root, buildOptions } = wco; const { root, buildOptions } = wco;
@ -62,19 +62,17 @@ function _createAotPlugin(
const i18nFileAndFormat = i18nExtract const i18nFileAndFormat = i18nExtract
? { ? {
i18nOutFile: buildOptions.i18nFile, i18nOutFile: buildOptions.i18nFile,
i18nOutFormat: buildOptions.i18nFormat, i18nOutFormat: buildOptions.i18nFormat
} : { }
: {
i18nInFile: i18nInFile, i18nInFile: i18nInFile,
i18nInFormat: buildOptions.i18nFormat, i18nInFormat: buildOptions.i18nFormat
}; };
const additionalLazyModules: { [module: string]: string } = {}; const additionalLazyModules: { [module: string]: string } = {};
if (buildOptions.lazyModules) { if (buildOptions.lazyModules) {
for (const lazyModule of buildOptions.lazyModules) { for (const lazyModule of buildOptions.lazyModules) {
additionalLazyModules[lazyModule] = path.resolve( additionalLazyModules[lazyModule] = path.resolve(root, lazyModule);
root,
lazyModule,
);
} }
} }
@ -82,7 +80,8 @@ function _createAotPlugin(
mainPath: path.join(root, buildOptions.main), mainPath: path.join(root, buildOptions.main),
...i18nFileAndFormat, ...i18nFileAndFormat,
locale: buildOptions.i18nLocale, locale: buildOptions.i18nLocale,
platform: buildOptions.platform === 'server' ? PLATFORM.Server : PLATFORM.Browser, platform:
buildOptions.platform === 'server' ? PLATFORM.Server : PLATFORM.Browser,
missingTranslation: buildOptions.i18nMissingTranslation, missingTranslation: buildOptions.i18nMissingTranslation,
sourceMap: buildOptions.sourceMap.scripts, sourceMap: buildOptions.sourceMap.scripts,
additionalLazyModules, additionalLazyModules,
@ -91,7 +90,7 @@ function _createAotPlugin(
contextElementDependencyConstructor: require('webpack/lib/dependencies/ContextElementDependency'), contextElementDependencyConstructor: require('webpack/lib/dependencies/ContextElementDependency'),
logger: wco.logger, logger: wco.logger,
directTemplateLoading: true, directTemplateLoading: true,
...options, ...options
}; };
pluginOptions = _pluginOptionsOverrides(buildOptions, pluginOptions); pluginOptions = _pluginOptionsOverrides(buildOptions, pluginOptions);
@ -127,7 +126,10 @@ export function getAotConfig(wco: WebpackConfigOptions, i18nExtract = false) {
}; };
} }
export function getTypescriptWorkerPlugin(wco: WebpackConfigOptions, workerTsConfigPath: string) { export function getTypescriptWorkerPlugin(
wco: WebpackConfigOptions,
workerTsConfigPath: string
) {
const { buildOptions } = wco; const { buildOptions } = wco;
let pluginOptions: AngularCompilerPluginOptions = { let pluginOptions: AngularCompilerPluginOptions = {
@ -142,7 +144,7 @@ export function getTypescriptWorkerPlugin(wco: WebpackConfigOptions, workerTsCon
// Run no transformers. // Run no transformers.
platformTransformers: [], platformTransformers: [],
// Don't attempt lazy route discovery. // Don't attempt lazy route discovery.
discoverLazyRoutes: false, discoverLazyRoutes: false
}; };
pluginOptions = _pluginOptionsOverrides(buildOptions, pluginOptions); pluginOptions = _pluginOptionsOverrides(buildOptions, pluginOptions);

View File

@ -28,14 +28,14 @@ export function getOutputHashFormat(option: string, length = 20): HashFormat {
chunk: `.[chunkhash:${length}]`, chunk: `.[chunkhash:${length}]`,
extract: `.[contenthash:${length}]`, extract: `.[contenthash:${length}]`,
file: '', file: '',
script: `.[hash:${length}]`, script: `.[hash:${length}]`
}, },
all: { all: {
chunk: `.[chunkhash:${length}]`, chunk: `.[chunkhash:${length}]`,
extract: `.[contenthash:${length}]`, extract: `.[contenthash:${length}]`,
file: `.[hash:${length}]`, file: `.[hash:${length}]`,
script: `.[hash:${length}]`, script: `.[hash:${length}]`
}, }
}; };
return hashFormats[option] || hashFormats['none']; return hashFormats[option] || hashFormats['none'];
} }
@ -46,12 +46,16 @@ export type NormalizedEntryPoint = Required<Omit<ExtraEntryPointClass, 'lazy'>>;
export function normalizeExtraEntryPoints( export function normalizeExtraEntryPoints(
extraEntryPoints: ExtraEntryPoint[], extraEntryPoints: ExtraEntryPoint[],
defaultBundleName: string, defaultBundleName: string
): NormalizedEntryPoint[] { ): NormalizedEntryPoint[] {
return extraEntryPoints.map(entry => { return extraEntryPoints.map(entry => {
let normalizedEntry; let normalizedEntry;
if (typeof entry === 'string') { if (typeof entry === 'string') {
normalizedEntry = { input: entry, inject: true, bundleName: defaultBundleName }; normalizedEntry = {
input: entry,
inject: true,
bundleName: defaultBundleName
};
} else { } else {
const { lazy, inject = true, ...newEntry } = entry; const { lazy, inject = true, ...newEntry } = entry;
const injectNormalized = entry.lazy !== undefined ? !entry.lazy : inject; const injectNormalized = entry.lazy !== undefined ? !entry.lazy : inject;
@ -62,7 +66,7 @@ export function normalizeExtraEntryPoints(
} else if (!injectNormalized) { } else if (!injectNormalized) {
// Lazy entry points use the file name as bundle name. // Lazy entry points use the file name as bundle name.
bundleName = basename( bundleName = basename(
normalize(entry.input.replace(/\.(js|css|scss|sass|less|styl)$/i, '')), normalize(entry.input.replace(/\.(js|css|scss|sass|less|styl)$/i, ''))
); );
} else { } else {
bundleName = defaultBundleName; bundleName = defaultBundleName;
@ -79,7 +83,7 @@ export function getSourceMapDevTool(
scriptsSourceMap: boolean, scriptsSourceMap: boolean,
stylesSourceMap: boolean, stylesSourceMap: boolean,
hiddenSourceMap = false, hiddenSourceMap = false,
inlineSourceMap = false, inlineSourceMap = false
): SourceMapDevToolPlugin { ): SourceMapDevToolPlugin {
const include = []; const include = [];
if (scriptsSourceMap) { if (scriptsSourceMap) {
@ -99,7 +103,7 @@ export function getSourceMapDevTool(
// there is no way to set the 'webRoot' // there is no way to set the 'webRoot'
sourceRoot: inlineSourceMap ? '' : 'webpack:///', sourceRoot: inlineSourceMap ? '' : 'webpack:///',
moduleFilenameTemplate: '[resource-path]', moduleFilenameTemplate: '[resource-path]',
append: hiddenSourceMap ? false : undefined, append: hiddenSourceMap ? false : undefined
}); });
} }
@ -108,7 +112,7 @@ export function getSourceMapDevTool(
*/ */
export function getEsVersionForFileName( export function getEsVersionForFileName(
scriptTargetOverride: ScriptTarget | undefined, scriptTargetOverride: ScriptTarget | undefined,
esVersionInFileName = false, esVersionInFileName = false
): string { ): string {
return scriptTargetOverride && esVersionInFileName return scriptTargetOverride && esVersionInFileName
? '-' + ScriptTarget[scriptTargetOverride].toLowerCase() ? '-' + ScriptTarget[scriptTargetOverride].toLowerCase()

View File

@ -12,7 +12,6 @@ import { getTypescriptWorkerPlugin } from './typescript';
const WorkerPlugin = require('worker-plugin'); const WorkerPlugin = require('worker-plugin');
export function getWorkerConfig(wco: WebpackConfigOptions): Configuration { export function getWorkerConfig(wco: WebpackConfigOptions): Configuration {
const { buildOptions } = wco; const { buildOptions } = wco;
@ -27,9 +26,11 @@ export function getWorkerConfig(wco: WebpackConfigOptions): Configuration {
const workerTsConfigPath = resolve(wco.root, buildOptions.webWorkerTsConfig); const workerTsConfigPath = resolve(wco.root, buildOptions.webWorkerTsConfig);
return { return {
plugins: [new WorkerPlugin({ plugins: [
new WorkerPlugin({
globalObject: false, globalObject: false,
plugins: [getTypescriptWorkerPlugin(wco, workerTsConfigPath)], plugins: [getTypescriptWorkerPlugin(wco, workerTsConfigPath)]
})], })
]
}; };
} }

View File

@ -7,7 +7,11 @@
*/ */
import { Compiler, compilation } from 'webpack'; import { Compiler, compilation } from 'webpack';
import { Budget, Type } from '../../browser/schema'; import { Budget, Type } from '../../browser/schema';
import { Size, calculateBytes, calculateSizes } from '../utilities/bundle-calculator'; import {
Size,
calculateBytes,
calculateSizes
} from '../utilities/bundle-calculator';
import { formatSize } from '../utilities/stats'; import { formatSize } from '../utilities/stats';
interface Thresholds { interface Thresholds {
@ -26,7 +30,7 @@ export interface BundleBudgetPluginOptions {
} }
export class BundleBudgetPlugin { export class BundleBudgetPlugin {
constructor(private options: BundleBudgetPluginOptions) { } constructor(private options: BundleBudgetPluginOptions) {}
apply(compiler: Compiler): void { apply(compiler: Compiler): void {
const { budgets } = this.options; const { budgets } = this.options;
@ -35,42 +39,70 @@ export class BundleBudgetPlugin {
return; return;
} }
compiler.hooks.compilation.tap('BundleBudgetPlugin', (compilation: compilation.Compilation) => { compiler.hooks.compilation.tap(
compilation.hooks.afterOptimizeChunkAssets.tap('BundleBudgetPlugin', () => { 'BundleBudgetPlugin',
(compilation: compilation.Compilation) => {
compilation.hooks.afterOptimizeChunkAssets.tap(
'BundleBudgetPlugin',
() => {
// In AOT compilations component styles get processed in child compilations. // In AOT compilations component styles get processed in child compilations.
// tslint:disable-next-line: no-any // tslint:disable-next-line: no-any
const parentCompilation = (compilation.compiler as any).parentCompilation; const parentCompilation = (compilation.compiler as any)
.parentCompilation;
if (!parentCompilation) { if (!parentCompilation) {
return; return;
} }
const filteredBudgets = budgets.filter(budget => budget.type === Type.AnyComponentStyle); const filteredBudgets = budgets.filter(
budget => budget.type === Type.AnyComponentStyle
);
this.runChecks(filteredBudgets, compilation); this.runChecks(filteredBudgets, compilation);
}); }
}); );
}
);
compiler.hooks.afterEmit.tap('BundleBudgetPlugin', (compilation: compilation.Compilation) => { compiler.hooks.afterEmit.tap(
const filteredBudgets = budgets.filter(budget => budget.type !== Type.AnyComponentStyle); 'BundleBudgetPlugin',
(compilation: compilation.Compilation) => {
const filteredBudgets = budgets.filter(
budget => budget.type !== Type.AnyComponentStyle
);
this.runChecks(filteredBudgets, compilation); this.runChecks(filteredBudgets, compilation);
}); }
);
} }
private checkMinimum(threshold: number | undefined, size: Size, messages: string[]) { private checkMinimum(
threshold: number | undefined,
size: Size,
messages: string[]
) {
if (threshold) { if (threshold) {
if (threshold > size.size) { if (threshold > size.size) {
const sizeDifference = formatSize(threshold - size.size); const sizeDifference = formatSize(threshold - size.size);
messages.push(`budgets, minimum exceeded for ${size.label}. ` messages.push(
+ `Budget ${formatSize(threshold)} was not reached by ${sizeDifference}.`); `budgets, minimum exceeded for ${size.label}. ` +
`Budget ${formatSize(
threshold
)} was not reached by ${sizeDifference}.`
);
} }
} }
} }
private checkMaximum(threshold: number | undefined, size: Size, messages: string[]) { private checkMaximum(
threshold: number | undefined,
size: Size,
messages: string[]
) {
if (threshold) { if (threshold) {
if (threshold < size.size) { if (threshold < size.size) {
const sizeDifference = formatSize(size.size - threshold); const sizeDifference = formatSize(size.size - threshold);
messages.push(`budgets, maximum exceeded for ${size.label}. ` messages.push(
+ `Budget ${formatSize(threshold)} was exceeded by ${sizeDifference}.`); `budgets, maximum exceeded for ${size.label}. ` +
`Budget ${formatSize(threshold)} was exceeded by ${sizeDifference}.`
);
} }
} }
} }
@ -78,27 +110,51 @@ export class BundleBudgetPlugin {
private calculate(budget: Budget): Thresholds { private calculate(budget: Budget): Thresholds {
const thresholds: Thresholds = {}; const thresholds: Thresholds = {};
if (budget.maximumWarning) { if (budget.maximumWarning) {
thresholds.maximumWarning = calculateBytes(budget.maximumWarning, budget.baseline, 1); thresholds.maximumWarning = calculateBytes(
budget.maximumWarning,
budget.baseline,
1
);
} }
if (budget.maximumError) { if (budget.maximumError) {
thresholds.maximumError = calculateBytes(budget.maximumError, budget.baseline, 1); thresholds.maximumError = calculateBytes(
budget.maximumError,
budget.baseline,
1
);
} }
if (budget.minimumWarning) { if (budget.minimumWarning) {
thresholds.minimumWarning = calculateBytes(budget.minimumWarning, budget.baseline, -1); thresholds.minimumWarning = calculateBytes(
budget.minimumWarning,
budget.baseline,
-1
);
} }
if (budget.minimumError) { if (budget.minimumError) {
thresholds.minimumError = calculateBytes(budget.minimumError, budget.baseline, -1); thresholds.minimumError = calculateBytes(
budget.minimumError,
budget.baseline,
-1
);
} }
if (budget.warning) { if (budget.warning) {
thresholds.warningLow = calculateBytes(budget.warning, budget.baseline, -1); thresholds.warningLow = calculateBytes(
budget.warning,
budget.baseline,
-1
);
} }
if (budget.warning) { if (budget.warning) {
thresholds.warningHigh = calculateBytes(budget.warning, budget.baseline, 1); thresholds.warningHigh = calculateBytes(
budget.warning,
budget.baseline,
1
);
} }
if (budget.error) { if (budget.error) {
@ -117,18 +173,50 @@ export class BundleBudgetPlugin {
.map(budget => ({ .map(budget => ({
budget, budget,
thresholds: this.calculate(budget), thresholds: this.calculate(budget),
sizes: calculateSizes(budget, compilation), sizes: calculateSizes(budget, compilation)
})) }))
.forEach(budgetCheck => { .forEach(budgetCheck => {
budgetCheck.sizes.forEach(size => { budgetCheck.sizes.forEach(size => {
this.checkMaximum(budgetCheck.thresholds.maximumWarning, size, compilation.warnings); this.checkMaximum(
this.checkMaximum(budgetCheck.thresholds.maximumError, size, compilation.errors); budgetCheck.thresholds.maximumWarning,
this.checkMinimum(budgetCheck.thresholds.minimumWarning, size, compilation.warnings); size,
this.checkMinimum(budgetCheck.thresholds.minimumError, size, compilation.errors); compilation.warnings
this.checkMinimum(budgetCheck.thresholds.warningLow, size, compilation.warnings); );
this.checkMaximum(budgetCheck.thresholds.warningHigh, size, compilation.warnings); this.checkMaximum(
this.checkMinimum(budgetCheck.thresholds.errorLow, size, compilation.errors); budgetCheck.thresholds.maximumError,
this.checkMaximum(budgetCheck.thresholds.errorHigh, size, compilation.errors); size,
compilation.errors
);
this.checkMinimum(
budgetCheck.thresholds.minimumWarning,
size,
compilation.warnings
);
this.checkMinimum(
budgetCheck.thresholds.minimumError,
size,
compilation.errors
);
this.checkMinimum(
budgetCheck.thresholds.warningLow,
size,
compilation.warnings
);
this.checkMaximum(
budgetCheck.thresholds.warningHigh,
size,
compilation.warnings
);
this.checkMinimum(
budgetCheck.thresholds.errorLow,
size,
compilation.errors
);
this.checkMaximum(
budgetCheck.thresholds.errorHigh,
size,
compilation.errors
);
}); });
}); });
} }

View File

@ -18,16 +18,17 @@ function hook(
compiler: Compiler, compiler: Compiler,
action: ( action: (
compilation: compilation.Compilation, compilation: compilation.Compilation,
chunks: compilation.Chunk[], chunks: compilation.Chunk[]
) => Promise<void | void[]>, ) => Promise<void | void[]>
) { ) {
compiler.hooks.compilation.tap( compiler.hooks.compilation.tap(
'cleancss-webpack-plugin', 'cleancss-webpack-plugin',
(compilation: compilation.Compilation) => { (compilation: compilation.Compilation) => {
compilation.hooks.optimizeChunkAssets.tapPromise('cleancss-webpack-plugin', chunks => compilation.hooks.optimizeChunkAssets.tapPromise(
action(compilation, chunks), 'cleancss-webpack-plugin',
chunks => action(compilation, chunks)
); );
}, }
); );
} }
@ -38,25 +39,27 @@ export class CleanCssWebpackPlugin {
this._options = { this._options = {
sourceMap: false, sourceMap: false,
test: file => file.endsWith('.css'), test: file => file.endsWith('.css'),
...options, ...options
}; };
} }
apply(compiler: Compiler): void { apply(compiler: Compiler): void {
hook(compiler, (compilation: compilation.Compilation, chunks: compilation.Chunk[]) => { hook(
compiler,
(compilation: compilation.Compilation, chunks: compilation.Chunk[]) => {
const cleancss = new CleanCSS({ const cleancss = new CleanCSS({
compatibility: 'ie9', compatibility: 'ie9',
level: { level: {
2: { 2: {
skipProperties: [ skipProperties: [
'transition', // Fixes #12408 'transition', // Fixes #12408
'font', // Fixes #9648 'font' // Fixes #9648
], ]
}, }
}, },
inline: false, inline: false,
returnPromise: true, returnPromise: true,
sourceMap: this._options.sourceMap, sourceMap: this._options.sourceMap
}); });
const files: string[] = [...compilation.additionalChunkAssets]; const files: string[] = [...compilation.additionalChunkAssets];
@ -99,7 +102,9 @@ export class CleanCssWebpackPlugin {
} }
if (output.errors && output.errors.length > 0) { if (output.errors && output.errors.length > 0) {
output.errors.forEach((error: string) => compilation.errors.push(new Error(error))); output.errors.forEach((error: string) =>
compilation.errors.push(new Error(error))
);
return; return;
} }
@ -117,7 +122,7 @@ export class CleanCssWebpackPlugin {
// tslint:disable-next-line: no-any // tslint:disable-next-line: no-any
output.sourceMap.toString() as any, output.sourceMap.toString() as any,
content, content,
map, map
); );
} else { } else {
newSource = new RawSource(output.styles); newSource = new RawSource(output.styles);
@ -127,6 +132,7 @@ export class CleanCssWebpackPlugin {
}); });
return Promise.all(actions); return Promise.all(actions);
}); }
);
} }
} }

View File

@ -11,7 +11,7 @@ import { RawSource } from 'webpack-sources';
import { import {
CrossOriginValue, CrossOriginValue,
FileInfo, FileInfo,
augmentIndexHtml, augmentIndexHtml
} from '../utilities/index-file/augment-index-html'; } from '../utilities/index-file/augment-index-html';
import { IndexHtmlTransform } from '../utilities/index-file/write-index-html'; import { IndexHtmlTransform } from '../utilities/index-file/write-index-html';
import { stripBom } from '../utilities/strip-bom'; import { stripBom } from '../utilities/strip-bom';
@ -29,9 +29,14 @@ export interface IndexHtmlWebpackPluginOptions {
crossOrigin?: CrossOriginValue; crossOrigin?: CrossOriginValue;
} }
function readFile(filename: string, compilation: compilation.Compilation): Promise<string> { function readFile(
filename: string,
compilation: compilation.Compilation
): Promise<string> {
return new Promise<string>((resolve, reject) => { return new Promise<string>((resolve, reject) => {
compilation.inputFileSystem.readFile(filename, (err: Error, data: Buffer) => { compilation.inputFileSystem.readFile(
filename,
(err: Error, data: Buffer) => {
if (err) { if (err) {
reject(err); reject(err);
@ -39,7 +44,8 @@ function readFile(filename: string, compilation: compilation.Compilation): Promi
} }
resolve(stripBom(data.toString())); resolve(stripBom(data.toString()));
}); }
);
}); });
} }
@ -54,12 +60,14 @@ export class IndexHtmlWebpackPlugin {
noModuleEntrypoints: [], noModuleEntrypoints: [],
moduleEntrypoints: [], moduleEntrypoints: [],
sri: false, sri: false,
...options, ...options
}; };
} }
apply(compiler: Compiler) { apply(compiler: Compiler) {
compiler.hooks.emit.tapPromise('index-html-webpack-plugin', async compilation => { compiler.hooks.emit.tapPromise(
'index-html-webpack-plugin',
async compilation => {
// Get input html file // Get input html file
const inputContent = await readFile(this._options.input, compilation); const inputContent = await readFile(this._options.input, compilation);
compilation.fileDependencies.add(this._options.input); compilation.fileDependencies.add(this._options.input);
@ -70,12 +78,15 @@ export class IndexHtmlWebpackPlugin {
const moduleFiles: FileInfo[] = []; const moduleFiles: FileInfo[] = [];
for (const [entryName, entrypoint] of compilation.entrypoints) { for (const [entryName, entrypoint] of compilation.entrypoints) {
const entryFiles: FileInfo[] = ((entrypoint && entrypoint.getFiles()) || []).map( const entryFiles: FileInfo[] = (
(entrypoint && entrypoint.getFiles()) ||
[]
).map(
(f: string): FileInfo => ({ (f: string): FileInfo => ({
name: entryName, name: entryName,
file: f, file: f,
extension: path.extname(f), extension: path.extname(f)
}), })
); );
if (this._options.noModuleEntrypoints.includes(entryName)) { if (this._options.noModuleEntrypoints.includes(entryName)) {
@ -87,7 +98,8 @@ export class IndexHtmlWebpackPlugin {
} }
} }
const loadOutputFile = (name: string) => compilation.assets[name].source(); const loadOutputFile = (name: string) =>
compilation.assets[name].source();
let indexSource = await augmentIndexHtml({ let indexSource = await augmentIndexHtml({
input: this._options.input, input: this._options.input,
inputContent, inputContent,
@ -99,7 +111,7 @@ export class IndexHtmlWebpackPlugin {
noModuleFiles, noModuleFiles,
loadOutputFile, loadOutputFile,
moduleFiles, moduleFiles,
entrypoints: this._options.entrypoints, entrypoints: this._options.entrypoints
}); });
if (this._options.postTransform) { if (this._options.postTransform) {
@ -108,6 +120,7 @@ export class IndexHtmlWebpackPlugin {
// Add to compilation assets // Add to compilation assets
compilation.assets[this._options.output] = new RawSource(indexSource); compilation.assets[this._options.output] = new RawSource(indexSource);
}); }
);
} }
} }

View File

@ -14,26 +14,28 @@ const ImportDependenciesBlock = require('webpack/lib/dependencies/ImportDependen
const Template = require('webpack/lib/Template'); const Template = require('webpack/lib/Template');
export class NamedLazyChunksPlugin { export class NamedLazyChunksPlugin {
constructor() { } constructor() {}
apply(compiler: Compiler): void { apply(compiler: Compiler): void {
compiler.hooks.compilation.tap('named-lazy-chunks-plugin', compilation => { compiler.hooks.compilation.tap('named-lazy-chunks-plugin', compilation => {
// The dependencyReference hook isn't in the webpack typings so we have to type it as any. // The dependencyReference hook isn't in the webpack typings so we have to type it as any.
// tslint:disable-next-line: no-any // tslint:disable-next-line: no-any
(compilation.hooks as any).dependencyReference.tap('named-lazy-chunks-plugin', (compilation.hooks as any).dependencyReference.tap(
'named-lazy-chunks-plugin',
// tslint:disable-next-line: no-any // tslint:disable-next-line: no-any
(_: any, dependency: any) => { (_: any, dependency: any) => {
if ( if (
// Check this dependency is from an `import()` statement. // Check this dependency is from an `import()` statement.
dependency instanceof ImportDependency dependency instanceof ImportDependency &&
&& dependency.block instanceof ImportDependenciesBlock dependency.block instanceof ImportDependenciesBlock &&
// Don't rename chunks that already have a name. // Don't rename chunks that already have a name.
&& dependency.block.chunkName === null dependency.block.chunkName === null
) { ) {
// Convert the request to a valid chunk name using the same logic used // Convert the request to a valid chunk name using the same logic used
// in webpack/lib/ContextModule.js // in webpack/lib/ContextModule.js
dependency.block.chunkName = Template.toPath(dependency.request); dependency.block.chunkName = Template.toPath(dependency.request);
} }
}); }
);
}); });
} }
} }

View File

@ -13,7 +13,7 @@ import * as webpack from 'webpack';
function wrapUrl(url: string): string { function wrapUrl(url: string): string {
let wrappedUrl; let wrappedUrl;
const hasSingleQuotes = url.indexOf('\'') >= 0; const hasSingleQuotes = url.indexOf("'") >= 0;
if (hasSingleQuotes) { if (hasSingleQuotes) {
wrappedUrl = `"${url}"`; wrappedUrl = `"${url}"`;
@ -36,7 +36,7 @@ export interface PostcssCliResourcesOptions {
async function resolve( async function resolve(
file: string, file: string,
base: string, base: string,
resolver: (file: string, base: string) => Promise<string>, resolver: (file: string, base: string) => Promise<string>
): Promise<string> { ): Promise<string> {
try { try {
return await resolver('./' + file, base); return await resolver('./' + file, base);
@ -45,19 +45,25 @@ async function resolve(
} }
} }
export default postcss.plugin('postcss-cli-resources', (options: PostcssCliResourcesOptions) => { export default postcss.plugin(
'postcss-cli-resources',
(options: PostcssCliResourcesOptions) => {
const { const {
deployUrl = '', deployUrl = '',
baseHref = '', baseHref = '',
resourcesOutputPath = '', resourcesOutputPath = '',
rebaseRootRelative = false, rebaseRootRelative = false,
filename, filename,
loader, loader
} = options; } = options;
const dedupeSlashes = (url: string) => url.replace(/\/\/+/g, '/'); const dedupeSlashes = (url: string) => url.replace(/\/\/+/g, '/');
const process = async (inputUrl: string, context: string, resourceCache: Map<string, string>) => { const process = async (
inputUrl: string,
context: string,
resourceCache: Map<string, string>
) => {
// If root-relative, absolute or protocol relative url, leave as is // If root-relative, absolute or protocol relative url, leave as is
if (/^((?:\w+:)?\/\/|data:|chrome:|#)/.test(inputUrl)) { if (/^((?:\w+:)?\/\/|data:|chrome:|#)/.test(inputUrl)) {
return inputUrl; return inputUrl;
@ -90,7 +96,9 @@ export default postcss.plugin('postcss-cli-resources', (options: PostcssCliResou
outputUrl = `${deployUrl.replace(/\/$/, '')}${inputUrl}`; outputUrl = `${deployUrl.replace(/\/$/, '')}${inputUrl}`;
} else if (baseHref.match(/:\/\//)) { } else if (baseHref.match(/:\/\//)) {
// If baseHref contains a scheme, include it as is. // If baseHref contains a scheme, include it as is.
outputUrl = baseHref.replace(/\/$/, '') + dedupeSlashes(`/${deployUrl}/${inputUrl}`); outputUrl =
baseHref.replace(/\/$/, '') +
dedupeSlashes(`/${deployUrl}/${inputUrl}`);
} else { } else {
// Join together base-href, deploy-url and the original URL. // Join together base-href, deploy-url and the original URL.
outputUrl = dedupeSlashes(`/${baseHref}/${deployUrl}/${inputUrl}`); outputUrl = dedupeSlashes(`/${baseHref}/${deployUrl}/${inputUrl}`);
@ -101,8 +109,11 @@ export default postcss.plugin('postcss-cli-resources', (options: PostcssCliResou
return outputUrl; return outputUrl;
} }
const { pathname, hash, search } = url.parse(inputUrl.replace(/\\/g, '/')); const { pathname, hash, search } = url.parse(
const resolver = (file: string, base: string) => new Promise<string>((resolve, reject) => { inputUrl.replace(/\\/g, '/')
);
const resolver = (file: string, base: string) =>
new Promise<string>((resolve, reject) => {
loader.resolve(base, decodeURI(file), (err, result) => { loader.resolve(base, decodeURI(file), (err, result) => {
if (err) { if (err) {
reject(err); reject(err);
@ -126,7 +137,7 @@ export default postcss.plugin('postcss-cli-resources', (options: PostcssCliResou
let outputPath = interpolateName( let outputPath = interpolateName(
{ resourcePath: result } as webpack.loader.LoaderContext, { resourcePath: result } as webpack.loader.LoaderContext,
filename, filename,
{ content }, { content }
); );
if (resourcesOutputPath) { if (resourcesOutputPath) {
@ -141,7 +152,10 @@ export default postcss.plugin('postcss-cli-resources', (options: PostcssCliResou
outputUrl = url.format({ pathname: outputUrl, hash, search }); outputUrl = url.format({ pathname: outputUrl, hash, search });
} }
if (deployUrl && loader.loaders[loader.loaderIndex].options.ident !== 'extracted') { if (
deployUrl &&
loader.loaders[loader.loaderIndex].options.ident !== 'extracted'
) {
outputUrl = url.resolve(deployUrl, outputUrl); outputUrl = url.resolve(deployUrl, outputUrl);
} }
@ -151,7 +165,7 @@ export default postcss.plugin('postcss-cli-resources', (options: PostcssCliResou
}); });
}; };
return (root) => { return root => {
const urlDeclarations: Array<postcss.Declaration> = []; const urlDeclarations: Array<postcss.Declaration> = [];
root.walkDecls(decl => { root.walkDecls(decl => {
if (decl.value && decl.value.includes('url')) { if (decl.value && decl.value.includes('url')) {
@ -165,7 +179,8 @@ export default postcss.plugin('postcss-cli-resources', (options: PostcssCliResou
const resourceCache = new Map<string, string>(); const resourceCache = new Map<string, string>();
return Promise.all(urlDeclarations.map(async decl => { return Promise.all(
urlDeclarations.map(async decl => {
const value = decl.value; const value = decl.value;
const urlRegex = /url\(\s*(?:"([^"]+)"|'([^']+)'|(.+?))\s*\)/g; const urlRegex = /url\(\s*(?:"([^"]+)"|'([^']+)'|(.+?))\s*\)/g;
const segments: string[] = []; const segments: string[] = [];
@ -176,16 +191,19 @@ export default postcss.plugin('postcss-cli-resources', (options: PostcssCliResou
// We want to load it relative to the file that imports // We want to load it relative to the file that imports
const inputFile = decl.source && decl.source.input.file; const inputFile = decl.source && decl.source.input.file;
const context = inputFile && path.dirname(inputFile) || loader.context; const context =
(inputFile && path.dirname(inputFile)) || loader.context;
// tslint:disable-next-line:no-conditional-assignment // tslint:disable-next-line:no-conditional-assignment
while (match = urlRegex.exec(value)) { while ((match = urlRegex.exec(value))) {
const originalUrl = match[1] || match[2] || match[3]; const originalUrl = match[1] || match[2] || match[3];
let processedUrl; let processedUrl;
try { try {
processedUrl = await process(originalUrl, context, resourceCache); processedUrl = await process(originalUrl, context, resourceCache);
} catch (err) { } catch (err) {
loader.emitError(decl.error(err.message, { word: originalUrl }).toString()); loader.emitError(
decl.error(err.message, { word: originalUrl }).toString()
);
continue; continue;
} }
@ -210,6 +228,8 @@ export default postcss.plugin('postcss-cli-resources', (options: PostcssCliResou
if (modified) { if (modified) {
decl.value = segments.join(''); decl.value = segments.join('');
} }
})); })
);
}; };
}); }
);

View File

@ -8,15 +8,13 @@
import { Compiler, compilation } from 'webpack'; import { Compiler, compilation } from 'webpack';
import { HashFormat } from '../models/webpack-configs/utils'; import { HashFormat } from '../models/webpack-configs/utils';
export interface RemoveHashPluginOptions { export interface RemoveHashPluginOptions {
chunkNames: string[]; chunkNames: string[];
hashFormat: HashFormat; hashFormat: HashFormat;
} }
export class RemoveHashPlugin { export class RemoveHashPlugin {
constructor(private options: RemoveHashPluginOptions) {}
constructor(private options: RemoveHashPluginOptions) { }
apply(compiler: Compiler): void { apply(compiler: Compiler): void {
compiler.hooks.compilation.tap('remove-hash-plugin', compilation => { compiler.hooks.compilation.tap('remove-hash-plugin', compilation => {
@ -24,7 +22,8 @@ export class RemoveHashPlugin {
hooks: compilation.CompilationHooks; hooks: compilation.CompilationHooks;
}; };
mainTemplate.hooks.assetPath.tap('remove-hash-plugin', mainTemplate.hooks.assetPath.tap(
'remove-hash-plugin',
(path: string, data: { chunk?: { name: string } }) => { (path: string, data: { chunk?: { name: string } }) => {
const chunkName = data.chunk && data.chunk.name; const chunkName = data.chunk && data.chunk.name;
const { chunkNames, hashFormat } = this.options; const { chunkNames, hashFormat } = this.options;
@ -37,7 +36,7 @@ export class RemoveHashPlugin {
} }
return path; return path;
}, }
); );
}); });
} }

View File

@ -16,7 +16,13 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import { Compiler, loader } from 'webpack'; import { Compiler, loader } from 'webpack';
import { CachedSource, ConcatSource, OriginalSource, RawSource, Source } from 'webpack-sources'; import {
CachedSource,
ConcatSource,
OriginalSource,
RawSource,
Source
} from 'webpack-sources';
import { interpolateName } from 'loader-utils'; import { interpolateName } from 'loader-utils';
import * as path from 'path'; import * as path from 'path';
@ -42,20 +48,26 @@ function addDependencies(compilation: any, scripts: string[]): void {
} }
} }
function hook(compiler: any, action: (compilation: any, callback: (err?: Error) => void) => void) { function hook(
compiler.hooks.thisCompilation.tap('scripts-webpack-plugin', (compilation: any) => { compiler: any,
action: (compilation: any, callback: (err?: Error) => void) => void
) {
compiler.hooks.thisCompilation.tap(
'scripts-webpack-plugin',
(compilation: any) => {
compilation.hooks.additionalAssets.tapAsync( compilation.hooks.additionalAssets.tapAsync(
'scripts-webpack-plugin', 'scripts-webpack-plugin',
(callback: (err?: Error) => void) => action(compilation, callback), (callback: (err?: Error) => void) => action(compilation, callback)
);
}
); );
});
} }
export class ScriptsWebpackPlugin { export class ScriptsWebpackPlugin {
private _lastBuildTime?: number; private _lastBuildTime?: number;
private _cachedOutput?: ScriptOutput; private _cachedOutput?: ScriptOutput;
constructor(private options: Partial<ScriptsWebpackPluginOptions> = {}) { } constructor(private options: Partial<ScriptsWebpackPluginOptions> = {}) {}
shouldSkip(compilation: any, scripts: string[]): boolean { shouldSkip(compilation: any, scripts: string[]): boolean {
if (this._lastBuildTime == undefined) { if (this._lastBuildTime == undefined) {
@ -74,7 +86,11 @@ export class ScriptsWebpackPlugin {
return true; return true;
} }
private _insertOutput(compilation: any, { filename, source }: ScriptOutput, cached = false) { private _insertOutput(
compilation: any,
{ filename, source }: ScriptOutput,
cached = false
) {
const chunk = new Chunk(this.options.name); const chunk = new Chunk(this.options.name);
chunk.rendered = !cached; chunk.rendered = !cached;
chunk.id = this.options.name; chunk.id = this.options.name;
@ -112,7 +128,9 @@ export class ScriptsWebpackPlugin {
const sourceGetters = scripts.map(fullPath => { const sourceGetters = scripts.map(fullPath => {
return new Promise<Source>((resolve, reject) => { return new Promise<Source>((resolve, reject) => {
compilation.inputFileSystem.readFile(fullPath, (err: Error, data: Buffer) => { compilation.inputFileSystem.readFile(
fullPath,
(err: Error, data: Buffer) => {
if (err) { if (err) {
reject(err); reject(err);
return; return;
@ -134,7 +152,8 @@ export class ScriptsWebpackPlugin {
} }
resolve(source); resolve(source);
}); }
);
}); });
}); });
@ -150,7 +169,7 @@ export class ScriptsWebpackPlugin {
const filename = interpolateName( const filename = interpolateName(
{ resourcePath: 'scripts.js' } as loader.LoaderContext, { resourcePath: 'scripts.js' } as loader.LoaderContext,
this.options.filename as string, this.options.filename as string,
{ content: combinedSource.source() }, { content: combinedSource.source() }
); );
const output = { filename, source: combinedSource }; const output = { filename, source: combinedSource };

View File

@ -15,7 +15,9 @@ export interface SingleTestTransformLoaderOptions {
logger: logging.Logger; logger: logging.Logger;
} }
export const SingleTestTransformLoader = require.resolve(join(__dirname, 'single-test-transform')); export const SingleTestTransformLoader = require.resolve(
join(__dirname, 'single-test-transform')
);
/** /**
* This loader transforms the default test file to only run tests * This loader transforms the default test file to only run tests
@ -46,12 +48,13 @@ export default function loader(this: loader.LoaderContext, source: string) {
const message = [ const message = [
`The 'include' option requires that the 'main' file for tests include the line below:`, `The 'include' option requires that the 'main' file for tests include the line below:`,
`const context = require.context('./', true, /\.spec\.ts$/);`, `const context = require.context('./', true, /\.spec\.ts$/);`,
`Arguments passed to require.context are not strict and can be changed`, `Arguments passed to require.context are not strict and can be changed`
]; ];
options.logger.error(message.join(lineSeparator)); options.logger.error(message.join(lineSeparator));
} }
const mockedRequireContext = '{ keys: () => ({ map: (_a) => { } }) };' + lineSeparator; const mockedRequireContext =
'{ keys: () => ({ map: (_a) => { } }) };' + lineSeparator;
source = source.replace(regex, mockedRequireContext + targettedImports); source = source.replace(regex, mockedRequireContext + targettedImports);
return source; return source;

View File

@ -12,10 +12,12 @@
// To be used together with ExtractTextPlugin. // To be used together with ExtractTextPlugin.
export class SuppressExtractedTextChunksWebpackPlugin { export class SuppressExtractedTextChunksWebpackPlugin {
constructor() { } constructor() {}
apply(compiler: any): void { apply(compiler: any): void {
compiler.hooks.compilation.tap('SuppressExtractedTextChunks', (compilation: any) => { compiler.hooks.compilation.tap(
'SuppressExtractedTextChunks',
(compilation: any) => {
// find which chunks have css only entry points // find which chunks have css only entry points
const cssOnlyChunks: string[] = []; const cssOnlyChunks: string[] = [];
const entryPoints = compilation.options.entry; const entryPoints = compilation.options.entry;
@ -24,8 +26,11 @@ export class SuppressExtractedTextChunksWebpackPlugin {
let entryFiles: string[] | string = entryPoints[entryPoint]; let entryFiles: string[] | string = entryPoints[entryPoint];
// when type of entryFiles is not array, make it as an array // when type of entryFiles is not array, make it as an array
entryFiles = entryFiles instanceof Array ? entryFiles : [entryFiles]; entryFiles = entryFiles instanceof Array ? entryFiles : [entryFiles];
if (entryFiles.every((el: string) => if (
el.match(/\.(css|scss|sass|less|styl)$/) !== null)) { entryFiles.every(
(el: string) => el.match(/\.(css|scss|sass|less|styl)$/) !== null
)
) {
cssOnlyChunks.push(entryPoint); cssOnlyChunks.push(entryPoint);
} }
} }
@ -57,6 +62,7 @@ export class SuppressExtractedTextChunksWebpackPlugin {
// htmlPluginData.body = htmlPluginData.body.filter(filterFn); // htmlPluginData.body = htmlPluginData.body.filter(filterFn);
// callback(null, htmlPluginData); // callback(null, htmlPluginData);
// }); // });
}); }
);
} }
} }

View File

@ -7,15 +7,28 @@
*/ */
// Exports the webpack plugins we use internally. // Exports the webpack plugins we use internally.
export { CleanCssWebpackPlugin, CleanCssWebpackPluginOptions } from './cleancss-webpack-plugin'; export {
CleanCssWebpackPlugin,
CleanCssWebpackPluginOptions
} from './cleancss-webpack-plugin';
export { BundleBudgetPlugin, BundleBudgetPluginOptions } from './bundle-budget'; export { BundleBudgetPlugin, BundleBudgetPluginOptions } from './bundle-budget';
export { ScriptsWebpackPlugin, ScriptsWebpackPluginOptions } from './scripts-webpack-plugin'; export {
export { SuppressExtractedTextChunksWebpackPlugin } from './suppress-entry-chunks-webpack-plugin'; ScriptsWebpackPlugin,
export { RemoveHashPlugin, RemoveHashPluginOptions } from './remove-hash-plugin'; ScriptsWebpackPluginOptions
export { NamedLazyChunksPlugin as NamedChunksPlugin } from './named-chunks-plugin'; } from './scripts-webpack-plugin';
export {
SuppressExtractedTextChunksWebpackPlugin
} from './suppress-entry-chunks-webpack-plugin';
export {
RemoveHashPlugin,
RemoveHashPluginOptions
} from './remove-hash-plugin';
export {
NamedLazyChunksPlugin as NamedChunksPlugin
} from './named-chunks-plugin';
export { export {
default as PostcssCliResources, default as PostcssCliResources,
PostcssCliResourcesOptions, PostcssCliResourcesOptions
} from './postcss-cli-resources'; } from './postcss-cli-resources';
import { join } from 'path'; import { join } from 'path';

View File

@ -9,7 +9,7 @@ import { Budget } from '../../browser/schema';
export interface Compilation { export interface Compilation {
assets: { [name: string]: { size: () => number } }; assets: { [name: string]: { size: () => number } };
chunks: { name: string, files: string[], isOnlyInitial: () => boolean }[]; chunks: { name: string; files: string[]; isOnlyInitial: () => boolean }[];
warnings: string[]; warnings: string[];
errors: string[]; errors: string[];
} }
@ -19,7 +19,10 @@ export interface Size {
label?: string; label?: string;
} }
export function calculateSizes(budget: Budget, compilation: Compilation): Size[] { export function calculateSizes(
budget: Budget,
compilation: Compilation
): Size[] {
const calculatorMap = { const calculatorMap = {
all: AllCalculator, all: AllCalculator,
allScript: AllScriptCalculator, allScript: AllScriptCalculator,
@ -27,7 +30,7 @@ export function calculateSizes(budget: Budget, compilation: Compilation): Size[]
anyScript: AnyScriptCalculator, anyScript: AnyScriptCalculator,
anyComponentStyle: AnyComponentStyleCalculator, anyComponentStyle: AnyComponentStyleCalculator,
bundle: BundleCalculator, bundle: BundleCalculator,
initial: InitialCalculator, initial: InitialCalculator
}; };
const ctor = calculatorMap[budget.type]; const ctor = calculatorMap[budget.type];
@ -37,7 +40,7 @@ export function calculateSizes(budget: Budget, compilation: Compilation): Size[]
} }
export abstract class Calculator { export abstract class Calculator {
constructor (protected budget: Budget, protected compilation: Compilation) {} constructor(protected budget: Budget, protected compilation: Compilation) {}
abstract calculate(): Size[]; abstract calculate(): Size[];
} }
@ -54,7 +57,7 @@ class BundleCalculator extends Calculator {
.map((file: string) => this.compilation.assets[file].size()) .map((file: string) => this.compilation.assets[file].size())
.reduce((total: number, size: number) => total + size, 0); .reduce((total: number, size: number) => total + size, 0);
return [{size, label: this.budget.name}]; return [{ size, label: this.budget.name }];
} }
} }
@ -63,14 +66,16 @@ class BundleCalculator extends Calculator {
*/ */
class InitialCalculator extends Calculator { class InitialCalculator extends Calculator {
calculate() { calculate() {
const initialChunks = this.compilation.chunks.filter(chunk => chunk.isOnlyInitial()); const initialChunks = this.compilation.chunks.filter(chunk =>
chunk.isOnlyInitial()
);
const size: number = initialChunks const size: number = initialChunks
.reduce((files, chunk) => [...files, ...chunk.files], []) .reduce((files, chunk) => [...files, ...chunk.files], [])
.filter((file: string) => !file.endsWith('.map')) .filter((file: string) => !file.endsWith('.map'))
.map((file: string) => this.compilation.assets[file].size()) .map((file: string) => this.compilation.assets[file].size())
.reduce((total: number, size: number) => total + size, 0); .reduce((total: number, size: number) => total + size, 0);
return [{size, label: 'initial'}]; return [{ size, label: 'initial' }];
} }
} }
@ -85,7 +90,7 @@ class AllScriptCalculator extends Calculator {
.map(asset => asset.size()) .map(asset => asset.size())
.reduce((total: number, size: number) => total + size, 0); .reduce((total: number, size: number) => total + size, 0);
return [{size, label: 'total scripts'}]; return [{ size, label: 'total scripts' }];
} }
} }
@ -99,7 +104,7 @@ class AllCalculator extends Calculator {
.map(key => this.compilation.assets[key].size()) .map(key => this.compilation.assets[key].size())
.reduce((total: number, size: number) => total + size, 0); .reduce((total: number, size: number) => total + size, 0);
return [{size, label: 'total'}]; return [{ size, label: 'total' }];
} }
} }
@ -112,7 +117,7 @@ class AnyComponentStyleCalculator extends Calculator {
.filter(key => key.endsWith('.css')) .filter(key => key.endsWith('.css'))
.map(key => ({ .map(key => ({
size: this.compilation.assets[key].size(), size: this.compilation.assets[key].size(),
label: key, label: key
})); }));
} }
} }
@ -129,7 +134,7 @@ class AnyScriptCalculator extends Calculator {
return { return {
size: asset.size(), size: asset.size(),
label: key, label: key
}; };
}); });
} }
@ -147,7 +152,7 @@ class AnyCalculator extends Calculator {
return { return {
size: asset.size(), size: asset.size(),
label: key, label: key
}; };
}); });
} }
@ -159,19 +164,21 @@ class AnyCalculator extends Calculator {
export function calculateBytes( export function calculateBytes(
input: string, input: string,
baseline?: string, baseline?: string,
factor: 1 | -1 = 1, factor: 1 | -1 = 1
): number { ): number {
const matches = input.match(/^\s*(\d+(?:\.\d+)?)\s*(%|(?:[mM]|[kK]|[gG])?[bB])?\s*$/); const matches = input.match(
/^\s*(\d+(?:\.\d+)?)\s*(%|(?:[mM]|[kK]|[gG])?[bB])?\s*$/
);
if (!matches) { if (!matches) {
return NaN; return NaN;
} }
const baselineBytes = baseline && calculateBytes(baseline) || 0; const baselineBytes = (baseline && calculateBytes(baseline)) || 0;
let value = Number(matches[1]); let value = Number(matches[1]);
switch (matches[2] && matches[2].toLowerCase()) { switch (matches[2] && matches[2].toLowerCase()) {
case '%': case '%':
value = baselineBytes * value / 100; value = (baselineBytes * value) / 100;
break; break;
case 'kb': case 'kb':
value *= 1024; value *= 1024;

View File

@ -67,16 +67,16 @@ describe('bundle-calculator', () => {
expect(calculateBytes('25.0gB')).toBe(25 * 1024 * 1024 * 1024); expect(calculateBytes('25.0gB')).toBe(25 * 1024 * 1024 * 1024);
}); });
it ('converts a decimal with mb and baseline', () => { it('converts a decimal with mb and baseline', () => {
expect(calculateBytes('3mb', '5mb', -1)).toBe(2 * 1024 * 1024); expect(calculateBytes('3mb', '5mb', -1)).toBe(2 * 1024 * 1024);
}); });
it ('converts a percentage with baseline', () => { it('converts a percentage with baseline', () => {
expect(calculateBytes('20%', '1mb')).toBe(1024 * 1024 * 1.2); expect(calculateBytes('20%', '1mb')).toBe(1024 * 1024 * 1.2);
expect(calculateBytes('20%', '1mb', -1)).toBe(1024 * 1024 * 0.8); expect(calculateBytes('20%', '1mb', -1)).toBe(1024 * 1024 * 0.8);
}); });
it ('supports whitespace', () => { it('supports whitespace', () => {
expect(calculateBytes(' 5kb ')).toBe(5 * 1024); expect(calculateBytes(' 5kb ')).toBe(5 * 1024);
expect(calculateBytes('0.25 MB')).toBe(0.25 * 1024 * 1024); expect(calculateBytes('0.25 MB')).toBe(0.25 * 1024 * 1024);
expect(calculateBytes(' 20 % ', ' 1 mb ')).toBe(1024 * 1024 * 1.2); expect(calculateBytes(' 20 % ', ' 1 mb ')).toBe(1024 * 1024 * 1.2);

View File

@ -7,7 +7,11 @@
*/ */
import * as net from 'net'; import * as net from 'net';
export function checkPort(port: number, host: string, basePort = 49152): Promise<number> { export function checkPort(
port: number,
host: string,
basePort = 49152
): Promise<number> {
return new Promise<number>((resolve, reject) => { return new Promise<number>((resolve, reject) => {
function _getPort(portNumber: number) { function _getPort(portNumber: number) {
if (portNumber > 65535) { if (portNumber > 65535) {
@ -16,7 +20,8 @@ export function checkPort(port: number, host: string, basePort = 49152): Promise
const server = net.createServer(); const server = net.createServer();
server.once('error', (err: Error & {code: string}) => { server
.once('error', (err: Error & { code: string }) => {
if (err.code !== 'EADDRINUSE') { if (err.code !== 'EADDRINUSE') {
reject(err); reject(err);
} else if (port === 0) { } else if (port === 0) {
@ -24,7 +29,9 @@ export function checkPort(port: number, host: string, basePort = 49152): Promise
} else { } else {
// If the port isn't available and we weren't looking for any port, throw error. // If the port isn't available and we weren't looking for any port, throw error.
reject( reject(
new Error(`Port ${port} is already in use. Use '--port' to specify a different port.`), new Error(
`Port ${port} is already in use. Use '--port' to specify a different port.`
)
); );
} }
}) })

View File

@ -11,7 +11,11 @@ import { basename, dirname, extname, join } from 'path';
import { isDirectory } from './is-directory'; import { isDirectory } from './is-directory';
// go through all patterns and find unique list of files // go through all patterns and find unique list of files
export function findTests(patterns: string[], cwd: string, workspaceRoot: string): string[] { export function findTests(
patterns: string[],
cwd: string,
workspaceRoot: string
): string[] {
return patterns.reduce( return patterns.reduce(
(files, pattern) => { (files, pattern) => {
const relativePathToMain = cwd.replace(workspaceRoot, '').substr(1); // remove leading slash const relativePathToMain = cwd.replace(workspaceRoot, '').substr(1); // remove leading slash
@ -24,11 +28,15 @@ export function findTests(patterns: string[], cwd: string, workspaceRoot: string
return files; return files;
}, },
[] as string[], [] as string[]
); );
} }
function findMatchingTests(pattern: string, cwd: string, relativePathToMain: string): string[] { function findMatchingTests(
pattern: string,
cwd: string,
relativePathToMain: string
): string[] {
// normalize pattern, glob lib only accepts forward slashes // normalize pattern, glob lib only accepts forward slashes
pattern = pattern.replace(/\\/g, '/'); pattern = pattern.replace(/\\/g, '/');
relativePathToMain = relativePathToMain.replace(/\\/g, '/'); relativePathToMain = relativePathToMain.replace(/\\/g, '/');
@ -55,7 +63,7 @@ function findMatchingTests(pattern: string, cwd: string, relativePathToMain: str
} }
const files = glob.sync(pattern, { const files = glob.sync(pattern, {
cwd, cwd
}); });
return files; return files;

View File

@ -9,7 +9,11 @@ import { existsSync } from 'fs';
import * as path from 'path'; import * as path from 'path';
import { isDirectory } from './is-directory'; import { isDirectory } from './is-directory';
export function findUp(names: string | string[], from: string, stopOnNodeModules = false) { export function findUp(
names: string | string[],
from: string,
stopOnNodeModules = false
) {
if (!Array.isArray(names)) { if (!Array.isArray(names)) {
names = [names]; names = [names];
} }

View File

@ -56,8 +56,16 @@ export interface FileInfo {
* after processing several configurations in order to build different sets of * after processing several configurations in order to build different sets of
* bundles for differential serving. * bundles for differential serving.
*/ */
export async function augmentIndexHtml(params: AugmentIndexHtmlOptions): Promise<string> { export async function augmentIndexHtml(
const { loadOutputFile, files, noModuleFiles = [], moduleFiles = [], entrypoints } = params; params: AugmentIndexHtmlOptions
): Promise<string> {
const {
loadOutputFile,
files,
noModuleFiles = [],
moduleFiles = [],
entrypoints
} = params;
let { crossOrigin = 'none' } = params; let { crossOrigin = 'none' } = params;
if (params.sri && crossOrigin === 'none') { if (params.sri && crossOrigin === 'none') {
@ -88,7 +96,10 @@ export async function augmentIndexHtml(params: AugmentIndexHtmlOptions): Promise
// Find the head and body elements // Find the head and body elements
const treeAdapter = parse5.treeAdapters.default; const treeAdapter = parse5.treeAdapters.default;
const document = parse5.parse(params.inputContent, { treeAdapter, locationInfo: true }); const document = parse5.parse(params.inputContent, {
treeAdapter,
locationInfo: true
});
let headElement; let headElement;
let bodyElement; let bodyElement;
for (const docChild of document.childNodes) { for (const docChild of document.childNodes) {
@ -127,12 +138,15 @@ export async function augmentIndexHtml(params: AugmentIndexHtmlOptions): Promise
} }
// Inject into the html // Inject into the html
const indexSource = new ReplaceSource(new RawSource(params.inputContent), params.input); const indexSource = new ReplaceSource(
new RawSource(params.inputContent),
params.input
);
let scriptElements = ''; let scriptElements = '';
for (const script of scripts) { for (const script of scripts) {
const attrs: { name: string; value: string | null }[] = [ const attrs: { name: string; value: string | null }[] = [
{ name: 'src', value: (params.deployUrl || '') + script }, { name: 'src', value: (params.deployUrl || '') + script }
]; ];
if (crossOrigin !== 'none') { if (crossOrigin !== 'none') {
@ -169,7 +183,9 @@ export async function augmentIndexHtml(params: AugmentIndexHtmlOptions): Promise
} }
const attributes = attrs const attributes = attrs
.map(attr => (attr.value === null ? attr.name : `${attr.name}="${attr.value}"`)) .map(attr =>
attr.value === null ? attr.name : `${attr.name}="${attr.value}"`
)
.join(' '); .join(' ');
scriptElements += `<script ${attributes}></script>`; scriptElements += `<script ${attributes}></script>`;
} }
@ -189,13 +205,13 @@ export async function augmentIndexHtml(params: AugmentIndexHtmlOptions): Promise
if (!baseElement) { if (!baseElement) {
baseElement = treeAdapter.createElement('base', undefined, [ baseElement = treeAdapter.createElement('base', undefined, [
{ name: 'href', value: params.baseHref }, { name: 'href', value: params.baseHref }
]); ]);
treeAdapter.appendChild(baseFragment, baseElement); treeAdapter.appendChild(baseFragment, baseElement);
indexSource.insert( indexSource.insert(
headElement.__location.startTag.endOffset, headElement.__location.startTag.endOffset,
parse5.serialize(baseFragment, { treeAdapter }), parse5.serialize(baseFragment, { treeAdapter })
); );
} else { } else {
let hrefAttribute; let hrefAttribute;
@ -214,7 +230,7 @@ export async function augmentIndexHtml(params: AugmentIndexHtmlOptions): Promise
indexSource.replace( indexSource.replace(
baseElement.__location.startOffset, baseElement.__location.startOffset,
baseElement.__location.endOffset, baseElement.__location.endOffset,
parse5.serialize(baseFragment, { treeAdapter }), parse5.serialize(baseFragment, { treeAdapter })
); );
} }
} }
@ -223,7 +239,7 @@ export async function augmentIndexHtml(params: AugmentIndexHtmlOptions): Promise
for (const stylesheet of stylesheets) { for (const stylesheet of stylesheets) {
const attrs = [ const attrs = [
{ name: 'rel', value: 'stylesheet' }, { name: 'rel', value: 'stylesheet' },
{ name: 'href', value: (params.deployUrl || '') + stylesheet }, { name: 'href', value: (params.deployUrl || '') + stylesheet }
]; ];
if (crossOrigin !== 'none') { if (crossOrigin !== 'none') {
@ -239,7 +255,10 @@ export async function augmentIndexHtml(params: AugmentIndexHtmlOptions): Promise
treeAdapter.appendChild(styleElements, element); treeAdapter.appendChild(styleElements, element);
} }
indexSource.insert(styleInsertionPoint, parse5.serialize(styleElements, { treeAdapter })); indexSource.insert(
styleInsertionPoint,
parse5.serialize(styleElements, { treeAdapter })
);
return indexSource.source(); return indexSource.source();
} }

View File

@ -6,7 +6,11 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import { tags } from '@angular-devkit/core'; import { tags } from '@angular-devkit/core';
import { AugmentIndexHtmlOptions, FileInfo, augmentIndexHtml } from './augment-index-html'; import {
AugmentIndexHtmlOptions,
FileInfo,
augmentIndexHtml
} from './augment-index-html';
describe('augment-index-html', () => { describe('augment-index-html', () => {
const indexGeneratorOptions: AugmentIndexHtmlOptions = { const indexGeneratorOptions: AugmentIndexHtmlOptions = {
@ -16,7 +20,7 @@ describe('augment-index-html', () => {
sri: false, sri: false,
files: [], files: [],
loadOutputFile: async (_fileName: string) => '', loadOutputFile: async (_fileName: string) => '',
entrypoints: ['scripts', 'polyfills', 'main', 'styles'], entrypoints: ['scripts', 'polyfills', 'main', 'styles']
}; };
const oneLineHtml = (html: TemplateStringsArray) => const oneLineHtml = (html: TemplateStringsArray) =>
@ -30,8 +34,8 @@ describe('augment-index-html', () => {
{ file: 'runtime.js', extension: '.js', name: 'main' }, { file: 'runtime.js', extension: '.js', name: 'main' },
{ file: 'main.js', extension: '.js', name: 'main' }, { file: 'main.js', extension: '.js', name: 'main' },
{ file: 'runtime.js', extension: '.js', name: 'polyfills' }, { file: 'runtime.js', extension: '.js', name: 'polyfills' },
{ file: 'polyfills.js', extension: '.js', name: 'polyfills' }, { file: 'polyfills.js', extension: '.js', name: 'polyfills' }
], ]
}); });
const html = await source; const html = await source;
@ -54,24 +58,24 @@ describe('augment-index-html', () => {
{ file: 'runtime-es2015.js', extension: '.js', name: 'main' }, { file: 'runtime-es2015.js', extension: '.js', name: 'main' },
{ file: 'main-es2015.js', extension: '.js', name: 'main' }, { file: 'main-es2015.js', extension: '.js', name: 'main' },
{ file: 'runtime-es2015.js', extension: '.js', name: 'polyfills' }, { file: 'runtime-es2015.js', extension: '.js', name: 'polyfills' },
{ file: 'polyfills-es2015.js', extension: '.js', name: 'polyfills' }, { file: 'polyfills-es2015.js', extension: '.js', name: 'polyfills' }
]; ];
const es5JsFiles: FileInfo[] = [ const es5JsFiles: FileInfo[] = [
{ file: 'runtime-es5.js', extension: '.js', name: 'main' }, { file: 'runtime-es5.js', extension: '.js', name: 'main' },
{ file: 'main-es5.js', extension: '.js', name: 'main' }, { file: 'main-es5.js', extension: '.js', name: 'main' },
{ file: 'runtime-es5.js', extension: '.js', name: 'polyfills' }, { file: 'runtime-es5.js', extension: '.js', name: 'polyfills' },
{ file: 'polyfills-es5.js', extension: '.js', name: 'polyfills' }, { file: 'polyfills-es5.js', extension: '.js', name: 'polyfills' }
]; ];
const source = augmentIndexHtml({ const source = augmentIndexHtml({
...indexGeneratorOptions, ...indexGeneratorOptions,
files: [ files: [
{ file: 'styles.css', extension: '.css', name: 'styles' }, { file: 'styles.css', extension: '.css', name: 'styles' },
{ file: 'styles.css', extension: '.css', name: 'styles' }, { file: 'styles.css', extension: '.css', name: 'styles' }
], ],
moduleFiles: es2015JsFiles, moduleFiles: es2015JsFiles,
noModuleFiles: es5JsFiles, noModuleFiles: es5JsFiles
}); });
const html = await source; const html = await source;
@ -93,27 +97,25 @@ describe('augment-index-html', () => {
`); `);
}); });
it( it(`should not add 'module' and 'non-module' attr to js files which are in both module formats`, async () => {
`should not add 'module' and 'non-module' attr to js files which are in both module formats`,
async () => {
const es2015JsFiles: FileInfo[] = [ const es2015JsFiles: FileInfo[] = [
{ file: 'scripts.js', extension: '.js', name: 'scripts' }, { file: 'scripts.js', extension: '.js', name: 'scripts' },
{ file: 'main-es2015.js', extension: '.js', name: 'main' }, { file: 'main-es2015.js', extension: '.js', name: 'main' }
]; ];
const es5JsFiles: FileInfo[] = [ const es5JsFiles: FileInfo[] = [
{ file: 'scripts.js', extension: '.js', name: 'scripts' }, { file: 'scripts.js', extension: '.js', name: 'scripts' },
{ file: 'main-es5.js', extension: '.js', name: 'main' }, { file: 'main-es5.js', extension: '.js', name: 'main' }
]; ];
const source = augmentIndexHtml({ const source = augmentIndexHtml({
...indexGeneratorOptions, ...indexGeneratorOptions,
files: [ files: [
{ file: 'styles.css', extension: '.css', name: 'styles' }, { file: 'styles.css', extension: '.css', name: 'styles' },
{ file: 'styles.css', extension: '.css', name: 'styles' }, { file: 'styles.css', extension: '.css', name: 'styles' }
], ],
moduleFiles: es2015JsFiles, moduleFiles: es2015JsFiles,
noModuleFiles: es5JsFiles, noModuleFiles: es5JsFiles
}); });
const html = await source; const html = await source;
@ -131,5 +133,4 @@ describe('augment-index-html', () => {
</html> </html>
`); `);
}); });
}); });

View File

@ -7,13 +7,23 @@
*/ */
import { EmittedFiles } from '@angular-devkit/build-webpack'; import { EmittedFiles } from '@angular-devkit/build-webpack';
import { Path, dirname, getSystemPath, join, virtualFs } from '@angular-devkit/core'; import {
Path,
dirname,
getSystemPath,
join,
virtualFs
} from '@angular-devkit/core';
import { Observable, of } from 'rxjs'; import { Observable, of } from 'rxjs';
import { map, switchMap } from 'rxjs/operators'; import { map, switchMap } from 'rxjs/operators';
import { ExtraEntryPoint } from '../../../browser/schema'; import { ExtraEntryPoint } from '../../../browser/schema';
import { generateEntryPoints } from '../package-chunk-sort'; import { generateEntryPoints } from '../package-chunk-sort';
import { stripBom } from '../strip-bom'; import { stripBom } from '../strip-bom';
import { CrossOriginValue, FileInfo, augmentIndexHtml } from './augment-index-html'; import {
CrossOriginValue,
FileInfo,
augmentIndexHtml
} from './augment-index-html';
type ExtensionFilter = '.js' | '.css'; type ExtensionFilter = '.js' | '.css';
@ -48,7 +58,7 @@ export function writeIndexHtml({
scripts = [], scripts = [],
styles = [], styles = [],
postTransform, postTransform,
crossOrigin, crossOrigin
}: WriteIndexHtmlOptions): Observable<void> { }: WriteIndexHtmlOptions): Observable<void> {
return host.read(indexPath).pipe( return host.read(indexPath).pipe(
map(content => stripBom(virtualFs.fileBufferToString(content))), map(content => stripBom(virtualFs.fileBufferToString(content))),
@ -69,18 +79,20 @@ export function writeIndexHtml({
.read(join(dirname(outputPath), filePath)) .read(join(dirname(outputPath), filePath))
.pipe(map(data => virtualFs.fileBufferToString(data))) .pipe(map(data => virtualFs.fileBufferToString(data)))
.toPromise(); .toPromise();
}, }
}), })
),
switchMap(content =>
postTransform ? postTransform(content) : of(content)
), ),
switchMap(content => (postTransform ? postTransform(content) : of(content))),
map(content => virtualFs.stringToFileBuffer(content)), map(content => virtualFs.stringToFileBuffer(content)),
switchMap(content => host.write(outputPath, content)), switchMap(content => host.write(outputPath, content))
); );
} }
function filterAndMapBuildFiles( function filterAndMapBuildFiles(
files: EmittedFiles[], files: EmittedFiles[],
extensionFilter: ExtensionFilter | ExtensionFilter[], extensionFilter: ExtensionFilter | ExtensionFilter[]
): FileInfo[] { ): FileInfo[] {
const filteredFiles: FileInfo[] = []; const filteredFiles: FileInfo[] = [];
const validExtensions: string[] = Array.isArray(extensionFilter) const validExtensions: string[] = Array.isArray(extensionFilter)

View File

@ -15,9 +15,12 @@ export function generateEntryPoints(appConfig: {
// Add all styles/scripts, except lazy-loaded ones. // Add all styles/scripts, except lazy-loaded ones.
const extraEntryPoints = ( const extraEntryPoints = (
extraEntryPoints: ExtraEntryPoint[], extraEntryPoints: ExtraEntryPoint[],
defaultBundleName: string, defaultBundleName: string
): string[] => { ): string[] => {
const entryPoints = normalizeExtraEntryPoints(extraEntryPoints, defaultBundleName) const entryPoints = normalizeExtraEntryPoints(
extraEntryPoints,
defaultBundleName
)
.filter(entry => entry.inject) .filter(entry => entry.inject)
.map(entry => entry.bundleName); .map(entry => entry.bundleName);
@ -34,15 +37,21 @@ export function generateEntryPoints(appConfig: {
...extraEntryPoints(appConfig.styles, 'styles'), ...extraEntryPoints(appConfig.styles, 'styles'),
...extraEntryPoints(appConfig.scripts, 'scripts'), ...extraEntryPoints(appConfig.scripts, 'scripts'),
'vendor', 'vendor',
'main', 'main'
]; ];
const duplicates = [ const duplicates = [
...new Set(entryPoints.filter(x => entryPoints.indexOf(x) !== entryPoints.lastIndexOf(x))), ...new Set(
entryPoints.filter(
x => entryPoints.indexOf(x) !== entryPoints.lastIndexOf(x)
)
)
]; ];
if (duplicates.length > 0) { if (duplicates.length > 0) {
throw new Error(`Multiple bundles have been named the same: '${duplicates.join(`', '`)}'.`); throw new Error(
`Multiple bundles have been named the same: '${duplicates.join(`', '`)}'.`
);
} }
return entryPoints; return entryPoints;

View File

@ -16,7 +16,10 @@ import * as path from 'path';
* @param workspaceRoot - workspaceRoot root location when provided * @param workspaceRoot - workspaceRoot root location when provided
* it will resolve 'tsconfigPath' from this path. * it will resolve 'tsconfigPath' from this path.
*/ */
export function readTsconfig(tsconfigPath: string, workspaceRoot?: string): ParsedConfiguration { export function readTsconfig(
tsconfigPath: string,
workspaceRoot?: string
): ParsedConfiguration {
const tsConfigFullPath = workspaceRoot const tsConfigFullPath = workspaceRoot
? path.resolve(workspaceRoot, tsconfigPath) ? path.resolve(workspaceRoot, tsconfigPath)
: tsconfigPath; : tsconfigPath;

View File

@ -13,16 +13,13 @@ import {
normalize, normalize,
relative, relative,
tags, tags,
virtualFs, virtualFs
} from '@angular-devkit/core'; } from '@angular-devkit/core';
import { import { Filesystem, Generator } from '@angular/service-worker/config'; // tslint:disable-line:no-implicit-dependencies
Filesystem,
Generator,
} from '@angular/service-worker/config'; // tslint:disable-line:no-implicit-dependencies
import * as crypto from 'crypto'; import * as crypto from 'crypto';
class CliFilesystem implements Filesystem { class CliFilesystem implements Filesystem {
constructor(private _host: virtualFs.Host, private base: string) { } constructor(private _host: virtualFs.Host, private base: string) {}
list(path: string): Promise<string[]> { list(path: string): Promise<string[]> {
return this._recursiveList(this._resolve(path), []).catch(() => []); return this._recursiveList(this._resolve(path), []).catch(() => []);
@ -40,7 +37,8 @@ class CliFilesystem implements Filesystem {
} }
write(path: string, content: string): Promise<void> { write(path: string, content: string): Promise<void> {
return this._host.write(this._resolve(path), virtualFs.stringToFileBuffer(content)) return this._host
.write(this._resolve(path), virtualFs.stringToFileBuffer(content))
.toPromise(); .toPromise();
} }
@ -75,19 +73,20 @@ export async function augmentAppWithServiceWorker(
appRoot: Path, appRoot: Path,
outputPath: Path, outputPath: Path,
baseHref: string, baseHref: string,
ngswConfigPath?: string, ngswConfigPath?: string
): Promise<void> { ): Promise<void> {
const distPath = normalize(outputPath); const distPath = normalize(outputPath);
const systemProjectRoot = getSystemPath(projectRoot); const systemProjectRoot = getSystemPath(projectRoot);
// Find the service worker package // Find the service worker package
const workerPath = normalize( const workerPath = normalize(
require.resolve('@angular/service-worker/ngsw-worker.js', { paths: [systemProjectRoot] }), require.resolve('@angular/service-worker/ngsw-worker.js', {
); paths: [systemProjectRoot]
const swConfigPath = require.resolve( })
'@angular/service-worker/config',
{ paths: [systemProjectRoot] },
); );
const swConfigPath = require.resolve('@angular/service-worker/config', {
paths: [systemProjectRoot]
});
// Determine the configuration file path // Determine the configuration file path
let configPath; let configPath;
@ -102,22 +101,32 @@ export async function augmentAppWithServiceWorker(
if (!configExists) { if (!configExists) {
throw new Error(tags.oneLine` throw new Error(tags.oneLine`
Error: Expected to find an ngsw-config.json configuration Error: Expected to find an ngsw-config.json configuration
file in the ${getSystemPath(appRoot)} folder. Either provide one or disable Service Worker file in the ${getSystemPath(
appRoot
)} folder. Either provide one or disable Service Worker
in your angular.json configuration file. in your angular.json configuration file.
`); `);
} }
// Read the configuration file // Read the configuration file
const config = JSON.parse(virtualFs.fileBufferToString(await host.read(configPath).toPromise())); const config = JSON.parse(
virtualFs.fileBufferToString(await host.read(configPath).toPromise())
);
// Generate the manifest // Generate the manifest
const GeneratorConstructor = require(swConfigPath).Generator as typeof Generator; const GeneratorConstructor = require(swConfigPath)
const generator = new GeneratorConstructor(new CliFilesystem(host, outputPath), baseHref); .Generator as typeof Generator;
const generator = new GeneratorConstructor(
new CliFilesystem(host, outputPath),
baseHref
);
const output = await generator.process(config); const output = await generator.process(config);
// Write the manifest // Write the manifest
const manifest = JSON.stringify(output, null, 2); const manifest = JSON.stringify(output, null, 2);
await host.write(join(distPath, 'ngsw.json'), virtualFs.stringToFileBuffer(manifest)).toPromise(); await host
.write(join(distPath, 'ngsw.json'), virtualFs.stringToFileBuffer(manifest))
.toPromise();
// Write the worker code // Write the worker code
// NOTE: This is inefficient (kernel -> userspace -> kernel). // NOTE: This is inefficient (kernel -> userspace -> kernel).
@ -130,7 +139,11 @@ export async function augmentAppWithServiceWorker(
if (await host.exists(safetyPath).toPromise()) { if (await host.exists(safetyPath).toPromise()) {
const safetyCode = await host.read(safetyPath).toPromise(); const safetyCode = await host.read(safetyPath).toPromise();
await host.write(join(distPath, 'worker-basic.min.js'), safetyCode).toPromise(); await host
await host.write(join(distPath, 'safety-worker.js'), safetyCode).toPromise(); .write(join(distPath, 'worker-basic.min.js'), safetyCode)
.toPromise();
await host
.write(join(distPath, 'safety-worker.js'), safetyCode)
.toPromise();
} }
} }

View File

@ -10,7 +10,6 @@
import { tags, terminal } from '@angular-devkit/core'; import { tags, terminal } from '@angular-devkit/core';
import * as path from 'path'; import * as path from 'path';
const { bold, green, red, reset, white, yellow } = terminal; const { bold, green, red, reset, white, yellow } = terminal;
export function formatSize(size: number): string { export function formatSize(size: number): string {
@ -21,7 +20,9 @@ export function formatSize(size: number): string {
const abbreviations = ['bytes', 'kB', 'MB', 'GB']; const abbreviations = ['bytes', 'kB', 'MB', 'GB'];
const index = Math.floor(Math.log(size) / Math.log(1024)); const index = Math.floor(Math.log(size) / Math.log(1024));
return `${+(size / Math.pow(1024, index)).toPrecision(3)} ${abbreviations[index]}`; return `${+(size / Math.pow(1024, index)).toPrecision(3)} ${
abbreviations[index]
}`;
} }
export function generateBundleStats( export function generateBundleStats(
@ -34,7 +35,7 @@ export function generateBundleStats(
initial: boolean; initial: boolean;
rendered?: boolean; rendered?: boolean;
}, },
colors: boolean, colors: boolean
): string { ): string {
const g = (x: string) => (colors ? bold(green(x)) : x); const g = (x: string) => (colors ? bold(green(x)) : x);
const y = (x: string) => (colors ? bold(yellow(x)) : x); const y = (x: string) => (colors ? bold(yellow(x)) : x);
@ -47,55 +48,81 @@ export function generateBundleStats(
.map(f => (f && (info as any)[f] ? g(` [${f}]`) : '')) .map(f => (f && (info as any)[f] ? g(` [${f}]`) : ''))
.join(''); .join('');
return `chunk {${y(info.id.toString())}} ${g(files)}${names}${size} ${initial}${flags}`; return `chunk {${y(info.id.toString())}} ${g(
files
)}${names}${size} ${initial}${flags}`;
} }
export function generateBuildStats(hash: string, time: number, colors: boolean): string { export function generateBuildStats(
const w = (x: string) => colors ? bold(white(x)) : x; hash: string,
return `Date: ${w(new Date().toISOString())} - Hash: ${w(hash)} - Time: ${w('' + time)}ms` time: number,
colors: boolean
): string {
const w = (x: string) => (colors ? bold(white(x)) : x);
return `Date: ${w(new Date().toISOString())} - Hash: ${w(hash)} - Time: ${w(
'' + time
)}ms`;
} }
export function statsToString(json: any, statsConfig: any) { export function statsToString(json: any, statsConfig: any) {
const colors = statsConfig.colors; const colors = statsConfig.colors;
const rs = (x: string) => colors ? reset(x) : x; const rs = (x: string) => (colors ? reset(x) : x);
const w = (x: string) => colors ? bold(white(x)) : x; const w = (x: string) => (colors ? bold(white(x)) : x);
const changedChunksStats = json.chunks const changedChunksStats = json.chunks
.filter((chunk: any) => chunk.rendered) .filter((chunk: any) => chunk.rendered)
.map((chunk: any) => { .map((chunk: any) => {
const asset = json.assets.filter((x: any) => x.name == chunk.files[0])[0]; const asset = json.assets.filter((x: any) => x.name == chunk.files[0])[0];
return generateBundleStats({ ...chunk, size: asset && asset.size }, colors); return generateBundleStats(
{ ...chunk, size: asset && asset.size },
colors
);
}); });
const unchangedChunkNumber = json.chunks.length - changedChunksStats.length; const unchangedChunkNumber = json.chunks.length - changedChunksStats.length;
if (unchangedChunkNumber > 0) { if (unchangedChunkNumber > 0) {
return '\n' + rs(tags.stripIndents` return (
'\n' +
rs(tags.stripIndents`
Date: ${w(new Date().toISOString())} - Hash: ${w(json.hash)} Date: ${w(new Date().toISOString())} - Hash: ${w(json.hash)}
${unchangedChunkNumber} unchanged chunks ${unchangedChunkNumber} unchanged chunks
${changedChunksStats.join('\n')} ${changedChunksStats.join('\n')}
Time: ${w('' + json.time)}ms Time: ${w('' + json.time)}ms
`); `)
);
} else { } else {
return '\n' + rs(tags.stripIndents` return (
'\n' +
rs(tags.stripIndents`
${changedChunksStats.join('\n')} ${changedChunksStats.join('\n')}
Date: ${w(new Date().toISOString())} - Hash: ${w(json.hash)} - Time: ${w('' + json.time)}ms Date: ${w(new Date().toISOString())} - Hash: ${w(json.hash)} - Time: ${w(
`); '' + json.time
)}ms
`)
);
} }
} }
export function statsWarningsToString(json: any, statsConfig: any) { export function statsWarningsToString(json: any, statsConfig: any) {
const colors = statsConfig.colors; const colors = statsConfig.colors;
const rs = (x: string) => colors ? reset(x) : x; const rs = (x: string) => (colors ? reset(x) : x);
const y = (x: string) => colors ? bold(yellow(x)) : x; const y = (x: string) => (colors ? bold(yellow(x)) : x);
return rs('\n' + json.warnings.map((warning: any) => y(`WARNING in ${warning}`)).join('\n\n')); return rs(
'\n' +
json.warnings
.map((warning: any) => y(`WARNING in ${warning}`))
.join('\n\n')
);
} }
export function statsErrorsToString(json: any, statsConfig: any) { export function statsErrorsToString(json: any, statsConfig: any) {
const colors = statsConfig.colors; const colors = statsConfig.colors;
const rs = (x: string) => colors ? reset(x) : x; const rs = (x: string) => (colors ? reset(x) : x);
const r = (x: string) => colors ? bold(red(x)) : x; const r = (x: string) => (colors ? bold(red(x)) : x);
return rs('\n' + json.errors.map((error: any) => r(`ERROR in ${error}`)).join('\n')); return rs(
'\n' + json.errors.map((error: any) => r(`ERROR in ${error}`)).join('\n')
);
} }

View File

@ -9,7 +9,11 @@ import { createHash } from 'crypto';
import * as findCacheDirectory from 'find-cache-dir'; import * as findCacheDirectory from 'find-cache-dir';
import * as fs from 'fs'; import * as fs from 'fs';
import { manglingDisabled } from '../utils/mangle-options'; import { manglingDisabled } from '../utils/mangle-options';
import { CacheKey, ProcessBundleOptions, ProcessBundleResult } from '../utils/process-bundle'; import {
CacheKey,
ProcessBundleOptions,
ProcessBundleResult
} from '../utils/process-bundle';
const cacache = require('cacache'); const cacache = require('cacache');
const cacheDownlevelPath = findCacheDirectory({ name: 'angular-build-dl' }); const cacheDownlevelPath = findCacheDirectory({ name: 'angular-build-dl' });
@ -20,7 +24,11 @@ const packageVersion = require('../../package.json').version;
let copyFileWorkaround = false; let copyFileWorkaround = false;
if (process.platform === 'darwin') { if (process.platform === 'darwin') {
const version = process.versions.node.split('.').map(part => Number(part)); const version = process.versions.node.split('.').map(part => Number(part));
if (version[0] < 10 || version[0] === 11 || (version[0] === 10 && version[1] < 16)) { if (
version[0] < 10 ||
version[0] === 11 ||
(version[0] === 10 && version[1] < 16)
) {
copyFileWorkaround = true; copyFileWorkaround = true;
} }
} }
@ -44,7 +52,7 @@ export class BundleActionCache {
fs.copyFileSync( fs.copyFileSync(
typeof entry === 'string' ? entry : entry.path, typeof entry === 'string' ? entry : entry.path,
dest, dest,
fs.constants.COPYFILE_EXCL, fs.constants.COPYFILE_EXCL
); );
if (process.platform !== 'win32') { if (process.platform !== 'win32') {
// The cache writes entries as readonly and when using copyFile the permissions will also be copied. // The cache writes entries as readonly and when using copyFile the permissions will also be copied.
@ -74,7 +82,8 @@ export class BundleActionCache {
// Postfix added to sourcemap cache keys when vendor sourcemaps are present // Postfix added to sourcemap cache keys when vendor sourcemaps are present
// Allows non-destructive caching of both variants // Allows non-destructive caching of both variants
const SourceMapVendorPostfix = !!action.sourceMaps && action.vendorSourceMaps ? '|vendor' : ''; const SourceMapVendorPostfix =
!!action.sourceMaps && action.vendorSourceMaps ? '|vendor' : '';
// Determine cache entries required based on build settings // Determine cache entries required based on build settings
const cacheKeys = []; const cacheKeys = [];
@ -85,7 +94,8 @@ export class BundleActionCache {
// If sourcemaps are enabled, add original sourcemap as required // If sourcemaps are enabled, add original sourcemap as required
if (action.sourceMaps) { if (action.sourceMaps) {
cacheKeys[CacheKey.OriginalMap] = baseCacheKey + SourceMapVendorPostfix + '|orig-map'; cacheKeys[CacheKey.OriginalMap] =
baseCacheKey + SourceMapVendorPostfix + '|orig-map';
} }
} }
// If not only optimizing, add downlevel as required // If not only optimizing, add downlevel as required
@ -94,14 +104,17 @@ export class BundleActionCache {
// If sourcemaps are enabled, add downlevel sourcemap as required // If sourcemaps are enabled, add downlevel sourcemap as required
if (action.sourceMaps) { if (action.sourceMaps) {
cacheKeys[CacheKey.DownlevelMap] = baseCacheKey + SourceMapVendorPostfix + '|dl-map'; cacheKeys[CacheKey.DownlevelMap] =
baseCacheKey + SourceMapVendorPostfix + '|dl-map';
} }
} }
return cacheKeys; return cacheKeys;
} }
async getCacheEntries(cacheKeys: (string | null)[]): Promise<(CacheEntry | null)[] | false> { async getCacheEntries(
cacheKeys: (string | null)[]
): Promise<(CacheEntry | null)[] | false> {
// Attempt to get required cache entries // Attempt to get required cache entries
const cacheEntries = []; const cacheEntries = [];
for (const key of cacheKeys) { for (const key of cacheKeys) {
@ -113,7 +126,7 @@ export class BundleActionCache {
cacheEntries.push({ cacheEntries.push({
path: entry.path, path: entry.path,
size: entry.size, size: entry.size,
integrity: entry.metadata && entry.metadata.integrity, integrity: entry.metadata && entry.metadata.integrity
}); });
} else { } else {
cacheEntries.push(null); cacheEntries.push(null);
@ -123,8 +136,11 @@ export class BundleActionCache {
return cacheEntries; return cacheEntries;
} }
async getCachedBundleResult(action: ProcessBundleOptions): Promise<ProcessBundleResult | null> { async getCachedBundleResult(
const entries = action.cacheKeys && await this.getCacheEntries(action.cacheKeys); action: ProcessBundleOptions
): Promise<ProcessBundleResult | null> {
const entries =
action.cacheKeys && (await this.getCacheEntries(action.cacheKeys));
if (!entries) { if (!entries) {
return null; return null;
} }
@ -136,7 +152,7 @@ export class BundleActionCache {
result.original = { result.original = {
filename: action.filename, filename: action.filename,
size: cacheEntry.size, size: cacheEntry.size,
integrity: cacheEntry.integrity, integrity: cacheEntry.integrity
}; };
BundleActionCache.copyEntryContent(cacheEntry, result.original.filename); BundleActionCache.copyEntryContent(cacheEntry, result.original.filename);
@ -145,10 +161,13 @@ export class BundleActionCache {
if (cacheEntry) { if (cacheEntry) {
result.original.map = { result.original.map = {
filename: action.filename + '.map', filename: action.filename + '.map',
size: cacheEntry.size, size: cacheEntry.size
}; };
BundleActionCache.copyEntryContent(cacheEntry, result.original.filename + '.map'); BundleActionCache.copyEntryContent(
cacheEntry,
result.original.filename + '.map'
);
} }
} else if (!action.ignoreOriginal) { } else if (!action.ignoreOriginal) {
// If the original wasn't processed (and therefore not cached), add info // If the original wasn't processed (and therefore not cached), add info
@ -160,8 +179,8 @@ export class BundleActionCache {
? undefined ? undefined
: { : {
filename: action.filename + '.map', filename: action.filename + '.map',
size: Buffer.byteLength(action.map, 'utf8'), size: Buffer.byteLength(action.map, 'utf8')
}, }
}; };
} }
@ -170,7 +189,7 @@ export class BundleActionCache {
result.downlevel = { result.downlevel = {
filename: action.filename.replace('es2015', 'es5'), filename: action.filename.replace('es2015', 'es5'),
size: cacheEntry.size, size: cacheEntry.size,
integrity: cacheEntry.integrity, integrity: cacheEntry.integrity
}; };
BundleActionCache.copyEntryContent(cacheEntry, result.downlevel.filename); BundleActionCache.copyEntryContent(cacheEntry, result.downlevel.filename);
@ -179,10 +198,13 @@ export class BundleActionCache {
if (cacheEntry) { if (cacheEntry) {
result.downlevel.map = { result.downlevel.map = {
filename: action.filename.replace('es2015', 'es5') + '.map', filename: action.filename.replace('es2015', 'es5') + '.map',
size: cacheEntry.size, size: cacheEntry.size
}; };
BundleActionCache.copyEntryContent(cacheEntry, result.downlevel.filename + '.map'); BundleActionCache.copyEntryContent(
cacheEntry,
result.downlevel.filename + '.map'
);
} }
} }

View File

@ -8,7 +8,10 @@
import JestWorker from 'jest-worker'; import JestWorker from 'jest-worker';
import * as os from 'os'; import * as os from 'os';
import * as path from 'path'; import * as path from 'path';
import { ProcessBundleOptions, ProcessBundleResult } from '../utils/process-bundle'; import {
ProcessBundleOptions,
ProcessBundleResult
} from '../utils/process-bundle';
import { BundleActionCache } from './action-cache'; import { BundleActionCache } from './action-cache';
let workerFile = require.resolve('../utils/process-bundle'); let workerFile = require.resolve('../utils/process-bundle');
@ -25,13 +28,19 @@ export class BundleActionExecutor {
constructor( constructor(
private workerOptions: unknown, private workerOptions: unknown,
integrityAlgorithm?: string, integrityAlgorithm?: string,
private readonly sizeThreshold = 32 * 1024, private readonly sizeThreshold = 32 * 1024
) { ) {
this.cache = new BundleActionCache(integrityAlgorithm); this.cache = new BundleActionCache(integrityAlgorithm);
} }
private static executeMethod<O>(worker: JestWorker, method: string, input: unknown): Promise<O> { private static executeMethod<O>(
return ((worker as unknown) as Record<string, (i: unknown) => Promise<O>>)[method](input); worker: JestWorker,
method: string,
input: unknown
): Promise<O> {
return ((worker as unknown) as Record<string, (i: unknown) => Promise<O>>)[
method
](input);
} }
private ensureLarge(): JestWorker { private ensureLarge(): JestWorker {
@ -42,7 +51,7 @@ export class BundleActionExecutor {
// larger files are processed in a separate process to limit memory usage in the main process // larger files are processed in a separate process to limit memory usage in the main process
return (this.largeWorker = new JestWorker(workerFile, { return (this.largeWorker = new JestWorker(workerFile, {
exposedMethods: ['process'], exposedMethods: ['process'],
setupArgs: [this.workerOptions], setupArgs: [this.workerOptions]
})); }));
} }
@ -58,16 +67,27 @@ export class BundleActionExecutor {
setupArgs: [this.workerOptions], setupArgs: [this.workerOptions],
numWorkers: os.cpus().length < 2 ? 1 : 2, numWorkers: os.cpus().length < 2 ? 1 : 2,
// Will automatically fallback to processes if not supported // Will automatically fallback to processes if not supported
enableWorkerThreads: true, enableWorkerThreads: true
})); }));
} }
private executeAction<O>(method: string, action: { code: string }): Promise<O> { private executeAction<O>(
method: string,
action: { code: string }
): Promise<O> {
// code.length is not an exact byte count but close enough for this // code.length is not an exact byte count but close enough for this
if (action.code.length > this.sizeThreshold) { if (action.code.length > this.sizeThreshold) {
return BundleActionExecutor.executeMethod<O>(this.ensureLarge(), method, action); return BundleActionExecutor.executeMethod<O>(
this.ensureLarge(),
method,
action
);
} else { } else {
return BundleActionExecutor.executeMethod<O>(this.ensureSmall(), method, action); return BundleActionExecutor.executeMethod<O>(
this.ensureSmall(),
method,
action
);
} }
} }
@ -87,7 +107,10 @@ export class BundleActionExecutor {
} }
async *processAll(actions: Iterable<ProcessBundleOptions>) { async *processAll(actions: Iterable<ProcessBundleOptions>) {
const executions = new Map<Promise<ProcessBundleResult>, Promise<ProcessBundleResult>>(); const executions = new Map<
Promise<ProcessBundleResult>,
Promise<ProcessBundleResult>
>();
for (const action of actions) { for (const action of actions) {
const execution = this.process(action); const execution = this.process(action);
executions.set( executions.set(
@ -96,7 +119,7 @@ export class BundleActionExecutor {
executions.delete(execution); executions.delete(execution);
return result; return result;
}), })
); );
} }

View File

@ -5,12 +5,16 @@
* Use of this source code is governed by an MIT-style license that can be * 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 * found in the LICENSE file at https://angular.io/license
*/ */
import { BuilderContext, BuilderOutput, createBuilder } from '@angular-devkit/architect'; import {
BuilderContext,
BuilderOutput,
createBuilder
} from '@angular-devkit/architect';
import { import {
BuildResult, BuildResult,
EmittedFiles, EmittedFiles,
WebpackLoggingCallback, WebpackLoggingCallback,
runWebpack, runWebpack
} from '@angular-devkit/build-webpack'; } from '@angular-devkit/build-webpack';
import { import {
experimental, experimental,
@ -21,7 +25,7 @@ import {
normalize, normalize,
resolve, resolve,
tags, tags,
virtualFs, virtualFs
} from '@angular-devkit/core'; } from '@angular-devkit/core';
import { NodeJsSyncHost } from '@angular-devkit/core/node'; import { NodeJsSyncHost } from '@angular-devkit/core/node';
import { createHash } from 'crypto'; import { createHash } from 'crypto';
@ -30,7 +34,14 @@ import * as fs from 'fs';
import * as os from 'os'; import * as os from 'os';
import * as path from 'path'; import * as path from 'path';
import { from, of } from 'rxjs'; import { from, of } from 'rxjs';
import { bufferCount, catchError, concatMap, map, mergeScan, switchMap } from 'rxjs/operators'; import {
bufferCount,
catchError,
concatMap,
map,
mergeScan,
switchMap
} from 'rxjs/operators';
import { ScriptTarget } from 'typescript'; import { ScriptTarget } from 'typescript';
import * as webpack from 'webpack'; import * as webpack from 'webpack';
// import { NgBuildAnalyticsPlugin } from '../../plugins/webpack/analytics'; // import { NgBuildAnalyticsPlugin } from '../../plugins/webpack/analytics';
@ -43,11 +54,11 @@ import {
getStatsConfig, getStatsConfig,
getStylesConfig, getStylesConfig,
getWorkerConfig, getWorkerConfig,
normalizeExtraEntryPoints, normalizeExtraEntryPoints
} from '../angular-cli-files/models/webpack-configs'; } from '../angular-cli-files/models/webpack-configs';
import { import {
IndexHtmlTransform, IndexHtmlTransform,
writeIndexHtml, writeIndexHtml
} from '../angular-cli-files/utilities/index-file/write-index-html'; } from '../angular-cli-files/utilities/index-file/write-index-html';
import { readTsconfig } from '../angular-cli-files/utilities/read-tsconfig'; import { readTsconfig } from '../angular-cli-files/utilities/read-tsconfig';
import { augmentAppWithServiceWorker } from '../angular-cli-files/utilities/service-worker'; import { augmentAppWithServiceWorker } from '../angular-cli-files/utilities/service-worker';
@ -56,7 +67,7 @@ import {
generateBundleStats, generateBundleStats,
statsErrorsToString, statsErrorsToString,
statsToString, statsToString,
statsWarningsToString, statsWarningsToString
} from '../angular-cli-files/utilities/stats'; } from '../angular-cli-files/utilities/stats';
import { ExecutionTransformer } from '../transforms'; import { ExecutionTransformer } from '../transforms';
import { import {
@ -64,18 +75,18 @@ import {
deleteOutputDir, deleteOutputDir,
fullDifferential, fullDifferential,
normalizeOptimization, normalizeOptimization,
normalizeSourceMaps, normalizeSourceMaps
} from '../utils'; } from '../utils';
import { import {
ProcessBundleFile, ProcessBundleFile,
ProcessBundleOptions, ProcessBundleOptions,
ProcessBundleResult, ProcessBundleResult
} from '../utils/process-bundle'; } from '../utils/process-bundle';
import { assertCompatibleAngularVersion } from '../utils/version'; import { assertCompatibleAngularVersion } from '../utils/version';
import { import {
generateBrowserWebpackConfigFromContext, generateBrowserWebpackConfigFromContext,
getIndexInputFile, getIndexInputFile,
getIndexOutputFile, getIndexOutputFile
} from '../utils/webpack-browser-config'; } from '../utils/webpack-browser-config';
import { BundleActionExecutor } from './action-executor'; import { BundleActionExecutor } from './action-executor';
import { Schema as BrowserBuilderSchema } from './schema'; import { Schema as BrowserBuilderSchema } from './schema';
@ -91,7 +102,7 @@ export type BrowserBuilderOutput = json.JsonObject &
export function createBrowserLoggingCallback( export function createBrowserLoggingCallback(
verbose: boolean, verbose: boolean,
logger: logging.LoggerApi, logger: logging.LoggerApi
): WebpackLoggingCallback { ): WebpackLoggingCallback {
return (stats, config) => { return (stats, config) => {
// config.stats contains our own stats settings, added during buildWebpackConfig(). // config.stats contains our own stats settings, added during buildWebpackConfig().
@ -114,8 +125,11 @@ export function createBrowserLoggingCallback(
export async function buildBrowserWebpackConfigFromContext( export async function buildBrowserWebpackConfigFromContext(
options: BrowserBuilderSchema, options: BrowserBuilderSchema,
context: BuilderContext, context: BuilderContext,
host: virtualFs.Host<fs.Stats> = new NodeJsSyncHost(), host: virtualFs.Host<fs.Stats> = new NodeJsSyncHost()
): Promise<{ workspace: experimental.workspace.Workspace; config: webpack.Configuration[] }> { ): Promise<{
workspace: experimental.workspace.Workspace;
config: webpack.Configuration[];
}> {
return generateBrowserWebpackConfigFromContext( return generateBrowserWebpackConfigFromContext(
options, options,
context, context,
@ -126,15 +140,15 @@ export async function buildBrowserWebpackConfigFromContext(
getStatsConfig(wco), getStatsConfig(wco),
getAnalyticsConfig(wco, context), getAnalyticsConfig(wco, context),
getCompilerConfig(wco), getCompilerConfig(wco),
wco.buildOptions.webWorkerTsConfig ? getWorkerConfig(wco) : {}, wco.buildOptions.webWorkerTsConfig ? getWorkerConfig(wco) : {}
], ],
host, host
); );
} }
function getAnalyticsConfig( function getAnalyticsConfig(
wco: WebpackConfigOptions, wco: WebpackConfigOptions,
context: BuilderContext, context: BuilderContext
): webpack.Configuration { ): webpack.Configuration {
if (context.analytics) { if (context.analytics) {
// If there's analytics, add our plugin. Otherwise no need to slow down the build. // If there's analytics, add our plugin. Otherwise no need to slow down the build.
@ -142,12 +156,14 @@ function getAnalyticsConfig(
if (context.builder) { if (context.builder) {
// We already vetted that this is a "safe" package, otherwise the analytics would be noop. // We already vetted that this is a "safe" package, otherwise the analytics would be noop.
category = category =
context.builder.builderName.split(':')[1] || context.builder.builderName || 'build'; context.builder.builderName.split(':')[1] ||
context.builder.builderName ||
'build';
} }
// The category is the builder name if it's an angular builder. // The category is the builder name if it's an angular builder.
return { return {
plugins: [], plugins: []
}; };
} }
@ -166,9 +182,16 @@ async function initialize(
options: BrowserBuilderSchema, options: BrowserBuilderSchema,
context: BuilderContext, context: BuilderContext,
host: virtualFs.Host<fs.Stats>, host: virtualFs.Host<fs.Stats>,
webpackConfigurationTransform?: ExecutionTransformer<webpack.Configuration>, webpackConfigurationTransform?: ExecutionTransformer<webpack.Configuration>
): Promise<{ workspace: experimental.workspace.Workspace; config: webpack.Configuration[] }> { ): Promise<{
const { config, workspace } = await buildBrowserWebpackConfigFromContext(options, context, host); workspace: experimental.workspace.Workspace;
config: webpack.Configuration[];
}> {
const { config, workspace } = await buildBrowserWebpackConfigFromContext(
options,
context,
host
);
let transformedConfig; let transformedConfig;
if (webpackConfigurationTransform) { if (webpackConfigurationTransform) {
@ -182,7 +205,7 @@ async function initialize(
await deleteOutputDir( await deleteOutputDir(
normalize(context.workspaceRoot), normalize(context.workspaceRoot),
normalize(options.outputPath), normalize(options.outputPath),
host, host
).toPromise(); ).toPromise();
} }
@ -197,7 +220,7 @@ export function buildWebpackBrowser(
webpackConfiguration?: ExecutionTransformer<webpack.Configuration>; webpackConfiguration?: ExecutionTransformer<webpack.Configuration>;
logging?: WebpackLoggingCallback; logging?: WebpackLoggingCallback;
indexHtml?: IndexHtmlTransform; indexHtml?: IndexHtmlTransform;
} = {}, } = {}
) { ) {
const host = new NodeJsSyncHost(); const host = new NodeJsSyncHost();
const root = normalize(context.workspaceRoot); const root = normalize(context.workspaceRoot);
@ -205,7 +228,9 @@ export function buildWebpackBrowser(
// Check Angular version. // Check Angular version.
assertCompatibleAngularVersion(context.workspaceRoot, context.logger); assertCompatibleAngularVersion(context.workspaceRoot, context.logger);
return from(initialize(options, context, host, transforms.webpackConfiguration)).pipe( return from(
initialize(options, context, host, transforms.webpackConfiguration)
).pipe(
// tslint:disable-next-line: no-big-function // tslint:disable-next-line: no-big-function
switchMap(({ workspace, config: configs }) => { switchMap(({ workspace, config: configs }) => {
const projectName = context.target const projectName = context.target
@ -213,17 +238,22 @@ export function buildWebpackBrowser(
: workspace.getDefaultProjectName(); : workspace.getDefaultProjectName();
if (!projectName) { if (!projectName) {
throw new Error('Must either have a target from the context or a default project.'); throw new Error(
'Must either have a target from the context or a default project.'
);
} }
const projectRoot = resolve( const projectRoot = resolve(
workspace.root, workspace.root,
normalize(workspace.getProject(projectName).root), normalize(workspace.getProject(projectName).root)
); );
const tsConfig = readTsconfig(options.tsConfig, context.workspaceRoot); const tsConfig = readTsconfig(options.tsConfig, context.workspaceRoot);
const target = tsConfig.options.target || ScriptTarget.ES5; const target = tsConfig.options.target || ScriptTarget.ES5;
const buildBrowserFeatures = new BuildBrowserFeatures(getSystemPath(projectRoot), target); const buildBrowserFeatures = new BuildBrowserFeatures(
getSystemPath(projectRoot),
target
);
const isDifferentialLoadingNeeded = buildBrowserFeatures.isDifferentialLoadingNeeded(); const isDifferentialLoadingNeeded = buildBrowserFeatures.isDifferentialLoadingNeeded();
@ -251,14 +281,17 @@ export function buildWebpackBrowser(
transforms.logging || transforms.logging ||
(useBundleDownleveling (useBundleDownleveling
? () => {} ? () => {}
: createBrowserLoggingCallback(!!options.verbose, context.logger)), : createBrowserLoggingCallback(
!!options.verbose,
context.logger
))
}); });
} else { } else {
return of(); return of();
} }
}, },
{ success: true } as BuildResult, { success: true } as BuildResult,
1, 1
), ),
bufferCount(configs.length), bufferCount(configs.length),
// tslint:disable-next-line: no-big-function // tslint:disable-next-line: no-big-function
@ -270,10 +303,14 @@ export function buildWebpackBrowser(
// If it fails show any diagnostic messages and bail // If it fails show any diagnostic messages and bail
const webpackStats = buildEvents[0].webpackStats; const webpackStats = buildEvents[0].webpackStats;
if (webpackStats && webpackStats.warnings.length > 0) { if (webpackStats && webpackStats.warnings.length > 0) {
context.logger.warn(statsWarningsToString(webpackStats, { colors: true })); context.logger.warn(
statsWarningsToString(webpackStats, { colors: true })
);
} }
if (webpackStats && webpackStats.errors.length > 0) { if (webpackStats && webpackStats.errors.length > 0) {
context.logger.error(statsErrorsToString(webpackStats, { colors: true })); context.logger.error(
statsErrorsToString(webpackStats, { colors: true })
);
} }
return { success }; return { success };
@ -284,14 +321,19 @@ export function buildWebpackBrowser(
const scriptsEntryPointName = normalizeExtraEntryPoints( const scriptsEntryPointName = normalizeExtraEntryPoints(
options.scripts || [], options.scripts || [],
'scripts', 'scripts'
).map(x => x.bundleName); ).map(x => x.bundleName);
const [firstBuild, secondBuild] = buildEvents; const [firstBuild, secondBuild] = buildEvents;
if (isDifferentialLoadingNeeded && (fullDifferential || options.watch)) { if (
isDifferentialLoadingNeeded &&
(fullDifferential || options.watch)
) {
moduleFiles = firstBuild.emittedFiles || []; moduleFiles = firstBuild.emittedFiles || [];
files = moduleFiles.filter( files = moduleFiles.filter(
x => x.extension === '.css' || (x.name && scriptsEntryPointName.includes(x.name)), x =>
x.extension === '.css' ||
(x.name && scriptsEntryPointName.includes(x.name))
); );
if (buildEvents.length === 2) { if (buildEvents.length === 2) {
@ -303,13 +345,17 @@ export function buildWebpackBrowser(
noModuleFiles = []; noModuleFiles = [];
// Common options for all bundle process actions // Common options for all bundle process actions
const sourceMapOptions = normalizeSourceMaps(options.sourceMap || false); const sourceMapOptions = normalizeSourceMaps(
options.sourceMap || false
);
const actionOptions: Partial<ProcessBundleOptions> = { const actionOptions: Partial<ProcessBundleOptions> = {
optimize: normalizeOptimization(options.optimization).scripts, optimize: normalizeOptimization(options.optimization).scripts,
sourceMaps: sourceMapOptions.scripts, sourceMaps: sourceMapOptions.scripts,
hiddenSourceMaps: sourceMapOptions.hidden, hiddenSourceMaps: sourceMapOptions.hidden,
vendorSourceMaps: sourceMapOptions.vendor, vendorSourceMaps: sourceMapOptions.vendor,
integrityAlgorithm: options.subresourceIntegrity ? 'sha384' : undefined, integrityAlgorithm: options.subresourceIntegrity
? 'sha384'
: undefined
}; };
const actions: ProcessBundleOptions[] = []; const actions: ProcessBundleOptions[] = [];
@ -347,14 +393,20 @@ export function buildWebpackBrowser(
} }
// If not optimizing then ES2015 polyfills do not need processing // If not optimizing then ES2015 polyfills do not need processing
// Unlike other module scripts, it is never downleveled // Unlike other module scripts, it is never downleveled
const es2015Polyfills = file.file.startsWith('polyfills-es2015'); const es2015Polyfills = file.file.startsWith(
'polyfills-es2015'
);
if (!actionOptions.optimize && es2015Polyfills) { if (!actionOptions.optimize && es2015Polyfills) {
continue; continue;
} }
// Retrieve the content/map for the file // Retrieve the content/map for the file
// NOTE: Additional future optimizations will read directly from memory // NOTE: Additional future optimizations will read directly from memory
let filename = path.resolve(getSystemPath(root), options.outputPath, file.file); let filename = path.resolve(
getSystemPath(root),
options.outputPath,
file.file
);
const code = fs.readFileSync(filename, 'utf8'); const code = fs.readFileSync(filename, 'utf8');
let map; let map;
if (actionOptions.sourceMaps) { if (actionOptions.sourceMaps) {
@ -383,7 +435,7 @@ export function buildWebpackBrowser(
name: file.id!, name: file.id!,
runtime: file.file.startsWith('runtime'), runtime: file.file.startsWith('runtime'),
ignoreOriginal: es5Polyfills, ignoreOriginal: es5Polyfills,
optimizeOnly: es2015Polyfills, optimizeOnly: es2015Polyfills
}); });
// ES2015 polyfills are only optimized; optimization check was performed above // ES2015 polyfills are only optimized; optimization check was performed above
@ -399,7 +451,9 @@ export function buildWebpackBrowser(
} }
// Execute the bundle processing actions // Execute the bundle processing actions
context.logger.info('Generating ES5 bundles for differential loading...'); context.logger.info(
'Generating ES5 bundles for differential loading...'
);
const processActions: typeof actions = []; const processActions: typeof actions = [];
let processRuntimeAction: ProcessBundleOptions | undefined; let processRuntimeAction: ProcessBundleOptions | undefined;
@ -416,11 +470,13 @@ export function buildWebpackBrowser(
const executor = new BundleActionExecutor( const executor = new BundleActionExecutor(
{ cachePath: cacheDownlevelPath }, { cachePath: cacheDownlevelPath },
options.subresourceIntegrity ? 'sha384' : undefined, options.subresourceIntegrity ? 'sha384' : undefined
); );
try { try {
for await (const result of executor.processAll(processActions)) { for await (const result of executor.processAll(
processActions
)) {
processResults.push(result); processResults.push(result);
} }
} finally { } finally {
@ -431,32 +487,40 @@ export function buildWebpackBrowser(
if (processRuntimeAction) { if (processRuntimeAction) {
const runtimeOptions = { const runtimeOptions = {
...processRuntimeAction, ...processRuntimeAction,
runtimeData: processResults, runtimeData: processResults
}; };
processResults.push( processResults.push(
await import('../utils/process-bundle').then(m => m.process(runtimeOptions)), await import('../utils/process-bundle').then(m =>
m.process(runtimeOptions)
)
); );
} }
context.logger.info('ES5 bundle generation complete.'); context.logger.info('ES5 bundle generation complete.');
type ArrayElement<A> = A extends ReadonlyArray<infer T> ? T : never; type ArrayElement<A> = A extends ReadonlyArray<infer T>
? T
: never;
function generateBundleInfoStats( function generateBundleInfoStats(
id: string | number, id: string | number,
bundle: ProcessBundleFile, bundle: ProcessBundleFile,
chunk: ArrayElement<webpack.Stats.ToJsonOutput['chunks']> | undefined, chunk:
| ArrayElement<webpack.Stats.ToJsonOutput['chunks']>
| undefined
): string { ): string {
return generateBundleStats( return generateBundleStats(
{ {
id, id,
size: bundle.size, size: bundle.size,
files: bundle.map ? [bundle.filename, bundle.map.filename] : [bundle.filename], files: bundle.map
? [bundle.filename, bundle.map.filename]
: [bundle.filename],
names: chunk && chunk.names, names: chunk && chunk.names,
entry: !!chunk && chunk.names.includes('runtime'), entry: !!chunk && chunk.names.includes('runtime'),
initial: !!chunk && chunk.initial, initial: !!chunk && chunk.initial,
rendered: true, rendered: true
}, },
true, true
); );
} }
@ -468,14 +532,26 @@ export function buildWebpackBrowser(
const chunk = const chunk =
webpackStats && webpackStats &&
webpackStats.chunks && webpackStats.chunks &&
webpackStats.chunks.find(c => result.name === c.id.toString()); webpackStats.chunks.find(
c => result.name === c.id.toString()
);
if (result.original) { if (result.original) {
bundleInfoText += bundleInfoText +=
'\n' + generateBundleInfoStats(result.name, result.original, chunk); '\n' +
generateBundleInfoStats(
result.name,
result.original,
chunk
);
} }
if (result.downlevel) { if (result.downlevel) {
bundleInfoText += bundleInfoText +=
'\n' + generateBundleInfoStats(result.name, result.downlevel, chunk); '\n' +
generateBundleInfoStats(
result.name,
result.downlevel,
chunk
);
} }
} }
@ -486,9 +562,14 @@ export function buildWebpackBrowser(
} }
const asset = const asset =
webpackStats.assets && webpackStats.assets.find(a => a.name === chunk.files[0]); webpackStats.assets &&
webpackStats.assets.find(a => a.name === chunk.files[0]);
bundleInfoText += bundleInfoText +=
'\n' + generateBundleStats({ ...chunk, size: asset && asset.size }, true); '\n' +
generateBundleStats(
{ ...chunk, size: asset && asset.size },
true
);
} }
} }
@ -497,19 +578,25 @@ export function buildWebpackBrowser(
generateBuildStats( generateBuildStats(
(webpackStats && webpackStats.hash) || '<unknown>', (webpackStats && webpackStats.hash) || '<unknown>',
Date.now() - startTime, Date.now() - startTime,
true, true
); );
context.logger.info(bundleInfoText); context.logger.info(bundleInfoText);
if (webpackStats && webpackStats.warnings.length > 0) { if (webpackStats && webpackStats.warnings.length > 0) {
context.logger.warn(statsWarningsToString(webpackStats, { colors: true })); context.logger.warn(
statsWarningsToString(webpackStats, { colors: true })
);
} }
if (webpackStats && webpackStats.errors.length > 0) { if (webpackStats && webpackStats.errors.length > 0) {
context.logger.error(statsErrorsToString(webpackStats, { colors: true })); context.logger.error(
statsErrorsToString(webpackStats, { colors: true })
);
} }
} else { } else {
const { emittedFiles = [] } = firstBuild; const { emittedFiles = [] } = firstBuild;
files = emittedFiles.filter(x => x.name !== 'polyfills-es5'); files = emittedFiles.filter(x => x.name !== 'polyfills-es5');
noModuleFiles = emittedFiles.filter(x => x.name === 'polyfills-es5'); noModuleFiles = emittedFiles.filter(
x => x.name === 'polyfills-es5'
);
} }
if (options.index) { if (options.index) {
@ -517,7 +604,10 @@ export function buildWebpackBrowser(
host, host,
outputPath: resolve( outputPath: resolve(
root, root,
join(normalize(options.outputPath), getIndexOutputFile(options)), join(
normalize(options.outputPath),
getIndexOutputFile(options)
)
), ),
indexPath: join(root, getIndexInputFile(options)), indexPath: join(root, getIndexInputFile(options)),
files, files,
@ -529,11 +619,13 @@ export function buildWebpackBrowser(
scripts: options.scripts, scripts: options.scripts,
styles: options.styles, styles: options.styles,
postTransform: transforms.indexHtml, postTransform: transforms.indexHtml,
crossOrigin: options.crossOrigin, crossOrigin: options.crossOrigin
}) })
.pipe( .pipe(
map(() => ({ success: true })), map(() => ({ success: true })),
catchError(error => of({ success: false, error: mapErrorToMessage(error) })), catchError(error =>
of({ success: false, error: mapErrorToMessage(error) })
)
) )
.toPromise(); .toPromise();
} else { } else {
@ -552,11 +644,11 @@ export function buildWebpackBrowser(
projectRoot, projectRoot,
resolve(root, normalize(options.outputPath)), resolve(root, normalize(options.outputPath)),
options.baseHref || '/', options.baseHref || '/',
options.ngswConfigPath, options.ngswConfigPath
).then( ).then(
() => ({ success: true }), () => ({ success: true }),
error => ({ success: false, error: mapErrorToMessage(error) }), error => ({ success: false, error: mapErrorToMessage(error) })
), )
); );
} else { } else {
return of(buildEvent); return of(buildEvent);
@ -567,11 +659,14 @@ export function buildWebpackBrowser(
({ ({
...event, ...event,
// If we use differential loading, both configs have the same outputs // If we use differential loading, both configs have the same outputs
outputPath: path.resolve(context.workspaceRoot, options.outputPath), outputPath: path.resolve(
} as BrowserBuilderOutput), context.workspaceRoot,
), options.outputPath
)
} as BrowserBuilderOutput)
)
); );
}), })
); );
} }
@ -587,4 +682,6 @@ function mapErrorToMessage(error: unknown): string | undefined {
return undefined; return undefined;
} }
export default createBuilder<json.JsonObject & BrowserBuilderSchema>(buildWebpackBrowser); export default createBuilder<json.JsonObject & BrowserBuilderSchema>(
buildWebpackBrowser
);

View File

@ -1,4 +1,3 @@
// THIS FILE IS AUTOMATICALLY GENERATED. TO UPDATE THIS FILE YOU NEED TO CHANGE THE // THIS FILE IS AUTOMATICALLY GENERATED. TO UPDATE THIS FILE YOU NEED TO CHANGE THE
// CORRESPONDING JSON SCHEMA FILE, THEN RUN devkit-admin build (or bazel build ...). // CORRESPONDING JSON SCHEMA FILE, THEN RUN devkit-admin build (or bazel build ...).
@ -295,22 +294,22 @@ export interface Budget {
* The type of budget. * The type of budget.
*/ */
export enum Type { export enum Type {
All = "all", All = 'all',
AllScript = "allScript", AllScript = 'allScript',
Any = "any", Any = 'any',
AnyComponentStyle = "anyComponentStyle", AnyComponentStyle = 'anyComponentStyle',
AnyScript = "anyScript", AnyScript = 'anyScript',
Bundle = "bundle", Bundle = 'bundle',
Initial = "initial", Initial = 'initial'
} }
/** /**
* Define the crossorigin attribute setting of elements that provide CORS support. * Define the crossorigin attribute setting of elements that provide CORS support.
*/ */
export enum CrossOrigin { export enum CrossOrigin {
Anonymous = "anonymous", Anonymous = 'anonymous',
None = "none", None = 'none',
UseCredentials = "use-credentials", UseCredentials = 'use-credentials'
} }
export interface FileReplacement { export interface FileReplacement {
@ -324,9 +323,9 @@ export interface FileReplacement {
* How to handle missing translations for i18n. * How to handle missing translations for i18n.
*/ */
export enum I18NMissingTranslation { export enum I18NMissingTranslation {
Error = "error", Error = 'error',
Ignore = "ignore", Ignore = 'ignore',
Warning = "warning", Warning = 'warning'
} }
/** /**
@ -368,10 +367,10 @@ export interface OptimizationClass {
* Define the output filename cache-busting hashing mode. * Define the output filename cache-busting hashing mode.
*/ */
export enum OutputHashing { export enum OutputHashing {
All = "all", All = 'all',
Bundles = "bundles", Bundles = 'bundles',
Media = "media", Media = 'media',
None = "none", None = 'none'
} }
export type ExtraEntryPoint = ExtraEntryPointClass | string; export type ExtraEntryPoint = ExtraEntryPointClass | string;

View File

@ -22,9 +22,11 @@ export class BuildBrowserFeatures {
constructor( constructor(
private projectRoot: string, private projectRoot: string,
private scriptTarget: ts.ScriptTarget, private scriptTarget: ts.ScriptTarget
) { ) {
this._supportedBrowsers = browserslist(undefined, { path: this.projectRoot }); this._supportedBrowsers = browserslist(undefined, {
path: this.projectRoot
});
this._es6TargetOrLater = this.scriptTarget > ts.ScriptTarget.ES5; this._es6TargetOrLater = this.scriptTarget > ts.ScriptTarget.ES5;
} }
@ -54,12 +56,11 @@ export class BuildBrowserFeatures {
return false; return false;
} }
const safariBrowsers = [ const safariBrowsers = ['safari 10.1', 'ios_saf 10.3'];
'safari 10.1',
'ios_saf 10.3',
];
return this._supportedBrowsers.some(browser => safariBrowsers.includes(browser)); return this._supportedBrowsers.some(browser =>
safariBrowsers.includes(browser)
);
} }
/** /**
@ -70,19 +71,17 @@ export class BuildBrowserFeatures {
// n: feature is unavailable // n: feature is unavailable
// a: feature is partially supported // a: feature is partially supported
// x: feature is prefixed // x: feature is prefixed
const criteria = [ const criteria = ['y', 'a'];
'y',
'a',
];
const data = feature(features[featureId]); const data = feature(features[featureId]);
return !this._supportedBrowsers return !this._supportedBrowsers.some(browser => {
.some(browser => {
const [agentId, version] = browser.split(' '); const [agentId, version] = browser.split(' ');
const browserData = data.stats[agentId]; const browserData = data.stats[agentId];
const featureStatus = (browserData && browserData[version]) as string | undefined; const featureStatus = (browserData && browserData[version]) as
| string
| undefined;
// We are only interested in the first character // We are only interested in the first character
// Ex: when 'a #4 #5', we only need to check for 'a' // Ex: when 'a #4 #5', we only need to check for 'a'

View File

@ -6,7 +6,6 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import { TestProjectHost } from '@angular-devkit/architect/testing'; import { TestProjectHost } from '@angular-devkit/architect/testing';
import { getSystemPath, join } from '@angular-devkit/core'; import { getSystemPath, join } from '@angular-devkit/core';
import { ScriptTarget } from 'typescript'; import { ScriptTarget } from 'typescript';
@ -15,7 +14,8 @@ import { BuildBrowserFeatures } from './build-browser-features';
const devkitRoot = (global as any)._DevKitRoot; // tslint:disable-line:no-any const devkitRoot = (global as any)._DevKitRoot; // tslint:disable-line:no-any
const workspaceRoot = join( const workspaceRoot = join(
devkitRoot, devkitRoot,
'tests/angular_devkit/build_angular/hello-world-app/'); 'tests/angular_devkit/build_angular/hello-world-app/'
);
const host = new TestProjectHost(workspaceRoot); const host = new TestProjectHost(workspaceRoot);
@ -31,48 +31,48 @@ describe('BuildBrowserFeatures', () => {
describe('isDifferentialLoadingNeeded', () => { describe('isDifferentialLoadingNeeded', () => {
it('should be true for for IE 9-11 and ES2015', () => { it('should be true for for IE 9-11 and ES2015', () => {
host.writeMultipleFiles({ host.writeMultipleFiles({
'browserslist': 'IE 9-11', browserslist: 'IE 9-11'
}); });
const buildBrowserFeatures = new BuildBrowserFeatures( const buildBrowserFeatures = new BuildBrowserFeatures(
workspaceRootSysPath, workspaceRootSysPath,
ScriptTarget.ES2015, ScriptTarget.ES2015
); );
expect(buildBrowserFeatures.isDifferentialLoadingNeeded()).toBe(true); expect(buildBrowserFeatures.isDifferentialLoadingNeeded()).toBe(true);
}); });
it('should be false for Chrome and ES2015', () => { it('should be false for Chrome and ES2015', () => {
host.writeMultipleFiles({ host.writeMultipleFiles({
'browserslist': 'last 1 chrome version', browserslist: 'last 1 chrome version'
}); });
const buildBrowserFeatures = new BuildBrowserFeatures( const buildBrowserFeatures = new BuildBrowserFeatures(
workspaceRootSysPath, workspaceRootSysPath,
ScriptTarget.ES2015, ScriptTarget.ES2015
); );
expect(buildBrowserFeatures.isDifferentialLoadingNeeded()).toBe(false); expect(buildBrowserFeatures.isDifferentialLoadingNeeded()).toBe(false);
}); });
it('detects no need for differential loading for target is ES5', () => { it('detects no need for differential loading for target is ES5', () => {
host.writeMultipleFiles({ host.writeMultipleFiles({
'browserslist': 'last 1 chrome version', browserslist: 'last 1 chrome version'
}); });
const buildBrowserFeatures = new BuildBrowserFeatures( const buildBrowserFeatures = new BuildBrowserFeatures(
workspaceRootSysPath, workspaceRootSysPath,
ScriptTarget.ES5, ScriptTarget.ES5
); );
expect(buildBrowserFeatures.isDifferentialLoadingNeeded()).toBe(false); expect(buildBrowserFeatures.isDifferentialLoadingNeeded()).toBe(false);
}); });
it('should be false for Safari 10.1 when target is ES2015', () => { it('should be false for Safari 10.1 when target is ES2015', () => {
host.writeMultipleFiles({ host.writeMultipleFiles({
'browserslist': 'Safari 10.1', browserslist: 'Safari 10.1'
}); });
const buildBrowserFeatures = new BuildBrowserFeatures( const buildBrowserFeatures = new BuildBrowserFeatures(
workspaceRootSysPath, workspaceRootSysPath,
ScriptTarget.ES2015, ScriptTarget.ES2015
); );
expect(buildBrowserFeatures.isDifferentialLoadingNeeded()).toBe(false); expect(buildBrowserFeatures.isDifferentialLoadingNeeded()).toBe(false);
}); });
@ -81,48 +81,48 @@ describe('BuildBrowserFeatures', () => {
describe('isFeatureSupported', () => { describe('isFeatureSupported', () => {
it('should be true for es6-module and Safari 10.1', () => { it('should be true for es6-module and Safari 10.1', () => {
host.writeMultipleFiles({ host.writeMultipleFiles({
'browserslist': 'Safari 10.1', browserslist: 'Safari 10.1'
}); });
const buildBrowserFeatures = new BuildBrowserFeatures( const buildBrowserFeatures = new BuildBrowserFeatures(
workspaceRootSysPath, workspaceRootSysPath,
ScriptTarget.ES2015, ScriptTarget.ES2015
); );
expect(buildBrowserFeatures.isFeatureSupported('es6-module')).toBe(true); expect(buildBrowserFeatures.isFeatureSupported('es6-module')).toBe(true);
}); });
it('should be false for es6-module and IE9', () => { it('should be false for es6-module and IE9', () => {
host.writeMultipleFiles({ host.writeMultipleFiles({
'browserslist': 'IE 9', browserslist: 'IE 9'
}); });
const buildBrowserFeatures = new BuildBrowserFeatures( const buildBrowserFeatures = new BuildBrowserFeatures(
workspaceRootSysPath, workspaceRootSysPath,
ScriptTarget.ES2015, ScriptTarget.ES2015
); );
expect(buildBrowserFeatures.isFeatureSupported('es6-module')).toBe(false); expect(buildBrowserFeatures.isFeatureSupported('es6-module')).toBe(false);
}); });
it('should be true for es6-module and last 1 chrome version', () => { it('should be true for es6-module and last 1 chrome version', () => {
host.writeMultipleFiles({ host.writeMultipleFiles({
'browserslist': 'last 1 chrome version', browserslist: 'last 1 chrome version'
}); });
const buildBrowserFeatures = new BuildBrowserFeatures( const buildBrowserFeatures = new BuildBrowserFeatures(
workspaceRootSysPath, workspaceRootSysPath,
ScriptTarget.ES2015, ScriptTarget.ES2015
); );
expect(buildBrowserFeatures.isFeatureSupported('es6-module')).toBe(true); expect(buildBrowserFeatures.isFeatureSupported('es6-module')).toBe(true);
}); });
it('should be true for es6-module and Edge 18', () => { it('should be true for es6-module and Edge 18', () => {
host.writeMultipleFiles({ host.writeMultipleFiles({
'browserslist': 'Edge 18', browserslist: 'Edge 18'
}); });
const buildBrowserFeatures = new BuildBrowserFeatures( const buildBrowserFeatures = new BuildBrowserFeatures(
workspaceRootSysPath, workspaceRootSysPath,
ScriptTarget.ES2015, ScriptTarget.ES2015
); );
expect(buildBrowserFeatures.isFeatureSupported('es6-module')).toBe(true); expect(buildBrowserFeatures.isFeatureSupported('es6-module')).toBe(true);
}); });
@ -131,63 +131,63 @@ describe('BuildBrowserFeatures', () => {
describe('isNoModulePolyfillNeeded', () => { describe('isNoModulePolyfillNeeded', () => {
it('should be false for Safari 10.1 when target is ES5', () => { it('should be false for Safari 10.1 when target is ES5', () => {
host.writeMultipleFiles({ host.writeMultipleFiles({
'browserslist': 'Safari 10.1', browserslist: 'Safari 10.1'
}); });
const buildBrowserFeatures = new BuildBrowserFeatures( const buildBrowserFeatures = new BuildBrowserFeatures(
workspaceRootSysPath, workspaceRootSysPath,
ScriptTarget.ES5, ScriptTarget.ES5
); );
expect(buildBrowserFeatures.isNoModulePolyfillNeeded()).toBe(false); expect(buildBrowserFeatures.isNoModulePolyfillNeeded()).toBe(false);
}); });
it('should be false for Safari 10.1 when target is ES2015', () => { it('should be false for Safari 10.1 when target is ES2015', () => {
host.writeMultipleFiles({ host.writeMultipleFiles({
'browserslist': 'Safari 10.1', browserslist: 'Safari 10.1'
}); });
const buildBrowserFeatures = new BuildBrowserFeatures( const buildBrowserFeatures = new BuildBrowserFeatures(
workspaceRootSysPath, workspaceRootSysPath,
ScriptTarget.ES2015, ScriptTarget.ES2015
); );
expect(buildBrowserFeatures.isNoModulePolyfillNeeded()).toBe(false); expect(buildBrowserFeatures.isNoModulePolyfillNeeded()).toBe(false);
}); });
it('should be true for Safari 9+ when target is ES2015', () => { it('should be true for Safari 9+ when target is ES2015', () => {
host.writeMultipleFiles({ host.writeMultipleFiles({
'browserslist': 'Safari >= 9', browserslist: 'Safari >= 9'
}); });
const buildBrowserFeatures = new BuildBrowserFeatures( const buildBrowserFeatures = new BuildBrowserFeatures(
workspaceRootSysPath, workspaceRootSysPath,
ScriptTarget.ES2015, ScriptTarget.ES2015
); );
expect(buildBrowserFeatures.isNoModulePolyfillNeeded()).toBe(true); expect(buildBrowserFeatures.isNoModulePolyfillNeeded()).toBe(true);
}); });
it('should be false for Safari 9+ when target is ES5', () => { it('should be false for Safari 9+ when target is ES5', () => {
host.writeMultipleFiles({ host.writeMultipleFiles({
'browserslist': 'Safari >= 9', browserslist: 'Safari >= 9'
}); });
const buildBrowserFeatures = new BuildBrowserFeatures( const buildBrowserFeatures = new BuildBrowserFeatures(
workspaceRootSysPath, workspaceRootSysPath,
ScriptTarget.ES5, ScriptTarget.ES5
); );
expect(buildBrowserFeatures.isNoModulePolyfillNeeded()).toBe(false); expect(buildBrowserFeatures.isNoModulePolyfillNeeded()).toBe(false);
}); });
it('should be false when not supporting Safari 10.1 target is ES2015', () => { it('should be false when not supporting Safari 10.1 target is ES2015', () => {
host.writeMultipleFiles({ host.writeMultipleFiles({
'browserslist': ` browserslist: `
Edge 18 Edge 18
IE 9 IE 9
`, `
}); });
const buildBrowserFeatures = new BuildBrowserFeatures( const buildBrowserFeatures = new BuildBrowserFeatures(
workspaceRootSysPath, workspaceRootSysPath,
ScriptTarget.ES2015, ScriptTarget.ES2015
); );
expect(buildBrowserFeatures.isNoModulePolyfillNeeded()).toBe(false); expect(buildBrowserFeatures.isNoModulePolyfillNeeded()).toBe(false);
}); });

View File

@ -12,14 +12,18 @@ import { concatMap, last } from 'rxjs/operators';
/** /**
* Delete an output directory, but error out if it's the root of the project. * Delete an output directory, but error out if it's the root of the project.
*/ */
export function deleteOutputDir(root: Path, outputPath: Path, host: virtualFs.Host) { export function deleteOutputDir(
root: Path,
outputPath: Path,
host: virtualFs.Host
) {
const resolvedOutputPath = resolve(root, outputPath); const resolvedOutputPath = resolve(root, outputPath);
if (resolvedOutputPath === root) { if (resolvedOutputPath === root) {
throw new Error('Output path MUST not be project root directory!'); throw new Error('Output path MUST not be project root directory!');
} }
return host.exists(resolvedOutputPath).pipe( return host.exists(resolvedOutputPath).pipe(
concatMap(exists => exists ? host.delete(resolvedOutputPath) : EMPTY), concatMap(exists => (exists ? host.delete(resolvedOutputPath) : EMPTY)),
last(null, null), last(null, null)
); );
} }

View File

@ -7,4 +7,5 @@
*/ */
const mangleVariable = process.env['NG_BUILD_MANGLE']; const mangleVariable = process.env['NG_BUILD_MANGLE'];
export const manglingDisabled = export const manglingDisabled =
!!mangleVariable && (mangleVariable === '0' || mangleVariable.toLowerCase() === 'false'); !!mangleVariable &&
(mangleVariable === '0' || mangleVariable.toLowerCase() === 'false');

View File

@ -14,11 +14,10 @@ import {
normalize, normalize,
relative, relative,
resolve, resolve,
virtualFs, virtualFs
} from '@angular-devkit/core'; } from '@angular-devkit/core';
import { AssetPattern, AssetPatternClass } from '../browser/schema'; import { AssetPattern, AssetPatternClass } from '../browser/schema';
export class MissingAssetSourceRootException extends BaseException { export class MissingAssetSourceRootException extends BaseException {
constructor(path: String) { constructor(path: String) {
super(`The ${path} asset path must start with the project source root.`); super(`The ${path} asset path must start with the project source root.`);
@ -30,7 +29,7 @@ export function normalizeAssetPatterns(
host: virtualFs.SyncDelegateHost, host: virtualFs.SyncDelegateHost,
root: Path, root: Path,
projectRoot: Path, projectRoot: Path,
maybeSourceRoot: Path | undefined, maybeSourceRoot: Path | undefined
): AssetPatternClass[] { ): AssetPatternClass[] {
// When sourceRoot is not available, we default to ${projectRoot}/src. // When sourceRoot is not available, we default to ${projectRoot}/src.
const sourceRoot = maybeSourceRoot || join(projectRoot, 'src'); const sourceRoot = maybeSourceRoot || join(projectRoot, 'src');
@ -40,8 +39,7 @@ export function normalizeAssetPatterns(
return []; return [];
} }
return assetPatterns return assetPatterns.map(assetPattern => {
.map(assetPattern => {
// Normalize string asset patterns to objects. // Normalize string asset patterns to objects.
if (typeof assetPattern === 'string') { if (typeof assetPattern === 'string') {
const assetPath = normalize(assetPattern); const assetPath = normalize(assetPattern);

View File

@ -1,4 +1,3 @@
/** /**
* @license * @license
* Copyright Google Inc. All Rights Reserved. * Copyright Google Inc. All Rights Reserved.
@ -7,50 +6,62 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import { Path, virtualFs } from '@angular-devkit/core'; import { Path, virtualFs } from '@angular-devkit/core';
import { BuildOptions } from '../angular-cli-files/models/build-options'; import { BuildOptions } from '../angular-cli-files/models/build-options';
import { import {
AssetPatternClass, AssetPatternClass,
OptimizationClass, OptimizationClass,
Schema as BrowserBuilderSchema, Schema as BrowserBuilderSchema,
SourceMapClass, SourceMapClass
} from '../browser/schema'; } from '../browser/schema';
import { normalizeAssetPatterns } from './normalize-asset-patterns'; import { normalizeAssetPatterns } from './normalize-asset-patterns';
import { import {
NormalizedFileReplacement, NormalizedFileReplacement,
normalizeFileReplacements, normalizeFileReplacements
} from './normalize-file-replacements'; } from './normalize-file-replacements';
import { normalizeOptimization } from './normalize-optimization'; import { normalizeOptimization } from './normalize-optimization';
import { normalizeSourceMaps } from './normalize-source-maps'; import { normalizeSourceMaps } from './normalize-source-maps';
/** /**
* A normalized browser builder schema. * A normalized browser builder schema.
*/ */
export type NormalizedBrowserBuilderSchema = BrowserBuilderSchema & BuildOptions & { export type NormalizedBrowserBuilderSchema = BrowserBuilderSchema &
BuildOptions & {
sourceMap: SourceMapClass; sourceMap: SourceMapClass;
assets: AssetPatternClass[]; assets: AssetPatternClass[];
fileReplacements: NormalizedFileReplacement[]; fileReplacements: NormalizedFileReplacement[];
optimization: OptimizationClass; optimization: OptimizationClass;
}; };
export function normalizeBrowserSchema( export function normalizeBrowserSchema(
host: virtualFs.Host<{}>, host: virtualFs.Host<{}>,
root: Path, root: Path,
projectRoot: Path, projectRoot: Path,
sourceRoot: Path | undefined, sourceRoot: Path | undefined,
options: BrowserBuilderSchema, options: BrowserBuilderSchema
): NormalizedBrowserBuilderSchema { ): NormalizedBrowserBuilderSchema {
const syncHost = new virtualFs.SyncDelegateHost(host); const syncHost = new virtualFs.SyncDelegateHost(host);
const normalizedSourceMapOptions = normalizeSourceMaps(options.sourceMap || false); const normalizedSourceMapOptions = normalizeSourceMaps(
normalizedSourceMapOptions.vendor = normalizedSourceMapOptions.vendor || options.vendorSourceMap; options.sourceMap || false
);
normalizedSourceMapOptions.vendor =
normalizedSourceMapOptions.vendor || options.vendorSourceMap;
return { return {
...options, ...options,
assets: normalizeAssetPatterns(options.assets || [], syncHost, root, projectRoot, sourceRoot), assets: normalizeAssetPatterns(
fileReplacements: normalizeFileReplacements(options.fileReplacements || [], syncHost, root), options.assets || [],
syncHost,
root,
projectRoot,
sourceRoot
),
fileReplacements: normalizeFileReplacements(
options.fileReplacements || [],
syncHost,
root
),
optimization: normalizeOptimization(options.optimization), optimization: normalizeOptimization(options.optimization),
sourceMap: normalizedSourceMapOptions, sourceMap: normalizedSourceMapOptions,
@ -60,10 +71,11 @@ export function normalizeBrowserSchema(
scripts: options.scripts || [], scripts: options.scripts || [],
styles: options.styles || [], styles: options.styles || [],
stylePreprocessorOptions: { stylePreprocessorOptions: {
includePaths: options.stylePreprocessorOptions includePaths:
&& options.stylePreprocessorOptions.includePaths (options.stylePreprocessorOptions &&
|| [], options.stylePreprocessorOptions.includePaths) ||
[]
}, },
lazyModules: options.lazyModules || [], lazyModules: options.lazyModules || []
}; };
} }

View File

@ -12,11 +12,10 @@ import {
getSystemPath, getSystemPath,
join, join,
normalize, normalize,
virtualFs, virtualFs
} from '@angular-devkit/core'; } from '@angular-devkit/core';
import { FileReplacement } from '../browser/schema'; import { FileReplacement } from '../browser/schema';
export class MissingFileReplacementException extends BaseException { export class MissingFileReplacementException extends BaseException {
constructor(path: String) { constructor(path: String) {
super(`The ${path} path in file replacements does not exist.`); super(`The ${path} path in file replacements does not exist.`);
@ -31,14 +30,15 @@ export interface NormalizedFileReplacement {
export function normalizeFileReplacements( export function normalizeFileReplacements(
fileReplacements: FileReplacement[], fileReplacements: FileReplacement[],
host: virtualFs.SyncDelegateHost, host: virtualFs.SyncDelegateHost,
root: Path, root: Path
): NormalizedFileReplacement[] { ): NormalizedFileReplacement[] {
if (fileReplacements.length === 0) { if (fileReplacements.length === 0) {
return []; return [];
} }
const normalizedReplacement = fileReplacements const normalizedReplacement = fileReplacements.map(replacement =>
.map(replacement => normalizeFileReplacement(replacement, root)); normalizeFileReplacement(replacement, root)
);
for (const { replace, with: replacementWith } of normalizedReplacement) { for (const { replace, with: replacementWith } of normalizedReplacement) {
if (!host.exists(replacementWith)) { if (!host.exists(replacementWith)) {
@ -55,7 +55,7 @@ export function normalizeFileReplacements(
function normalizeFileReplacement( function normalizeFileReplacement(
fileReplacement: FileReplacement, fileReplacement: FileReplacement,
root?: Path, root?: Path
): NormalizedFileReplacement { ): NormalizedFileReplacement {
let replacePath: Path; let replacePath: Path;
let withPath: Path; let withPath: Path;
@ -66,7 +66,9 @@ function normalizeFileReplacement(
replacePath = normalize(fileReplacement.replace); replacePath = normalize(fileReplacement.replace);
withPath = normalize(fileReplacement.with); withPath = normalize(fileReplacement.with);
} else { } else {
throw new Error(`Invalid file replacement: ${JSON.stringify(fileReplacement)}`); throw new Error(
`Invalid file replacement: ${JSON.stringify(fileReplacement)}`
);
} }
// TODO: For 7.x should this only happen if not absolute? // TODO: For 7.x should this only happen if not absolute?

View File

@ -9,10 +9,12 @@
import { OptimizationClass, OptimizationUnion } from '../browser/schema'; import { OptimizationClass, OptimizationUnion } from '../browser/schema';
export function normalizeOptimization( export function normalizeOptimization(
optimization: OptimizationUnion = false, optimization: OptimizationUnion = false
): Required<OptimizationClass> { ): Required<OptimizationClass> {
return { return {
scripts: typeof optimization === 'object' ? !!optimization.scripts : optimization, scripts:
styles: typeof optimization === 'object' ? !!optimization.styles : optimization, typeof optimization === 'object' ? !!optimization.scripts : optimization,
styles:
typeof optimization === 'object' ? !!optimization.styles : optimization
}; };
} }

View File

@ -11,13 +11,13 @@ import { SourceMapClass, SourceMapUnion } from '../browser/schema';
export function normalizeSourceMaps(sourceMap: SourceMapUnion): SourceMapClass { export function normalizeSourceMaps(sourceMap: SourceMapUnion): SourceMapClass {
const scripts = typeof sourceMap === 'object' ? sourceMap.scripts : sourceMap; const scripts = typeof sourceMap === 'object' ? sourceMap.scripts : sourceMap;
const styles = typeof sourceMap === 'object' ? sourceMap.styles : sourceMap; const styles = typeof sourceMap === 'object' ? sourceMap.styles : sourceMap;
const hidden = typeof sourceMap === 'object' && sourceMap.hidden || false; const hidden = (typeof sourceMap === 'object' && sourceMap.hidden) || false;
const vendor = typeof sourceMap === 'object' && sourceMap.vendor || false; const vendor = (typeof sourceMap === 'object' && sourceMap.vendor) || false;
return { return {
vendor, vendor,
hidden, hidden,
scripts, scripts,
styles, styles
}; };
} }

View File

@ -8,7 +8,11 @@
import { createHash } from 'crypto'; import { createHash } from 'crypto';
import * as fs from 'fs'; import * as fs from 'fs';
import * as path from 'path'; import * as path from 'path';
import { RawSourceMap, SourceMapConsumer, SourceMapGenerator } from 'source-map'; import {
RawSourceMap,
SourceMapConsumer,
SourceMapGenerator
} from 'source-map';
import { minify } from 'terser'; import { minify } from 'terser';
import { ScriptTarget, transpileModule } from 'typescript'; import { ScriptTarget, transpileModule } from 'typescript';
import { SourceMapSource } from 'webpack-sources'; import { SourceMapSource } from 'webpack-sources';
@ -54,7 +58,7 @@ export const enum CacheKey {
OriginalCode = 0, OriginalCode = 0,
OriginalMap = 1, OriginalMap = 1,
DownlevelCode = 2, DownlevelCode = 2,
DownlevelMap = 3, DownlevelMap = 3
} }
let cachePath: string | undefined; let cachePath: string | undefined;
@ -63,15 +67,21 @@ export function setup(options: { cachePath: string }): void {
cachePath = options.cachePath; cachePath = options.cachePath;
} }
async function cachePut(content: string, key: string | null, integrity?: string): Promise<void> { async function cachePut(
content: string,
key: string | null,
integrity?: string
): Promise<void> {
if (cachePath && key) { if (cachePath && key) {
await cacache.put(cachePath, key, content, { await cacache.put(cachePath, key, content, {
metadata: { integrity }, metadata: { integrity }
}); });
} }
} }
export async function process(options: ProcessBundleOptions): Promise<ProcessBundleResult> { export async function process(
options: ProcessBundleOptions
): Promise<ProcessBundleResult> {
if (!options.cacheKeys) { if (!options.cacheKeys) {
options.cacheKeys = []; options.cacheKeys = [];
} }
@ -79,7 +89,10 @@ export async function process(options: ProcessBundleOptions): Promise<ProcessBun
const result: ProcessBundleResult = { name: options.name }; const result: ProcessBundleResult = { name: options.name };
if (options.integrityAlgorithm) { if (options.integrityAlgorithm) {
// Store unmodified code integrity value -- used for SRI value replacement // Store unmodified code integrity value -- used for SRI value replacement
result.integrity = generateIntegrityValue(options.integrityAlgorithm, options.code); result.integrity = generateIntegrityValue(
options.integrityAlgorithm,
options.code
);
} }
// Runtime chunk requires specialized handling // Runtime chunk requires specialized handling
@ -108,15 +121,18 @@ export async function process(options: ProcessBundleOptions): Promise<ProcessBun
fileName: downlevelFilename, fileName: downlevelFilename,
compilerOptions: { compilerOptions: {
sourceMap: !!sourceMap, sourceMap: !!sourceMap,
target: ScriptTarget.ES5, target: ScriptTarget.ES5
}, }
}); });
downlevelCode = transformResult.outputText; downlevelCode = transformResult.outputText;
if (sourceMap && transformResult.sourceMapText) { if (sourceMap && transformResult.sourceMapText) {
if (manualSourceMaps) { if (manualSourceMaps) {
downlevelMap = await mergeSourcemaps(sourceMap, JSON.parse(transformResult.sourceMapText)); downlevelMap = await mergeSourcemaps(
sourceMap,
JSON.parse(transformResult.sourceMapText)
);
} else { } else {
// More accurate but significantly more costly // More accurate but significantly more costly
const tempSource = new SourceMapSource( const tempSource = new SourceMapSource(
@ -124,7 +140,7 @@ export async function process(options: ProcessBundleOptions): Promise<ProcessBun
downlevelFilename, downlevelFilename,
JSON.parse(transformResult.sourceMapText), JSON.parse(transformResult.sourceMapText),
sourceCode, sourceCode,
sourceMap, sourceMap
); );
downlevelMap = tempSource.map(); downlevelMap = tempSource.map();
@ -137,7 +153,7 @@ export async function process(options: ProcessBundleOptions): Promise<ProcessBun
const minifyResult = terserMangle(downlevelCode, { const minifyResult = terserMangle(downlevelCode, {
filename: downlevelFilename, filename: downlevelFilename,
map: downlevelMap, map: downlevelMap,
compress: true, compress: true
}); });
downlevelCode = minifyResult.code; downlevelCode = minifyResult.code;
downlevelMap = minifyResult.map; downlevelMap = minifyResult.map;
@ -166,13 +182,13 @@ export async function process(options: ProcessBundleOptions): Promise<ProcessBun
downlevelFilename, downlevelFilename,
downlevelCode, downlevelCode,
mapContent, mapContent,
options.integrityAlgorithm, options.integrityAlgorithm
); );
await cachePut( await cachePut(
downlevelCode, downlevelCode,
options.cacheKeys[CacheKey.DownlevelCode], options.cacheKeys[CacheKey.DownlevelCode],
result.downlevel.integrity, result.downlevel.integrity
); );
fs.writeFileSync(downlevelPath, downlevelCode); fs.writeFileSync(downlevelPath, downlevelCode);
} }
@ -183,7 +199,7 @@ export async function process(options: ProcessBundleOptions): Promise<ProcessBun
options.filename, options.filename,
options.code, options.code,
options.map, options.map,
options.integrityAlgorithm, options.integrityAlgorithm
); );
} }
@ -205,7 +221,7 @@ async function mergeSourcemaps(first: RawSourceMap, second: RawSourceMap) {
} }
const originalPosition = originalConsumer.originalPositionFor({ const originalPosition = originalConsumer.originalPositionFor({
line: mapping.originalLine, line: mapping.originalLine,
column: mapping.originalColumn, column: mapping.originalColumn
}); });
if ( if (
originalPosition.line === null || originalPosition.line === null ||
@ -217,14 +233,14 @@ async function mergeSourcemaps(first: RawSourceMap, second: RawSourceMap) {
generator.addMapping({ generator.addMapping({
generated: { generated: {
line: mapping.generatedLine, line: mapping.generatedLine,
column: mapping.generatedColumn, column: mapping.generatedColumn
}, },
name: originalPosition.name || undefined, name: originalPosition.name || undefined,
original: { original: {
line: originalPosition.line, line: originalPosition.line,
column: originalPosition.column, column: originalPosition.column
}, },
source: originalPosition.source, source: originalPosition.source
}); });
}); });
}); });
@ -242,24 +258,28 @@ async function mergeSourcemaps(first: RawSourceMap, second: RawSourceMap) {
return map; return map;
} }
async function mangleOriginal(options: ProcessBundleOptions): Promise<ProcessBundleFile> { async function mangleOriginal(
options: ProcessBundleOptions
): Promise<ProcessBundleFile> {
const result = terserMangle(options.code, { const result = terserMangle(options.code, {
filename: path.basename(options.filename), filename: path.basename(options.filename),
map: options.map ? JSON.parse(options.map) : undefined, map: options.map ? JSON.parse(options.map) : undefined,
ecma: 6, ecma: 6
}); });
let mapContent; let mapContent;
if (result.map) { if (result.map) {
if (!options.hiddenSourceMaps) { if (!options.hiddenSourceMaps) {
result.code += `\n//# sourceMappingURL=${path.basename(options.filename)}.map`; result.code += `\n//# sourceMappingURL=${path.basename(
options.filename
)}.map`;
} }
mapContent = JSON.stringify(result.map); mapContent = JSON.stringify(result.map);
await cachePut( await cachePut(
mapContent, mapContent,
(options.cacheKeys && options.cacheKeys[CacheKey.OriginalMap]) || null, (options.cacheKeys && options.cacheKeys[CacheKey.OriginalMap]) || null
); );
fs.writeFileSync(options.filename + '.map', mapContent); fs.writeFileSync(options.filename + '.map', mapContent);
} }
@ -268,13 +288,13 @@ async function mangleOriginal(options: ProcessBundleOptions): Promise<ProcessBun
options.filename, options.filename,
result.code, result.code,
mapContent, mapContent,
options.integrityAlgorithm, options.integrityAlgorithm
); );
await cachePut( await cachePut(
result.code, result.code,
(options.cacheKeys && options.cacheKeys[CacheKey.OriginalCode]) || null, (options.cacheKeys && options.cacheKeys[CacheKey.OriginalCode]) || null,
fileResult.integrity, fileResult.integrity
); );
fs.writeFileSync(options.filename, result.code); fs.writeFileSync(options.filename, result.code);
@ -283,7 +303,12 @@ async function mangleOriginal(options: ProcessBundleOptions): Promise<ProcessBun
function terserMangle( function terserMangle(
code: string, code: string,
options: { filename?: string; map?: RawSourceMap; compress?: boolean; ecma?: 5 | 6 } = {}, options: {
filename?: string;
map?: RawSourceMap;
compress?: boolean;
ecma?: 5 | 6;
} = {}
) { ) {
// Note: Investigate converting the AST instead of re-parsing // Note: Investigate converting the AST instead of re-parsing
// estree -> terser is already supported; need babel -> estree/terser // estree -> terser is already supported; need babel -> estree/terser
@ -296,7 +321,7 @@ function terserMangle(
safari10: true, safari10: true,
output: { output: {
ascii_only: true, ascii_only: true,
webkit: true, webkit: true
}, },
sourceMap: sourceMap:
!!options.map && !!options.map &&
@ -305,10 +330,10 @@ function terserMangle(
// terser uses an old version of the sourcemap typings // terser uses an old version of the sourcemap typings
// tslint:disable-next-line: no-any // tslint:disable-next-line: no-any
content: options.map as any, content: options.map as any,
asObject: true, asObject: true
// typings don't include asObject option // typings don't include asObject option
// tslint:disable-next-line: no-any // tslint:disable-next-line: no-any
} as any), } as any)
}); });
if (minifyOutput.error) { if (minifyOutput.error) {
@ -316,25 +341,29 @@ function terserMangle(
} }
// tslint:disable-next-line: no-non-null-assertion // tslint:disable-next-line: no-non-null-assertion
return { code: minifyOutput.code!, map: minifyOutput.map as unknown | RawSourceMap | undefined }; return {
code: minifyOutput.code!,
map: minifyOutput.map as unknown | RawSourceMap | undefined
};
} }
function createFileEntry( function createFileEntry(
filename: string, filename: string,
code: string, code: string,
map: string | undefined, map: string | undefined,
integrityAlgorithm?: string, integrityAlgorithm?: string
): ProcessBundleFile { ): ProcessBundleFile {
return { return {
filename: filename, filename: filename,
size: Buffer.byteLength(code), size: Buffer.byteLength(code),
integrity: integrityAlgorithm && generateIntegrityValue(integrityAlgorithm, code), integrity:
integrityAlgorithm && generateIntegrityValue(integrityAlgorithm, code),
map: !map map: !map
? undefined ? undefined
: { : {
filename: filename + '.map', filename: filename + '.map',
size: Buffer.byteLength(map), size: Buffer.byteLength(map)
}, }
}; };
} }
@ -352,7 +381,7 @@ function generateIntegrityValue(hashAlgorithm: string, code: string) {
// However, two variants are still needed due to lazy routing and SRI differences // However, two variants are still needed due to lazy routing and SRI differences
// NOTE: This should eventually be a babel plugin // NOTE: This should eventually be a babel plugin
async function processRuntime( async function processRuntime(
options: ProcessBundleOptions, options: ProcessBundleOptions
): Promise<Partial<ProcessBundleResult>> { ): Promise<Partial<ProcessBundleResult>> {
let originalCode = options.code; let originalCode = options.code;
let downlevelCode = options.code; let downlevelCode = options.code;
@ -365,10 +394,16 @@ async function processRuntime(
} }
if (data.original && data.original.integrity) { if (data.original && data.original.integrity) {
originalCode = originalCode.replace(data.integrity, data.original.integrity); originalCode = originalCode.replace(
data.integrity,
data.original.integrity
);
} }
if (data.downlevel && data.downlevel.integrity) { if (data.downlevel && data.downlevel.integrity) {
downlevelCode = downlevelCode.replace(data.integrity, data.downlevel.integrity); downlevelCode = downlevelCode.replace(
data.integrity,
data.downlevel.integrity
);
} }
} }
} }
@ -383,7 +418,7 @@ async function processRuntime(
if (options.optimize) { if (options.optimize) {
const minifiyResults = terserMangle(downlevelCode, { const minifiyResults = terserMangle(downlevelCode, {
filename: path.basename(downlevelFilePath), filename: path.basename(downlevelFilePath),
map: options.map === undefined ? undefined : JSON.parse(options.map), map: options.map === undefined ? undefined : JSON.parse(options.map)
}); });
downlevelCode = minifiyResults.code; downlevelCode = minifiyResults.code;
downlevelMap = JSON.stringify(minifiyResults.map); downlevelMap = JSON.stringify(minifiyResults.map);
@ -394,8 +429,8 @@ async function processRuntime(
downlevelFilePath, downlevelFilePath,
downlevelCode, downlevelCode,
downlevelMap, downlevelMap,
options.integrityAlgorithm, options.integrityAlgorithm
), )
}; };
} else { } else {
if (options.map) { if (options.map) {
@ -409,28 +444,30 @@ async function processRuntime(
options.filename, options.filename,
originalCode, originalCode,
options.map, options.map,
options.integrityAlgorithm, options.integrityAlgorithm
), ),
downlevel: createFileEntry( downlevel: createFileEntry(
downlevelFilePath, downlevelFilePath,
downlevelCode, downlevelCode,
downlevelMap, downlevelMap,
options.integrityAlgorithm, options.integrityAlgorithm
), )
}; };
} }
if (downlevelMap) { if (downlevelMap) {
await cachePut( await cachePut(
downlevelMap, downlevelMap,
(options.cacheKeys && options.cacheKeys[CacheKey.DownlevelMap]) || null, (options.cacheKeys && options.cacheKeys[CacheKey.DownlevelMap]) || null
); );
fs.writeFileSync(downlevelFilePath + '.map', downlevelMap); fs.writeFileSync(downlevelFilePath + '.map', downlevelMap);
downlevelCode += `\n//# sourceMappingURL=${path.basename(downlevelFilePath)}.map`; downlevelCode += `\n//# sourceMappingURL=${path.basename(
downlevelFilePath
)}.map`;
} }
await cachePut( await cachePut(
downlevelCode, downlevelCode,
(options.cacheKeys && options.cacheKeys[CacheKey.DownlevelCode]) || null, (options.cacheKeys && options.cacheKeys[CacheKey.DownlevelCode]) || null
); );
fs.writeFileSync(downlevelFilePath, downlevelCode); fs.writeFileSync(downlevelFilePath, downlevelCode);

View File

@ -11,27 +11,26 @@ import { resolve } from 'path';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
const treeKill = require('tree-kill'); const treeKill = require('tree-kill');
export function runModuleAsObservableFork( export function runModuleAsObservableFork(
cwd: string, cwd: string,
modulePath: string, modulePath: string,
exportName: string | undefined, exportName: string | undefined,
// tslint:disable-next-line:no-any // tslint:disable-next-line:no-any
args: any[], args: any[]
): Observable<BuilderOutput> { ): Observable<BuilderOutput> {
return new Observable(obs => { return new Observable(obs => {
const workerPath: string = resolve(__dirname, './run-module-worker.js'); const workerPath: string = resolve(__dirname, './run-module-worker.js');
const debugArgRegex = /--inspect(?:-brk|-port)?|--debug(?:-brk|-port)/; const debugArgRegex = /--inspect(?:-brk|-port)?|--debug(?:-brk|-port)/;
const execArgv = process.execArgv.filter((arg) => { const execArgv = process.execArgv.filter(arg => {
// Remove debug args. // Remove debug args.
// Workaround for https://github.com/nodejs/node/issues/9435 // Workaround for https://github.com/nodejs/node/issues/9435
return !debugArgRegex.test(arg); return !debugArgRegex.test(arg);
}); });
const forkOptions: ForkOptions = { const forkOptions: ForkOptions = ({
cwd, cwd,
execArgv, execArgv
} as {} as ForkOptions; } as {}) as ForkOptions;
// TODO: support passing in a logger to use as stdio streams // TODO: support passing in a logger to use as stdio streams
// if (logger) { // if (logger) {
@ -77,7 +76,7 @@ export function runModuleAsObservableFork(
hash: '5d4b9a5c0a4e0f9977598437b0e85bcc', hash: '5d4b9a5c0a4e0f9977598437b0e85bcc',
modulePath, modulePath,
exportName, exportName,
args, args
}); });
// Teardown logic. When unsubscribing, kill the forked process. // Teardown logic. When unsubscribing, kill the forked process.

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
process.on('message', (message) => { process.on('message', message => {
// Only process messages with the hash in 'run-module-as-observable-fork.ts'. // Only process messages with the hash in 'run-module-as-observable-fork.ts'.
if (message.hash === '5d4b9a5c0a4e0f9977598437b0e85bcc') { if (message.hash === '5d4b9a5c0a4e0f9977598437b0e85bcc') {
const requiredModule = require(message.modulePath); const requiredModule = require(message.modulePath);
@ -17,4 +17,3 @@ process.on('message', (message) => {
} }
} }
}); });

View File

@ -8,15 +8,24 @@
import { logging, tags } from '@angular-devkit/core'; import { logging, tags } from '@angular-devkit/core';
import { SemVer, gte, satisfies } from 'semver'; import { SemVer, gte, satisfies } from 'semver';
export function assertCompatibleAngularVersion(projectRoot: string, logger: logging.LoggerApi) { export function assertCompatibleAngularVersion(
projectRoot: string,
logger: logging.LoggerApi
) {
let angularCliPkgJson; let angularCliPkgJson;
let angularPkgJson; let angularPkgJson;
let rxjsPkgJson; let rxjsPkgJson;
const resolveOptions = { paths: [projectRoot] }; const resolveOptions = { paths: [projectRoot] };
try { try {
const angularPackagePath = require.resolve('@angular/core/package.json', resolveOptions); const angularPackagePath = require.resolve(
const rxjsPackagePath = require.resolve('rxjs/package.json', resolveOptions); '@angular/core/package.json',
resolveOptions
);
const rxjsPackagePath = require.resolve(
'rxjs/package.json',
resolveOptions
);
angularPkgJson = require(angularPackagePath); angularPkgJson = require(angularPackagePath);
rxjsPkgJson = require(rxjsPackagePath); rxjsPkgJson = require(rxjsPackagePath);
@ -28,7 +37,14 @@ export function assertCompatibleAngularVersion(projectRoot: string, logger: logg
process.exit(2); process.exit(2);
} }
if (!(angularPkgJson && angularPkgJson['version'] && rxjsPkgJson && rxjsPkgJson['version'])) { if (
!(
angularPkgJson &&
angularPkgJson['version'] &&
rxjsPkgJson &&
rxjsPkgJson['version']
)
) {
logger.error(tags.stripIndents` logger.error(tags.stripIndents`
Cannot determine versions of "@angular/core" and/or "rxjs". Cannot determine versions of "@angular/core" and/or "rxjs".
This likely means your local installation is broken. Please reinstall your packages. This likely means your local installation is broken. Please reinstall your packages.
@ -38,7 +54,10 @@ export function assertCompatibleAngularVersion(projectRoot: string, logger: logg
} }
try { try {
const angularCliPkgPath = require.resolve('@angular/cli/package.json', resolveOptions); const angularCliPkgPath = require.resolve(
'@angular/cli/package.json',
resolveOptions
);
angularCliPkgJson = require(angularCliPkgPath); angularCliPkgJson = require(angularCliPkgPath);
if (!(angularCliPkgJson && angularCliPkgJson['version'])) { if (!(angularCliPkgJson && angularCliPkgJson['version'])) {
throw new Error(); throw new Error();
@ -66,7 +85,11 @@ export function assertCompatibleAngularVersion(projectRoot: string, logger: logg
const angularVersion = new SemVer(angularPkgJson['version']); const angularVersion = new SemVer(angularPkgJson['version']);
const rxjsVersion = new SemVer(rxjsPkgJson['version']); const rxjsVersion = new SemVer(rxjsPkgJson['version']);
if (!satisfies(angularVersion, supportedAngularSemver, { includePrerelease: true })) { if (
!satisfies(angularVersion, supportedAngularSemver, {
includePrerelease: true
})
) {
logger.error( logger.error(
tags.stripIndents` tags.stripIndents`
This version of CLI is only compatible with Angular versions ${supportedAngularSemver}, This version of CLI is only compatible with Angular versions ${supportedAngularSemver},
@ -74,7 +97,7 @@ export function assertCompatibleAngularVersion(projectRoot: string, logger: logg
Please visit the link below to find instructions on how to update Angular. Please visit the link below to find instructions on how to update Angular.
https://angular-update-guide.firebaseapp.com/ https://angular-update-guide.firebaseapp.com/
` + '\n', ` + '\n'
); );
process.exit(3); process.exit(3);
@ -90,18 +113,21 @@ export function assertCompatibleAngularVersion(projectRoot: string, logger: logg
Please visit the link below to find instructions on how to update RxJs. Please visit the link below to find instructions on how to update RxJs.
https://docs.google.com/document/d/12nlLt71VLKb-z3YaSGzUfx6mJbc34nsMXtByPUN35cg/edit# https://docs.google.com/document/d/12nlLt71VLKb-z3YaSGzUfx6mJbc34nsMXtByPUN35cg/edit#
` + '\n', ` + '\n'
); );
process.exit(3); process.exit(3);
} else if (gte(angularVersion, '6.0.0-rc.0') && !gte(rxjsVersion, '6.0.0-beta.0')) { } else if (
gte(angularVersion, '6.0.0-rc.0') &&
!gte(rxjsVersion, '6.0.0-beta.0')
) {
logger.warn( logger.warn(
tags.stripIndents` tags.stripIndents`
This project uses a temporary compatibility version of RxJs (${rxjsVersion}). This project uses a temporary compatibility version of RxJs (${rxjsVersion}).
Please visit the link below to find instructions on how to update RxJs. Please visit the link below to find instructions on how to update RxJs.
https://docs.google.com/document/d/12nlLt71VLKb-z3YaSGzUfx6mJbc34nsMXtByPUN35cg/edit# https://docs.google.com/document/d/12nlLt71VLKb-z3YaSGzUfx6mJbc34nsMXtByPUN35cg/edit#
` + '\n', ` + '\n'
); );
} }
} }

View File

@ -13,7 +13,7 @@ import {
normalize, normalize,
resolve, resolve,
schema, schema,
virtualFs, virtualFs
} from '@angular-devkit/core'; } from '@angular-devkit/core';
import { NodeJsSyncHost } from '@angular-devkit/core/node'; import { NodeJsSyncHost } from '@angular-devkit/core/node';
import * as fs from 'fs'; import * as fs from 'fs';
@ -27,14 +27,16 @@ import {
NormalizedBrowserBuilderSchema, NormalizedBrowserBuilderSchema,
defaultProgress, defaultProgress,
fullDifferential, fullDifferential,
normalizeBrowserSchema, normalizeBrowserSchema
} from '../utils'; } from '../utils';
import { BuildBrowserFeatures } from './build-browser-features'; import { BuildBrowserFeatures } from './build-browser-features';
const SpeedMeasurePlugin = require('speed-measure-webpack-plugin'); const SpeedMeasurePlugin = require('speed-measure-webpack-plugin');
const webpackMerge = require('webpack-merge'); const webpackMerge = require('webpack-merge');
type BrowserWebpackConfigOptions = WebpackConfigOptions<NormalizedBrowserBuilderSchema>; type BrowserWebpackConfigOptions = WebpackConfigOptions<
NormalizedBrowserBuilderSchema
>;
export async function generateWebpackConfig( export async function generateWebpackConfig(
context: BuilderContext, context: BuilderContext,
@ -42,12 +44,16 @@ export async function generateWebpackConfig(
projectRoot: string, projectRoot: string,
sourceRoot: string | undefined, sourceRoot: string | undefined,
options: NormalizedBrowserBuilderSchema, options: NormalizedBrowserBuilderSchema,
webpackPartialGenerator: (wco: BrowserWebpackConfigOptions) => webpack.Configuration[], webpackPartialGenerator: (
logger: logging.LoggerApi, wco: BrowserWebpackConfigOptions
) => webpack.Configuration[],
logger: logging.LoggerApi
): Promise<webpack.Configuration[]> { ): Promise<webpack.Configuration[]> {
// Ensure Build Optimizer is only used with AOT. // Ensure Build Optimizer is only used with AOT.
if (options.buildOptimizer && !options.aot) { if (options.buildOptimizer && !options.aot) {
throw new Error(`The 'buildOptimizer' option cannot be used without 'aot'.`); throw new Error(
`The 'buildOptimizer' option cannot be used without 'aot'.`
);
} }
const tsConfigPath = path.resolve(workspaceRoot, options.tsConfig); const tsConfigPath = path.resolve(workspaceRoot, options.tsConfig);
@ -59,10 +65,14 @@ export async function generateWebpackConfig(
// At the moment, only the browser builder supports differential loading // At the moment, only the browser builder supports differential loading
// However this config generation is used by multiple builders such as dev-server // However this config generation is used by multiple builders such as dev-server
const scriptTarget = tsConfig.options.target || ts.ScriptTarget.ES5; const scriptTarget = tsConfig.options.target || ts.ScriptTarget.ES5;
const buildBrowserFeatures = new BuildBrowserFeatures(projectRoot, scriptTarget); const buildBrowserFeatures = new BuildBrowserFeatures(
const differentialLoading = context.builder.builderName === 'browser' projectRoot,
&& !options.watch scriptTarget
&& buildBrowserFeatures.isDifferentialLoadingNeeded(); );
const differentialLoading =
context.builder.builderName === 'browser' &&
!options.watch &&
buildBrowserFeatures.isDifferentialLoadingNeeded();
const scriptTargets = [scriptTarget]; const scriptTargets = [scriptTarget];
@ -73,29 +83,33 @@ export async function generateWebpackConfig(
// For differential loading, we can have several targets // For differential loading, we can have several targets
return scriptTargets.map(scriptTarget => { return scriptTargets.map(scriptTarget => {
let buildOptions: NormalizedBrowserBuilderSchema = { ...options }; let buildOptions: NormalizedBrowserBuilderSchema = { ...options };
const supportES2015 const supportES2015 =
= scriptTarget !== ts.ScriptTarget.ES3 && scriptTarget !== ts.ScriptTarget.ES5; scriptTarget !== ts.ScriptTarget.ES3 &&
scriptTarget !== ts.ScriptTarget.ES5;
if (differentialLoading && fullDifferential) { if (differentialLoading && fullDifferential) {
buildOptions = { buildOptions = {
...options, ...options,
...( ...// FIXME: we do create better webpack config composition to achieve the below
// FIXME: we do create better webpack config composition to achieve the below
// When DL is enabled and supportES2015 is true it means that we are on the second build // When DL is enabled and supportES2015 is true it means that we are on the second build
// This also means that we don't need to include styles and assets multiple times // This also means that we don't need to include styles and assets multiple times
supportES2015 (supportES2015
? {} ? {}
: { : {
styles: options.extractCss ? [] : options.styles, styles: options.extractCss ? [] : options.styles,
assets: [], assets: []
} }),
),
es5BrowserSupport: undefined, es5BrowserSupport: undefined,
esVersionInFileName: true, esVersionInFileName: true,
scriptTargetOverride: scriptTarget, scriptTargetOverride: scriptTarget
}; };
} else if (differentialLoading && !fullDifferential) { } else if (differentialLoading && !fullDifferential) {
buildOptions = { ...options, esVersionInFileName: true, scriptTargetOverride: ts.ScriptTarget.ES5, es5BrowserSupport: undefined }; buildOptions = {
...options,
esVersionInFileName: true,
scriptTargetOverride: ts.ScriptTarget.ES5,
es5BrowserSupport: undefined
};
} }
const wco: BrowserWebpackConfigOptions = { const wco: BrowserWebpackConfigOptions = {
@ -106,7 +120,7 @@ export async function generateWebpackConfig(
buildOptions, buildOptions,
tsConfig, tsConfig,
tsConfigPath, tsConfigPath,
supportES2015, supportES2015
}; };
wco.buildOptions.progress = defaultProgress(wco.buildOptions.progress); wco.buildOptions.progress = defaultProgress(wco.buildOptions.progress);
@ -121,21 +135,24 @@ export async function generateWebpackConfig(
if (!webpackConfig.resolve.alias) { if (!webpackConfig.resolve.alias) {
webpackConfig.resolve.alias = {}; webpackConfig.resolve.alias = {};
} }
webpackConfig.resolve.alias['zone.js/dist/zone'] = 'zone.js/dist/zone-evergreen'; webpackConfig.resolve.alias['zone.js/dist/zone'] =
'zone.js/dist/zone-evergreen';
} }
if (options.profile || process.env['NG_BUILD_PROFILING']) { if (options.profile || process.env['NG_BUILD_PROFILING']) {
const esVersionInFileName = getEsVersionForFileName( const esVersionInFileName = getEsVersionForFileName(
fullDifferential ? buildOptions.scriptTargetOverride : tsConfig.options.target, fullDifferential
wco.buildOptions.esVersionInFileName, ? buildOptions.scriptTargetOverride
: tsConfig.options.target,
wco.buildOptions.esVersionInFileName
); );
const smp = new SpeedMeasurePlugin({ const smp = new SpeedMeasurePlugin({
outputFormat: 'json', outputFormat: 'json',
outputTarget: path.resolve( outputTarget: path.resolve(
workspaceRoot, workspaceRoot,
`speed-measure-plugin${esVersionInFileName}.json`, `speed-measure-plugin${esVersionInFileName}.json`
), )
}); });
return smp.wrap(webpackConfig); return smp.wrap(webpackConfig);
@ -145,18 +162,22 @@ export async function generateWebpackConfig(
}); });
} }
export async function generateBrowserWebpackConfigFromWorkspace( export async function generateBrowserWebpackConfigFromWorkspace(
options: BrowserBuilderSchema, options: BrowserBuilderSchema,
context: BuilderContext, context: BuilderContext,
projectName: string, projectName: string,
workspace: experimental.workspace.Workspace, workspace: experimental.workspace.Workspace,
host: virtualFs.Host<fs.Stats>, host: virtualFs.Host<fs.Stats>,
webpackPartialGenerator: (wco: BrowserWebpackConfigOptions) => webpack.Configuration[], webpackPartialGenerator: (
logger: logging.LoggerApi, wco: BrowserWebpackConfigOptions
) => webpack.Configuration[],
logger: logging.LoggerApi
): Promise<webpack.Configuration[]> { ): Promise<webpack.Configuration[]> {
// TODO: Use a better interface for workspace access. // TODO: Use a better interface for workspace access.
const projectRoot = resolve(workspace.root, normalize(workspace.getProject(projectName).root)); const projectRoot = resolve(
workspace.root,
normalize(workspace.getProject(projectName).root)
);
const projectSourceRoot = workspace.getProject(projectName).sourceRoot; const projectSourceRoot = workspace.getProject(projectName).sourceRoot;
const sourceRoot = projectSourceRoot const sourceRoot = projectSourceRoot
? resolve(workspace.root, normalize(projectSourceRoot)) ? resolve(workspace.root, normalize(projectSourceRoot))
@ -167,7 +188,7 @@ export async function generateBrowserWebpackConfigFromWorkspace(
workspace.root, workspace.root,
projectRoot, projectRoot,
sourceRoot, sourceRoot,
options, options
); );
return generateWebpackConfig( return generateWebpackConfig(
@ -177,30 +198,38 @@ export async function generateBrowserWebpackConfigFromWorkspace(
sourceRoot && getSystemPath(sourceRoot), sourceRoot && getSystemPath(sourceRoot),
normalizedOptions, normalizedOptions,
webpackPartialGenerator, webpackPartialGenerator,
logger, logger
); );
} }
export async function generateBrowserWebpackConfigFromContext( export async function generateBrowserWebpackConfigFromContext(
options: BrowserBuilderSchema, options: BrowserBuilderSchema,
context: BuilderContext, context: BuilderContext,
webpackPartialGenerator: (wco: BrowserWebpackConfigOptions) => webpack.Configuration[], webpackPartialGenerator: (
host: virtualFs.Host<fs.Stats> = new NodeJsSyncHost(), wco: BrowserWebpackConfigOptions
): Promise<{ workspace: experimental.workspace.Workspace, config: webpack.Configuration[] }> { ) => webpack.Configuration[],
host: virtualFs.Host<fs.Stats> = new NodeJsSyncHost()
): Promise<{
workspace: experimental.workspace.Workspace;
config: webpack.Configuration[];
}> {
const registry = new schema.CoreSchemaRegistry(); const registry = new schema.CoreSchemaRegistry();
registry.addPostTransform(schema.transforms.addUndefinedDefaults); registry.addPostTransform(schema.transforms.addUndefinedDefaults);
const workspace = await experimental.workspace.Workspace.fromPath( const workspace = await experimental.workspace.Workspace.fromPath(
host, host,
normalize(context.workspaceRoot), normalize(context.workspaceRoot),
registry, registry
); );
const projectName = context.target ? context.target.project : workspace.getDefaultProjectName(); const projectName = context.target
? context.target.project
: workspace.getDefaultProjectName();
if (!projectName) { if (!projectName) {
throw new Error('Must either have a target from the context or a default project.'); throw new Error(
'Must either have a target from the context or a default project.'
);
} }
const config = await generateBrowserWebpackConfigFromWorkspace( const config = await generateBrowserWebpackConfigFromWorkspace(
@ -210,7 +239,7 @@ export async function generateBrowserWebpackConfigFromContext(
workspace, workspace,
host, host,
webpackPartialGenerator, webpackPartialGenerator,
context.logger, context.logger
); );
return { workspace, config }; return { workspace, config };

View File

@ -95,16 +95,14 @@ export function getBaseWebpackPartial(
} }
if (options.extractLicenses) { if (options.extractLicenses) {
extraPlugins.push( extraPlugins.push(new LicenseWebpackPlugin({
new LicenseWebpackPlugin({
stats: { stats: {
warnings: false, warnings: false,
errors: false, errors: false
}, },
perChunkOutput: false, perChunkOutput: false,
outputFilename: `3rdpartylicenses.txt`, outputFilename: `3rdpartylicenses.txt`
}) as any }) as any);
);
} }
// process asset entries // process asset entries