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) {
extraPlugins.push(
new LicenseWebpackPlugin({
extraPlugins.push((new LicenseWebpackPlugin({
stats: {
warnings: false,
errors: false,
errors: false
},
perChunkOutput: false,
outputFilename: `3rdpartylicenses.txt`,
}) as unknown as webpack.Plugin
);
outputFilename: `3rdpartylicenses.txt`
}) as unknown) as webpack.Plugin);
}
// process asset entries

View File

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

View File

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

View File

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

View File

@ -7,7 +7,7 @@
*/
import {
BuildOptimizerWebpackPlugin,
buildOptimizerLoaderPath,
buildOptimizerLoaderPath
} from '@angular-devkit/build-optimizer';
import { tags } from '@angular-devkit/core';
import * as CopyWebpackPlugin from 'copy-webpack-plugin';
@ -19,7 +19,7 @@ import {
ContextReplacementPlugin,
HashedModuleIdsPlugin,
compilation,
debug,
debug
} from 'webpack';
import { RawSource } from 'webpack-sources';
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 { findAllNodeModules, findUp } from '../../utilities/find-up';
import { WebpackConfigOptions } from '../build-options';
import { getEsVersionForFileName, getOutputHashFormat, normalizeExtraEntryPoints } from './utils';
import {
getEsVersionForFileName,
getOutputHashFormat,
normalizeExtraEntryPoints
} from './utils';
const ProgressPlugin = require('webpack/lib/ProgressPlugin');
const CircularDependencyPlugin = require('circular-dependency-plugin');
@ -43,11 +47,14 @@ const g: any = typeof global !== 'undefined' ? global : {};
// tslint:disable-next-line:no-big-function
export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
const { root, projectRoot, buildOptions, tsConfig } = wco;
const { styles: stylesOptimization, scripts: scriptsOptimization } = buildOptions.optimization;
const {
styles: stylesOptimization,
scripts: scriptsOptimization
} = buildOptions.optimization;
const {
styles: stylesSourceMap,
scripts: scriptsSourceMap,
vendor: vendorSourceMap,
vendor: vendorSourceMap
} = buildOptions.sourceMap;
const nodeModules = findUp('node_modules', projectRoot);
@ -60,8 +67,10 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
const entryPoints: { [key: string]: string[] } = {};
const targetInFileName = getEsVersionForFileName(
fullDifferential ? buildOptions.scriptTargetOverride : tsConfig.options.target,
buildOptions.esVersionInFileName,
fullDifferential
? buildOptions.scriptTargetOverride
: tsConfig.options.target,
buildOptions.esVersionInFileName
);
if (buildOptions.main) {
@ -72,15 +81,19 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
if (wco.buildOptions.platform !== 'server') {
const buildBrowserFeatures = new BuildBrowserFeatures(
projectRoot,
tsConfig.options.target || ScriptTarget.ES5,
tsConfig.options.target || ScriptTarget.ES5
);
differentialLoadingNeeded = buildBrowserFeatures.isDifferentialLoadingNeeded();
if ((buildOptions.scriptTargetOverride || tsConfig.options.target) === ScriptTarget.ES5) {
if (
(buildOptions.scriptTargetOverride || tsConfig.options.target) ===
ScriptTarget.ES5
) {
if (
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
// outside of webpack compilation because otherwise webpack will cause the
@ -88,7 +101,7 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
if (buildBrowserFeatures.isNoModulePolyfillNeeded()) {
const noModuleScript: ExtraEntryPoint = {
bundleName: 'polyfills-nomodule-es5',
input: path.join(__dirname, '..', 'safari-nomodule.js'),
input: path.join(__dirname, '..', 'safari-nomodule.js')
};
buildOptions.scripts = buildOptions.scripts
? [...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
// because they will be loaded exclusivly based on module and nomodule
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) {
// Add zone.js legacy support to the es5 polyfills
// 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 not performing a full differential build the JIT polyfills need to be added to ES5
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 (!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) {
entryPoints['polyfills'] = [
...(entryPoints['polyfills'] || []),
path.resolve(root, buildOptions.polyfills),
path.resolve(root, buildOptions.polyfills)
];
}
if (!buildOptions.aot) {
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']) {
extraPlugins.push(
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
const globalScriptsByBundleName = normalizeExtraEntryPoints(
buildOptions.scripts,
'scripts',
).reduce((prev: { bundleName: string; paths: string[]; inject: boolean }[], curr) => {
'scripts'
).reduce(
(
prev: { bundleName: string; paths: string[]; inject: boolean }[],
curr
) => {
const bundleName = curr.bundleName;
const resolvedPath = path.resolve(root, curr.input);
const existingEntry = prev.find(el => el.bundleName === bundleName);
@ -158,7 +188,7 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
if (existingEntry.inject && !curr.inject) {
// All entries have to be lazy for the bundle to be lazy.
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({
bundleName,
paths: [resolvedPath],
inject: curr.inject,
inject: curr.inject
});
}
return prev;
}, []);
},
[]
);
if (globalScriptsByBundleName.length > 0) {
// Add a new asset for each entry.
@ -187,22 +219,28 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
sourceMap: scriptsSourceMap,
filename: `${path.basename(bundleName)}${hash}.js`,
scripts: script.paths,
basePath: projectRoot,
}),
basePath: projectRoot
})
);
});
}
// process asset entries
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.
asset.input = path.resolve(root, asset.input).replace(/\\/g, '/');
asset.input = asset.input.endsWith('/') ? asset.input : asset.input + '/';
asset.output = asset.output.endsWith('/') ? asset.output : asset.output + '/';
asset.input = asset.input.endsWith('/')
? asset.input
: asset.input + '/';
asset.output = asset.output.endsWith('/')
? asset.output
: asset.output + '/';
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);
}
@ -213,16 +251,19 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
ignore: asset.ignore,
from: {
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(
copyWebpackPluginPatterns,
copyWebpackPluginOptions,
copyWebpackPluginOptions
);
extraPlugins.push(copyWebpackPluginInstance);
}
@ -234,8 +275,8 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
if (buildOptions.showCircularDependencies) {
extraPlugins.push(
new CircularDependencyPlugin({
exclude: /([\\\/]node_modules[\\\/])|(ngfactory\.js$)/,
}),
exclude: /([\\\/]node_modules[\\\/])|(ngfactory\.js$)/
})
);
}
@ -244,11 +285,15 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
new (class {
apply(compiler: Compiler) {
compiler.hooks.emit.tap('angular-cli-stats', compilation => {
const data = JSON.stringify(compilation.getStats().toJson('verbose'));
compilation.assets[`stats${targetInFileName}.json`] = new RawSource(data);
const data = JSON.stringify(
compilation.getStats().toJson('verbose')
);
compilation.assets[`stats${targetInFileName}.json`] = new RawSource(
data
);
});
}
})(),
})()
);
}
@ -261,9 +306,9 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
sourceMapUseRule = {
use: [
{
loader: 'source-map-loader',
},
],
loader: 'source-map-loader'
}
]
};
}
@ -274,9 +319,9 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
use: [
{
loader: buildOptimizerLoaderPath,
options: { sourceMap: scriptsSourceMap },
},
],
options: { sourceMap: scriptsSourceMap }
}
]
};
}
@ -293,7 +338,9 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
const rxjsPathMappingImport = wco.supportES2015
? 'rxjs/_esm2015/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);
} catch {}
@ -303,19 +350,20 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
new CleanCssWebpackPlugin({
sourceMap: stylesSourceMap,
// 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) {
let angularGlobalDefinitions = {
ngDevMode: false,
ngI18nClosureMode: false,
ngI18nClosureMode: false
};
// Try to load known global definitions from @angular/compiler-cli.
const GLOBAL_DEFS_FOR_TERSER = require('@angular/compiler-cli').GLOBAL_DEFS_FOR_TERSER;
const GLOBAL_DEFS_FOR_TERSER = require('@angular/compiler-cli')
.GLOBAL_DEFS_FOR_TERSER;
if (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) {
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: {
ecma: terserEcma,
comments: false,
webkit: true,
webkit: true
},
// 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
@ -350,7 +398,7 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
? {
ecma: terserEcma,
global_defs: angularGlobalDefinitions,
keep_fnames: true,
keep_fnames: true
}
: {
ecma: terserEcma,
@ -358,14 +406,15 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
// PURE comments work best with 3 passes.
// See https://github.com/webpack/webpack/issues/2899#issuecomment-317425926.
passes: buildOptions.buildOptimizer ? 3 : 1,
global_defs: angularGlobalDefinitions,
global_defs: angularGlobalDefinitions
},
// We also want to avoid mangling on server.
// Name mangling is handled within the browser builder
mangle:
!manglingDisabled &&
buildOptions.platform !== 'server' &&
(!differentialLoadingNeeded || (differentialLoadingNeeded && fullDifferential)),
(!differentialLoadingNeeded ||
(differentialLoadingNeeded && fullDifferential))
};
extraMinimizers.push(
@ -375,7 +424,7 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
cache: true,
chunkFilter: (chunk: compilation.Chunk) =>
!globalScriptsByBundleName.some(s => s.bundleName === chunk.name),
terserOptions,
terserOptions
}),
// Script bundles are fully optimized here in one step since they are never downleveled.
// They are shared between ES2015 & ES5 outputs so must support ES5.
@ -389,15 +438,15 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
...terserOptions,
compress: {
...terserOptions.compress,
ecma: 5,
ecma: 5
},
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 {
mode: scriptsOptimization || stylesOptimization ? 'production' : 'development',
mode:
scriptsOptimization || stylesOptimization ? 'production' : 'development',
devtool: false,
profile: buildOptions.statsJson,
resolve: {
extensions: ['.ts', '.tsx', '.mjs', '.js'],
symlinks: !buildOptions.preserveSymlinks,
modules: [wco.tsConfig.options.baseUrl || projectRoot, 'node_modules'],
alias,
alias
},
resolveLoader: {
modules: loaderNodeModules,
modules: loaderNodeModules
},
context: projectRoot,
entry: entryPoints,
@ -431,14 +481,14 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
futureEmitAssets: true,
path: path.resolve(root, buildOptions.outputPath as string),
publicPath: buildOptions.deployUrl,
filename: `[name]${targetInFileName}${hashFormat.chunk}.js`,
filename: `[name]${targetInFileName}${hashFormat.chunk}.js`
},
watch: buildOptions.watch,
watchOptions: {
poll: buildOptions.poll,
poll: buildOptions.poll
},
performance: {
hints: false,
hints: false
},
module: {
// 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)$/,
loader: 'file-loader',
options: {
name: `[name]${hashFormat.file}.[ext]`,
},
name: `[name]${hashFormat.file}.[ext]`
}
},
{
// Mark files inside `@angular/core` as using SystemJS style dynamic imports.
// Removing this will cause deprecation warnings to appear.
test: /[\/\\]@angular[\/\\]core[\/\\].+\.js$/,
parser: { system: true },
parser: { system: true }
},
{
test: /[\/\\]hot[\/\\]emitter\.js$/,
parser: { node: { events: true } },
parser: { node: { events: true } }
},
{
test: /[\/\\]webpack-dev-server[\/\\]client[\/\\]utils[\/\\]createSocketUrl\.js$/,
parser: { node: { querystring: true } },
parser: { node: { querystring: true } }
},
{
test: /\.js$/,
// Factory files are processed by BO in the rules added in typescript.ts.
exclude: /(ngfactory|ngstyle)\.js$/,
...buildOptimizerUseRule,
...buildOptimizerUseRule
},
{
test: /\.js$/,
exclude: /(ngfactory|ngstyle)\.js$/,
enforce: 'pre',
...sourceMapUseRule,
},
],
...sourceMapUseRule
}
]
},
optimization: {
noEmitOnErrors: true,
@ -485,15 +535,15 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
new HashedModuleIdsPlugin(),
// TODO: check with Mike what this feature needs.
new BundleBudgetPlugin({ budgets: buildOptions.budgets }),
...extraMinimizers,
],
...extraMinimizers
]
},
plugins: [
// Always replace the context for the System.import in angular/core to prevent warnings.
// https://github.com/angular/angular/issues/11580
// With VE the correct context is added in @ngtools/webpack, but Ivy doesn't need it at all.
new ContextReplacementPlugin(/\@angular(\\|\/)core(\\|\/)/),
...extraPlugins,
],
...extraPlugins
]
};
}

View File

@ -19,25 +19,31 @@ export function getServerConfig(wco: WebpackConfigOptions): Configuration {
if (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 = {
resolve: {
mainFields: [...(wco.supportES2015 ? ['es2015'] : []), 'main', 'module'],
mainFields: [...(wco.supportES2015 ? ['es2015'] : []), 'main', 'module']
},
target: 'node',
output: {
libraryTarget: 'commonjs',
libraryTarget: 'commonjs'
},
plugins: extraPlugins,
node: false,
node: false
};
if (wco.buildOptions.bundleDependencies == 'none') {
config.externals = [
/^@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
if (/^\.{0,2}\//.test(request) || isAbsolute(request)) {
return callback();
@ -50,7 +56,7 @@ export function getServerConfig(wco: WebpackConfigOptions): Configuration {
// Node couldn't find it, so it must be user-aliased
callback();
}
},
}
];
}

View File

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

View File

@ -12,7 +12,7 @@ import {
PostcssCliResources,
RawCssLoader,
RemoveHashPlugin,
SuppressExtractedTextChunksWebpackPlugin,
SuppressExtractedTextChunksWebpackPlugin
} from '../../plugins/webpack';
import { WebpackConfigOptions } from '../build-options';
import { getOutputHashFormat, normalizeExtraEntryPoints } from './utils';
@ -63,7 +63,7 @@ export function getStylesConfig(wco: WebpackConfigOptions) {
resolve(content);
});
});
},
}
}),
PostcssCliResources({
baseHref: buildOptions.baseHref,
@ -71,9 +71,9 @@ export function getStylesConfig(wco: WebpackConfigOptions) {
resourcesOutputPath: buildOptions.resourcesOutputPath,
loader,
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.length > 0
) {
buildOptions.stylePreprocessorOptions.includePaths.forEach((includePath: string) =>
includePaths.push(path.resolve(root, includePath)),
buildOptions.stylePreprocessorOptions.includePaths.forEach(
(includePath: string) =>
includePaths.push(path.resolve(root, includePath))
);
lessPathOptions = {
paths: includePaths,
paths: includePaths
};
}
@ -150,10 +151,10 @@ export function getStylesConfig(wco: WebpackConfigOptions) {
sourceMap: cssSourceMap,
// bootstrap-sass requires a minimum precision of 8
precision: 8,
includePaths,
},
},
],
includePaths
}
}
]
},
{
test: /\.less$/,
@ -163,10 +164,10 @@ export function getStylesConfig(wco: WebpackConfigOptions) {
options: {
sourceMap: cssSourceMap,
javascriptEnabled: true,
...lessPathOptions,
},
},
],
...lessPathOptions
}
}
]
},
{
test: /\.styl$/,
@ -175,11 +176,11 @@ export function getStylesConfig(wco: WebpackConfigOptions) {
loader: 'stylus-loader',
options: {
sourceMap: cssSourceMap,
paths: includePaths,
},
},
],
},
paths: includePaths
}
}
]
}
];
// load component css as raw strings
@ -193,17 +194,20 @@ export function getStylesConfig(wco: WebpackConfigOptions) {
options: {
ident: 'embedded',
plugins: postcssPluginCreator,
sourceMap: cssSourceMap
sourceMap:
cssSourceMap &&
// Never use component css sourcemap when style optimizations are on.
// 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
// 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
@ -214,7 +218,9 @@ export function getStylesConfig(wco: WebpackConfigOptions) {
include: globalStylePaths,
test,
use: [
buildOptions.extractCss ? MiniCssExtractPlugin.loader : 'style-loader',
buildOptions.extractCss
? MiniCssExtractPlugin.loader
: 'style-loader',
RawCssLoader,
{
loader: 'postcss-loader',
@ -222,15 +228,17 @@ export function getStylesConfig(wco: WebpackConfigOptions) {
ident: buildOptions.extractCss ? 'extracted' : 'embedded',
plugins: postcssPluginCreator,
sourceMap:
cssSourceMap && !buildOptions.extractCss && !buildOptions.sourceMap.hidden
cssSourceMap &&
!buildOptions.extractCss &&
!buildOptions.sourceMap.hidden
? '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
new MiniCssExtractPlugin({ filename: `[name]${hashFormat.extract}.css` }),
// suppress empty .js files in css only entry points
new SuppressExtractedTextChunksWebpackPlugin(),
new SuppressExtractedTextChunksWebpackPlugin()
);
}
return {
entry: entryPoints,
module: { rules },
plugins: extraPlugins,
plugins: extraPlugins
};
}

View File

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

View File

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

View File

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

View File

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

View File

@ -7,7 +7,11 @@
*/
import { Compiler, compilation } from 'webpack';
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';
interface Thresholds {
@ -26,7 +30,7 @@ export interface BundleBudgetPluginOptions {
}
export class BundleBudgetPlugin {
constructor(private options: BundleBudgetPluginOptions) { }
constructor(private options: BundleBudgetPluginOptions) {}
apply(compiler: Compiler): void {
const { budgets } = this.options;
@ -35,42 +39,70 @@ export class BundleBudgetPlugin {
return;
}
compiler.hooks.compilation.tap('BundleBudgetPlugin', (compilation: compilation.Compilation) => {
compilation.hooks.afterOptimizeChunkAssets.tap('BundleBudgetPlugin', () => {
compiler.hooks.compilation.tap(
'BundleBudgetPlugin',
(compilation: compilation.Compilation) => {
compilation.hooks.afterOptimizeChunkAssets.tap(
'BundleBudgetPlugin',
() => {
// In AOT compilations component styles get processed in child compilations.
// tslint:disable-next-line: no-any
const parentCompilation = (compilation.compiler as any).parentCompilation;
const parentCompilation = (compilation.compiler as any)
.parentCompilation;
if (!parentCompilation) {
return;
}
const filteredBudgets = budgets.filter(budget => budget.type === Type.AnyComponentStyle);
const filteredBudgets = budgets.filter(
budget => budget.type === Type.AnyComponentStyle
);
this.runChecks(filteredBudgets, compilation);
});
});
}
);
}
);
compiler.hooks.afterEmit.tap('BundleBudgetPlugin', (compilation: compilation.Compilation) => {
const filteredBudgets = budgets.filter(budget => budget.type !== Type.AnyComponentStyle);
compiler.hooks.afterEmit.tap(
'BundleBudgetPlugin',
(compilation: compilation.Compilation) => {
const filteredBudgets = budgets.filter(
budget => budget.type !== Type.AnyComponentStyle
);
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 > size.size) {
const sizeDifference = formatSize(threshold - size.size);
messages.push(`budgets, minimum exceeded for ${size.label}. `
+ `Budget ${formatSize(threshold)} was not reached by ${sizeDifference}.`);
messages.push(
`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 < size.size) {
const sizeDifference = formatSize(size.size - threshold);
messages.push(`budgets, maximum exceeded for ${size.label}. `
+ `Budget ${formatSize(threshold)} was exceeded by ${sizeDifference}.`);
messages.push(
`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 {
const thresholds: Thresholds = {};
if (budget.maximumWarning) {
thresholds.maximumWarning = calculateBytes(budget.maximumWarning, budget.baseline, 1);
thresholds.maximumWarning = calculateBytes(
budget.maximumWarning,
budget.baseline,
1
);
}
if (budget.maximumError) {
thresholds.maximumError = calculateBytes(budget.maximumError, budget.baseline, 1);
thresholds.maximumError = calculateBytes(
budget.maximumError,
budget.baseline,
1
);
}
if (budget.minimumWarning) {
thresholds.minimumWarning = calculateBytes(budget.minimumWarning, budget.baseline, -1);
thresholds.minimumWarning = calculateBytes(
budget.minimumWarning,
budget.baseline,
-1
);
}
if (budget.minimumError) {
thresholds.minimumError = calculateBytes(budget.minimumError, budget.baseline, -1);
thresholds.minimumError = calculateBytes(
budget.minimumError,
budget.baseline,
-1
);
}
if (budget.warning) {
thresholds.warningLow = calculateBytes(budget.warning, budget.baseline, -1);
thresholds.warningLow = calculateBytes(
budget.warning,
budget.baseline,
-1
);
}
if (budget.warning) {
thresholds.warningHigh = calculateBytes(budget.warning, budget.baseline, 1);
thresholds.warningHigh = calculateBytes(
budget.warning,
budget.baseline,
1
);
}
if (budget.error) {
@ -117,18 +173,50 @@ export class BundleBudgetPlugin {
.map(budget => ({
budget,
thresholds: this.calculate(budget),
sizes: calculateSizes(budget, compilation),
sizes: calculateSizes(budget, compilation)
}))
.forEach(budgetCheck => {
budgetCheck.sizes.forEach(size => {
this.checkMaximum(budgetCheck.thresholds.maximumWarning, size, compilation.warnings);
this.checkMaximum(budgetCheck.thresholds.maximumError, 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);
this.checkMaximum(
budgetCheck.thresholds.maximumWarning,
size,
compilation.warnings
);
this.checkMaximum(
budgetCheck.thresholds.maximumError,
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,
action: (
compilation: compilation.Compilation,
chunks: compilation.Chunk[],
) => Promise<void | void[]>,
chunks: compilation.Chunk[]
) => Promise<void | void[]>
) {
compiler.hooks.compilation.tap(
'cleancss-webpack-plugin',
(compilation: compilation.Compilation) => {
compilation.hooks.optimizeChunkAssets.tapPromise('cleancss-webpack-plugin', chunks =>
action(compilation, chunks),
compilation.hooks.optimizeChunkAssets.tapPromise(
'cleancss-webpack-plugin',
chunks => action(compilation, chunks)
);
},
}
);
}
@ -38,25 +39,27 @@ export class CleanCssWebpackPlugin {
this._options = {
sourceMap: false,
test: file => file.endsWith('.css'),
...options,
...options
};
}
apply(compiler: Compiler): void {
hook(compiler, (compilation: compilation.Compilation, chunks: compilation.Chunk[]) => {
hook(
compiler,
(compilation: compilation.Compilation, chunks: compilation.Chunk[]) => {
const cleancss = new CleanCSS({
compatibility: 'ie9',
level: {
2: {
skipProperties: [
'transition', // Fixes #12408
'font', // Fixes #9648
],
},
'font' // Fixes #9648
]
}
},
inline: false,
returnPromise: true,
sourceMap: this._options.sourceMap,
sourceMap: this._options.sourceMap
});
const files: string[] = [...compilation.additionalChunkAssets];
@ -99,7 +102,9 @@ export class CleanCssWebpackPlugin {
}
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;
}
@ -117,7 +122,7 @@ export class CleanCssWebpackPlugin {
// tslint:disable-next-line: no-any
output.sourceMap.toString() as any,
content,
map,
map
);
} else {
newSource = new RawSource(output.styles);
@ -127,6 +132,7 @@ export class CleanCssWebpackPlugin {
});
return Promise.all(actions);
});
}
);
}
}

View File

@ -11,7 +11,7 @@ import { RawSource } from 'webpack-sources';
import {
CrossOriginValue,
FileInfo,
augmentIndexHtml,
augmentIndexHtml
} from '../utilities/index-file/augment-index-html';
import { IndexHtmlTransform } from '../utilities/index-file/write-index-html';
import { stripBom } from '../utilities/strip-bom';
@ -29,9 +29,14 @@ export interface IndexHtmlWebpackPluginOptions {
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) => {
compilation.inputFileSystem.readFile(filename, (err: Error, data: Buffer) => {
compilation.inputFileSystem.readFile(
filename,
(err: Error, data: Buffer) => {
if (err) {
reject(err);
@ -39,7 +44,8 @@ function readFile(filename: string, compilation: compilation.Compilation): Promi
}
resolve(stripBom(data.toString()));
});
}
);
});
}
@ -54,12 +60,14 @@ export class IndexHtmlWebpackPlugin {
noModuleEntrypoints: [],
moduleEntrypoints: [],
sri: false,
...options,
...options
};
}
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
const inputContent = await readFile(this._options.input, compilation);
compilation.fileDependencies.add(this._options.input);
@ -70,12 +78,15 @@ export class IndexHtmlWebpackPlugin {
const moduleFiles: FileInfo[] = [];
for (const [entryName, entrypoint] of compilation.entrypoints) {
const entryFiles: FileInfo[] = ((entrypoint && entrypoint.getFiles()) || []).map(
const entryFiles: FileInfo[] = (
(entrypoint && entrypoint.getFiles()) ||
[]
).map(
(f: string): FileInfo => ({
name: entryName,
file: f,
extension: path.extname(f),
}),
extension: path.extname(f)
})
);
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({
input: this._options.input,
inputContent,
@ -99,7 +111,7 @@ export class IndexHtmlWebpackPlugin {
noModuleFiles,
loadOutputFile,
moduleFiles,
entrypoints: this._options.entrypoints,
entrypoints: this._options.entrypoints
});
if (this._options.postTransform) {
@ -108,6 +120,7 @@ export class IndexHtmlWebpackPlugin {
// Add to compilation assets
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');
export class NamedLazyChunksPlugin {
constructor() { }
constructor() {}
apply(compiler: Compiler): void {
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.
// 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
(_: any, dependency: any) => {
if (
// Check this dependency is from an `import()` statement.
dependency instanceof ImportDependency
&& dependency.block instanceof ImportDependenciesBlock
dependency instanceof ImportDependency &&
dependency.block instanceof ImportDependenciesBlock &&
// 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
// in webpack/lib/ContextModule.js
dependency.block.chunkName = Template.toPath(dependency.request);
}
});
}
);
});
}
}

View File

@ -13,7 +13,7 @@ import * as webpack from 'webpack';
function wrapUrl(url: string): string {
let wrappedUrl;
const hasSingleQuotes = url.indexOf('\'') >= 0;
const hasSingleQuotes = url.indexOf("'") >= 0;
if (hasSingleQuotes) {
wrappedUrl = `"${url}"`;
@ -36,7 +36,7 @@ export interface PostcssCliResourcesOptions {
async function resolve(
file: string,
base: string,
resolver: (file: string, base: string) => Promise<string>,
resolver: (file: string, base: string) => Promise<string>
): Promise<string> {
try {
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 {
deployUrl = '',
baseHref = '',
resourcesOutputPath = '',
rebaseRootRelative = false,
filename,
loader,
loader
} = options;
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 (/^((?:\w+:)?\/\/|data:|chrome:|#)/.test(inputUrl)) {
return inputUrl;
@ -90,7 +96,9 @@ export default postcss.plugin('postcss-cli-resources', (options: PostcssCliResou
outputUrl = `${deployUrl.replace(/\/$/, '')}${inputUrl}`;
} else if (baseHref.match(/:\/\//)) {
// If baseHref contains a scheme, include it as is.
outputUrl = baseHref.replace(/\/$/, '') + dedupeSlashes(`/${deployUrl}/${inputUrl}`);
outputUrl =
baseHref.replace(/\/$/, '') +
dedupeSlashes(`/${deployUrl}/${inputUrl}`);
} else {
// Join together base-href, deploy-url and the original URL.
outputUrl = dedupeSlashes(`/${baseHref}/${deployUrl}/${inputUrl}`);
@ -101,8 +109,11 @@ export default postcss.plugin('postcss-cli-resources', (options: PostcssCliResou
return outputUrl;
}
const { pathname, hash, search } = url.parse(inputUrl.replace(/\\/g, '/'));
const resolver = (file: string, base: string) => new Promise<string>((resolve, reject) => {
const { pathname, hash, search } = url.parse(
inputUrl.replace(/\\/g, '/')
);
const resolver = (file: string, base: string) =>
new Promise<string>((resolve, reject) => {
loader.resolve(base, decodeURI(file), (err, result) => {
if (err) {
reject(err);
@ -126,7 +137,7 @@ export default postcss.plugin('postcss-cli-resources', (options: PostcssCliResou
let outputPath = interpolateName(
{ resourcePath: result } as webpack.loader.LoaderContext,
filename,
{ content },
{ content }
);
if (resourcesOutputPath) {
@ -141,7 +152,10 @@ export default postcss.plugin('postcss-cli-resources', (options: PostcssCliResou
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);
}
@ -151,7 +165,7 @@ export default postcss.plugin('postcss-cli-resources', (options: PostcssCliResou
});
};
return (root) => {
return root => {
const urlDeclarations: Array<postcss.Declaration> = [];
root.walkDecls(decl => {
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>();
return Promise.all(urlDeclarations.map(async decl => {
return Promise.all(
urlDeclarations.map(async decl => {
const value = decl.value;
const urlRegex = /url\(\s*(?:"([^"]+)"|'([^']+)'|(.+?))\s*\)/g;
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
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
while (match = urlRegex.exec(value)) {
while ((match = urlRegex.exec(value))) {
const originalUrl = match[1] || match[2] || match[3];
let processedUrl;
try {
processedUrl = await process(originalUrl, context, resourceCache);
} catch (err) {
loader.emitError(decl.error(err.message, { word: originalUrl }).toString());
loader.emitError(
decl.error(err.message, { word: originalUrl }).toString()
);
continue;
}
@ -210,6 +228,8 @@ export default postcss.plugin('postcss-cli-resources', (options: PostcssCliResou
if (modified) {
decl.value = segments.join('');
}
}));
})
);
};
});
}
);

View File

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

View File

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

View File

@ -15,7 +15,9 @@ export interface SingleTestTransformLoaderOptions {
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
@ -46,12 +48,13 @@ export default function loader(this: loader.LoaderContext, source: string) {
const message = [
`The 'include' option requires that the 'main' file for tests include the line below:`,
`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));
}
const mockedRequireContext = '{ keys: () => ({ map: (_a) => { } }) };' + lineSeparator;
const mockedRequireContext =
'{ keys: () => ({ map: (_a) => { } }) };' + lineSeparator;
source = source.replace(regex, mockedRequireContext + targettedImports);
return source;

View File

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

View File

@ -7,15 +7,28 @@
*/
// 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 { ScriptsWebpackPlugin, ScriptsWebpackPluginOptions } 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 {
ScriptsWebpackPlugin,
ScriptsWebpackPluginOptions
} 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 {
default as PostcssCliResources,
PostcssCliResourcesOptions,
PostcssCliResourcesOptions
} from './postcss-cli-resources';
import { join } from 'path';

View File

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

View File

@ -67,16 +67,16 @@ describe('bundle-calculator', () => {
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);
});
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', -1)).toBe(1024 * 1024 * 0.8);
});
it ('supports whitespace', () => {
it('supports whitespace', () => {
expect(calculateBytes(' 5kb ')).toBe(5 * 1024);
expect(calculateBytes('0.25 MB')).toBe(0.25 * 1024 * 1024);
expect(calculateBytes(' 20 % ', ' 1 mb ')).toBe(1024 * 1024 * 1.2);

View File

@ -7,7 +7,11 @@
*/
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) => {
function _getPort(portNumber: number) {
if (portNumber > 65535) {
@ -16,7 +20,8 @@ export function checkPort(port: number, host: string, basePort = 49152): Promise
const server = net.createServer();
server.once('error', (err: Error & {code: string}) => {
server
.once('error', (err: Error & { code: string }) => {
if (err.code !== 'EADDRINUSE') {
reject(err);
} else if (port === 0) {
@ -24,7 +29,9 @@ export function checkPort(port: number, host: string, basePort = 49152): Promise
} else {
// If the port isn't available and we weren't looking for any port, throw error.
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';
// 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(
(files, pattern) => {
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;
},
[] 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
pattern = pattern.replace(/\\/g, '/');
relativePathToMain = relativePathToMain.replace(/\\/g, '/');
@ -55,7 +63,7 @@ function findMatchingTests(pattern: string, cwd: string, relativePathToMain: str
}
const files = glob.sync(pattern, {
cwd,
cwd
});
return files;

View File

@ -9,7 +9,11 @@ import { existsSync } from 'fs';
import * as path from 'path';
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)) {
names = [names];
}

View File

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

View File

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

View File

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

View File

@ -15,9 +15,12 @@ export function generateEntryPoints(appConfig: {
// Add all styles/scripts, except lazy-loaded ones.
const extraEntryPoints = (
extraEntryPoints: ExtraEntryPoint[],
defaultBundleName: string,
defaultBundleName: string
): string[] => {
const entryPoints = normalizeExtraEntryPoints(extraEntryPoints, defaultBundleName)
const entryPoints = normalizeExtraEntryPoints(
extraEntryPoints,
defaultBundleName
)
.filter(entry => entry.inject)
.map(entry => entry.bundleName);
@ -34,15 +37,21 @@ export function generateEntryPoints(appConfig: {
...extraEntryPoints(appConfig.styles, 'styles'),
...extraEntryPoints(appConfig.scripts, 'scripts'),
'vendor',
'main',
'main'
];
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) {
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;

View File

@ -16,7 +16,10 @@ import * as path from 'path';
* @param workspaceRoot - workspaceRoot root location when provided
* 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
? path.resolve(workspaceRoot, tsconfigPath)
: tsconfigPath;

View File

@ -13,16 +13,13 @@ import {
normalize,
relative,
tags,
virtualFs,
virtualFs
} from '@angular-devkit/core';
import {
Filesystem,
Generator,
} from '@angular/service-worker/config'; // tslint:disable-line:no-implicit-dependencies
import { Filesystem, Generator } from '@angular/service-worker/config'; // tslint:disable-line:no-implicit-dependencies
import * as crypto from 'crypto';
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[]> {
return this._recursiveList(this._resolve(path), []).catch(() => []);
@ -40,7 +37,8 @@ class CliFilesystem implements Filesystem {
}
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();
}
@ -75,19 +73,20 @@ export async function augmentAppWithServiceWorker(
appRoot: Path,
outputPath: Path,
baseHref: string,
ngswConfigPath?: string,
ngswConfigPath?: string
): Promise<void> {
const distPath = normalize(outputPath);
const systemProjectRoot = getSystemPath(projectRoot);
// Find the service worker package
const workerPath = normalize(
require.resolve('@angular/service-worker/ngsw-worker.js', { paths: [systemProjectRoot] }),
);
const swConfigPath = require.resolve(
'@angular/service-worker/config',
{ paths: [systemProjectRoot] },
require.resolve('@angular/service-worker/ngsw-worker.js', {
paths: [systemProjectRoot]
})
);
const swConfigPath = require.resolve('@angular/service-worker/config', {
paths: [systemProjectRoot]
});
// Determine the configuration file path
let configPath;
@ -102,22 +101,32 @@ export async function augmentAppWithServiceWorker(
if (!configExists) {
throw new Error(tags.oneLine`
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.
`);
}
// 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
const GeneratorConstructor = require(swConfigPath).Generator as typeof Generator;
const generator = new GeneratorConstructor(new CliFilesystem(host, outputPath), baseHref);
const GeneratorConstructor = require(swConfigPath)
.Generator as typeof Generator;
const generator = new GeneratorConstructor(
new CliFilesystem(host, outputPath),
baseHref
);
const output = await generator.process(config);
// Write the manifest
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
// NOTE: This is inefficient (kernel -> userspace -> kernel).
@ -130,7 +139,11 @@ export async function augmentAppWithServiceWorker(
if (await host.exists(safetyPath).toPromise()) {
const safetyCode = await host.read(safetyPath).toPromise();
await host.write(join(distPath, 'worker-basic.min.js'), safetyCode).toPromise();
await host.write(join(distPath, 'safety-worker.js'), safetyCode).toPromise();
await host
.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 * as path from 'path';
const { bold, green, red, reset, white, yellow } = terminal;
export function formatSize(size: number): string {
@ -21,7 +20,9 @@ export function formatSize(size: number): string {
const abbreviations = ['bytes', 'kB', 'MB', 'GB'];
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(
@ -34,7 +35,7 @@ export function generateBundleStats(
initial: boolean;
rendered?: boolean;
},
colors: boolean,
colors: boolean
): string {
const g = (x: string) => (colors ? bold(green(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}]`) : ''))
.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 {
const w = (x: string) => colors ? bold(white(x)) : x;
return `Date: ${w(new Date().toISOString())} - Hash: ${w(hash)} - Time: ${w('' + time)}ms`
export function generateBuildStats(
hash: string,
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) {
const colors = statsConfig.colors;
const rs = (x: string) => colors ? reset(x) : x;
const w = (x: string) => colors ? bold(white(x)) : x;
const rs = (x: string) => (colors ? reset(x) : x);
const w = (x: string) => (colors ? bold(white(x)) : x);
const changedChunksStats = json.chunks
.filter((chunk: any) => chunk.rendered)
.map((chunk: any) => {
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;
if (unchangedChunkNumber > 0) {
return '\n' + rs(tags.stripIndents`
return (
'\n' +
rs(tags.stripIndents`
Date: ${w(new Date().toISOString())} - Hash: ${w(json.hash)}
${unchangedChunkNumber} unchanged chunks
${changedChunksStats.join('\n')}
Time: ${w('' + json.time)}ms
`);
`)
);
} else {
return '\n' + rs(tags.stripIndents`
return (
'\n' +
rs(tags.stripIndents`
${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) {
const colors = statsConfig.colors;
const rs = (x: string) => colors ? reset(x) : x;
const y = (x: string) => colors ? bold(yellow(x)) : x;
const rs = (x: string) => (colors ? reset(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) {
const colors = statsConfig.colors;
const rs = (x: string) => colors ? reset(x) : x;
const r = (x: string) => colors ? bold(red(x)) : x;
const rs = (x: string) => (colors ? reset(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 fs from 'fs';
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 cacheDownlevelPath = findCacheDirectory({ name: 'angular-build-dl' });
@ -20,7 +24,11 @@ const packageVersion = require('../../package.json').version;
let copyFileWorkaround = false;
if (process.platform === 'darwin') {
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;
}
}
@ -44,7 +52,7 @@ export class BundleActionCache {
fs.copyFileSync(
typeof entry === 'string' ? entry : entry.path,
dest,
fs.constants.COPYFILE_EXCL,
fs.constants.COPYFILE_EXCL
);
if (process.platform !== 'win32') {
// 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
// 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
const cacheKeys = [];
@ -85,7 +94,8 @@ export class BundleActionCache {
// If sourcemaps are enabled, add original sourcemap as required
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
@ -94,14 +104,17 @@ export class BundleActionCache {
// If sourcemaps are enabled, add downlevel sourcemap as required
if (action.sourceMaps) {
cacheKeys[CacheKey.DownlevelMap] = baseCacheKey + SourceMapVendorPostfix + '|dl-map';
cacheKeys[CacheKey.DownlevelMap] =
baseCacheKey + SourceMapVendorPostfix + '|dl-map';
}
}
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
const cacheEntries = [];
for (const key of cacheKeys) {
@ -113,7 +126,7 @@ export class BundleActionCache {
cacheEntries.push({
path: entry.path,
size: entry.size,
integrity: entry.metadata && entry.metadata.integrity,
integrity: entry.metadata && entry.metadata.integrity
});
} else {
cacheEntries.push(null);
@ -123,8 +136,11 @@ export class BundleActionCache {
return cacheEntries;
}
async getCachedBundleResult(action: ProcessBundleOptions): Promise<ProcessBundleResult | null> {
const entries = action.cacheKeys && await this.getCacheEntries(action.cacheKeys);
async getCachedBundleResult(
action: ProcessBundleOptions
): Promise<ProcessBundleResult | null> {
const entries =
action.cacheKeys && (await this.getCacheEntries(action.cacheKeys));
if (!entries) {
return null;
}
@ -136,7 +152,7 @@ export class BundleActionCache {
result.original = {
filename: action.filename,
size: cacheEntry.size,
integrity: cacheEntry.integrity,
integrity: cacheEntry.integrity
};
BundleActionCache.copyEntryContent(cacheEntry, result.original.filename);
@ -145,10 +161,13 @@ export class BundleActionCache {
if (cacheEntry) {
result.original.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) {
// If the original wasn't processed (and therefore not cached), add info
@ -160,8 +179,8 @@ export class BundleActionCache {
? undefined
: {
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 = {
filename: action.filename.replace('es2015', 'es5'),
size: cacheEntry.size,
integrity: cacheEntry.integrity,
integrity: cacheEntry.integrity
};
BundleActionCache.copyEntryContent(cacheEntry, result.downlevel.filename);
@ -179,10 +198,13 @@ export class BundleActionCache {
if (cacheEntry) {
result.downlevel.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 * as os from 'os';
import * as path from 'path';
import { ProcessBundleOptions, ProcessBundleResult } from '../utils/process-bundle';
import {
ProcessBundleOptions,
ProcessBundleResult
} from '../utils/process-bundle';
import { BundleActionCache } from './action-cache';
let workerFile = require.resolve('../utils/process-bundle');
@ -25,13 +28,19 @@ export class BundleActionExecutor {
constructor(
private workerOptions: unknown,
integrityAlgorithm?: string,
private readonly sizeThreshold = 32 * 1024,
private readonly sizeThreshold = 32 * 1024
) {
this.cache = new BundleActionCache(integrityAlgorithm);
}
private static executeMethod<O>(worker: JestWorker, method: string, input: unknown): Promise<O> {
return ((worker as unknown) as Record<string, (i: unknown) => Promise<O>>)[method](input);
private static executeMethod<O>(
worker: JestWorker,
method: string,
input: unknown
): Promise<O> {
return ((worker as unknown) as Record<string, (i: unknown) => Promise<O>>)[
method
](input);
}
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
return (this.largeWorker = new JestWorker(workerFile, {
exposedMethods: ['process'],
setupArgs: [this.workerOptions],
setupArgs: [this.workerOptions]
}));
}
@ -58,16 +67,27 @@ export class BundleActionExecutor {
setupArgs: [this.workerOptions],
numWorkers: os.cpus().length < 2 ? 1 : 2,
// 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
if (action.code.length > this.sizeThreshold) {
return BundleActionExecutor.executeMethod<O>(this.ensureLarge(), method, action);
return BundleActionExecutor.executeMethod<O>(
this.ensureLarge(),
method,
action
);
} 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>) {
const executions = new Map<Promise<ProcessBundleResult>, Promise<ProcessBundleResult>>();
const executions = new Map<
Promise<ProcessBundleResult>,
Promise<ProcessBundleResult>
>();
for (const action of actions) {
const execution = this.process(action);
executions.set(
@ -96,7 +119,7 @@ export class BundleActionExecutor {
executions.delete(execution);
return result;
}),
})
);
}

View File

@ -5,12 +5,16 @@
* 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
*/
import { BuilderContext, BuilderOutput, createBuilder } from '@angular-devkit/architect';
import {
BuilderContext,
BuilderOutput,
createBuilder
} from '@angular-devkit/architect';
import {
BuildResult,
EmittedFiles,
WebpackLoggingCallback,
runWebpack,
runWebpack
} from '@angular-devkit/build-webpack';
import {
experimental,
@ -21,7 +25,7 @@ import {
normalize,
resolve,
tags,
virtualFs,
virtualFs
} from '@angular-devkit/core';
import { NodeJsSyncHost } from '@angular-devkit/core/node';
import { createHash } from 'crypto';
@ -30,7 +34,14 @@ import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
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 * as webpack from 'webpack';
// import { NgBuildAnalyticsPlugin } from '../../plugins/webpack/analytics';
@ -43,11 +54,11 @@ import {
getStatsConfig,
getStylesConfig,
getWorkerConfig,
normalizeExtraEntryPoints,
normalizeExtraEntryPoints
} from '../angular-cli-files/models/webpack-configs';
import {
IndexHtmlTransform,
writeIndexHtml,
writeIndexHtml
} from '../angular-cli-files/utilities/index-file/write-index-html';
import { readTsconfig } from '../angular-cli-files/utilities/read-tsconfig';
import { augmentAppWithServiceWorker } from '../angular-cli-files/utilities/service-worker';
@ -56,7 +67,7 @@ import {
generateBundleStats,
statsErrorsToString,
statsToString,
statsWarningsToString,
statsWarningsToString
} from '../angular-cli-files/utilities/stats';
import { ExecutionTransformer } from '../transforms';
import {
@ -64,18 +75,18 @@ import {
deleteOutputDir,
fullDifferential,
normalizeOptimization,
normalizeSourceMaps,
normalizeSourceMaps
} from '../utils';
import {
ProcessBundleFile,
ProcessBundleOptions,
ProcessBundleResult,
ProcessBundleResult
} from '../utils/process-bundle';
import { assertCompatibleAngularVersion } from '../utils/version';
import {
generateBrowserWebpackConfigFromContext,
getIndexInputFile,
getIndexOutputFile,
getIndexOutputFile
} from '../utils/webpack-browser-config';
import { BundleActionExecutor } from './action-executor';
import { Schema as BrowserBuilderSchema } from './schema';
@ -91,7 +102,7 @@ export type BrowserBuilderOutput = json.JsonObject &
export function createBrowserLoggingCallback(
verbose: boolean,
logger: logging.LoggerApi,
logger: logging.LoggerApi
): WebpackLoggingCallback {
return (stats, config) => {
// config.stats contains our own stats settings, added during buildWebpackConfig().
@ -114,8 +125,11 @@ export function createBrowserLoggingCallback(
export async function buildBrowserWebpackConfigFromContext(
options: BrowserBuilderSchema,
context: BuilderContext,
host: virtualFs.Host<fs.Stats> = new NodeJsSyncHost(),
): Promise<{ workspace: experimental.workspace.Workspace; config: webpack.Configuration[] }> {
host: virtualFs.Host<fs.Stats> = new NodeJsSyncHost()
): Promise<{
workspace: experimental.workspace.Workspace;
config: webpack.Configuration[];
}> {
return generateBrowserWebpackConfigFromContext(
options,
context,
@ -126,15 +140,15 @@ export async function buildBrowserWebpackConfigFromContext(
getStatsConfig(wco),
getAnalyticsConfig(wco, context),
getCompilerConfig(wco),
wco.buildOptions.webWorkerTsConfig ? getWorkerConfig(wco) : {},
wco.buildOptions.webWorkerTsConfig ? getWorkerConfig(wco) : {}
],
host,
host
);
}
function getAnalyticsConfig(
wco: WebpackConfigOptions,
context: BuilderContext,
context: BuilderContext
): webpack.Configuration {
if (context.analytics) {
// If there's analytics, add our plugin. Otherwise no need to slow down the build.
@ -142,12 +156,14 @@ function getAnalyticsConfig(
if (context.builder) {
// We already vetted that this is a "safe" package, otherwise the analytics would be noop.
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.
return {
plugins: [],
plugins: []
};
}
@ -166,9 +182,16 @@ async function initialize(
options: BrowserBuilderSchema,
context: BuilderContext,
host: virtualFs.Host<fs.Stats>,
webpackConfigurationTransform?: ExecutionTransformer<webpack.Configuration>,
): Promise<{ workspace: experimental.workspace.Workspace; config: webpack.Configuration[] }> {
const { config, workspace } = await buildBrowserWebpackConfigFromContext(options, context, host);
webpackConfigurationTransform?: ExecutionTransformer<webpack.Configuration>
): Promise<{
workspace: experimental.workspace.Workspace;
config: webpack.Configuration[];
}> {
const { config, workspace } = await buildBrowserWebpackConfigFromContext(
options,
context,
host
);
let transformedConfig;
if (webpackConfigurationTransform) {
@ -182,7 +205,7 @@ async function initialize(
await deleteOutputDir(
normalize(context.workspaceRoot),
normalize(options.outputPath),
host,
host
).toPromise();
}
@ -197,7 +220,7 @@ export function buildWebpackBrowser(
webpackConfiguration?: ExecutionTransformer<webpack.Configuration>;
logging?: WebpackLoggingCallback;
indexHtml?: IndexHtmlTransform;
} = {},
} = {}
) {
const host = new NodeJsSyncHost();
const root = normalize(context.workspaceRoot);
@ -205,7 +228,9 @@ export function buildWebpackBrowser(
// Check Angular version.
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
switchMap(({ workspace, config: configs }) => {
const projectName = context.target
@ -213,17 +238,22 @@ export function buildWebpackBrowser(
: workspace.getDefaultProjectName();
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(
workspace.root,
normalize(workspace.getProject(projectName).root),
normalize(workspace.getProject(projectName).root)
);
const tsConfig = readTsconfig(options.tsConfig, context.workspaceRoot);
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();
@ -251,14 +281,17 @@ export function buildWebpackBrowser(
transforms.logging ||
(useBundleDownleveling
? () => {}
: createBrowserLoggingCallback(!!options.verbose, context.logger)),
: createBrowserLoggingCallback(
!!options.verbose,
context.logger
))
});
} else {
return of();
}
},
{ success: true } as BuildResult,
1,
1
),
bufferCount(configs.length),
// tslint:disable-next-line: no-big-function
@ -270,10 +303,14 @@ export function buildWebpackBrowser(
// If it fails show any diagnostic messages and bail
const webpackStats = buildEvents[0].webpackStats;
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) {
context.logger.error(statsErrorsToString(webpackStats, { colors: true }));
context.logger.error(
statsErrorsToString(webpackStats, { colors: true })
);
}
return { success };
@ -284,14 +321,19 @@ export function buildWebpackBrowser(
const scriptsEntryPointName = normalizeExtraEntryPoints(
options.scripts || [],
'scripts',
'scripts'
).map(x => x.bundleName);
const [firstBuild, secondBuild] = buildEvents;
if (isDifferentialLoadingNeeded && (fullDifferential || options.watch)) {
if (
isDifferentialLoadingNeeded &&
(fullDifferential || options.watch)
) {
moduleFiles = firstBuild.emittedFiles || [];
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) {
@ -303,13 +345,17 @@ export function buildWebpackBrowser(
noModuleFiles = [];
// Common options for all bundle process actions
const sourceMapOptions = normalizeSourceMaps(options.sourceMap || false);
const sourceMapOptions = normalizeSourceMaps(
options.sourceMap || false
);
const actionOptions: Partial<ProcessBundleOptions> = {
optimize: normalizeOptimization(options.optimization).scripts,
sourceMaps: sourceMapOptions.scripts,
hiddenSourceMaps: sourceMapOptions.hidden,
vendorSourceMaps: sourceMapOptions.vendor,
integrityAlgorithm: options.subresourceIntegrity ? 'sha384' : undefined,
integrityAlgorithm: options.subresourceIntegrity
? 'sha384'
: undefined
};
const actions: ProcessBundleOptions[] = [];
@ -347,14 +393,20 @@ export function buildWebpackBrowser(
}
// If not optimizing then ES2015 polyfills do not need processing
// 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) {
continue;
}
// Retrieve the content/map for the file
// 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');
let map;
if (actionOptions.sourceMaps) {
@ -383,7 +435,7 @@ export function buildWebpackBrowser(
name: file.id!,
runtime: file.file.startsWith('runtime'),
ignoreOriginal: es5Polyfills,
optimizeOnly: es2015Polyfills,
optimizeOnly: es2015Polyfills
});
// ES2015 polyfills are only optimized; optimization check was performed above
@ -399,7 +451,9 @@ export function buildWebpackBrowser(
}
// 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 = [];
let processRuntimeAction: ProcessBundleOptions | undefined;
@ -416,11 +470,13 @@ export function buildWebpackBrowser(
const executor = new BundleActionExecutor(
{ cachePath: cacheDownlevelPath },
options.subresourceIntegrity ? 'sha384' : undefined,
options.subresourceIntegrity ? 'sha384' : undefined
);
try {
for await (const result of executor.processAll(processActions)) {
for await (const result of executor.processAll(
processActions
)) {
processResults.push(result);
}
} finally {
@ -431,32 +487,40 @@ export function buildWebpackBrowser(
if (processRuntimeAction) {
const runtimeOptions = {
...processRuntimeAction,
runtimeData: processResults,
runtimeData: processResults
};
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.');
type ArrayElement<A> = A extends ReadonlyArray<infer T> ? T : never;
type ArrayElement<A> = A extends ReadonlyArray<infer T>
? T
: never;
function generateBundleInfoStats(
id: string | number,
bundle: ProcessBundleFile,
chunk: ArrayElement<webpack.Stats.ToJsonOutput['chunks']> | undefined,
chunk:
| ArrayElement<webpack.Stats.ToJsonOutput['chunks']>
| undefined
): string {
return generateBundleStats(
{
id,
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,
entry: !!chunk && chunk.names.includes('runtime'),
initial: !!chunk && chunk.initial,
rendered: true,
rendered: true
},
true,
true
);
}
@ -468,14 +532,26 @@ export function buildWebpackBrowser(
const chunk =
webpackStats &&
webpackStats.chunks &&
webpackStats.chunks.find(c => result.name === c.id.toString());
webpackStats.chunks.find(
c => result.name === c.id.toString()
);
if (result.original) {
bundleInfoText +=
'\n' + generateBundleInfoStats(result.name, result.original, chunk);
'\n' +
generateBundleInfoStats(
result.name,
result.original,
chunk
);
}
if (result.downlevel) {
bundleInfoText +=
'\n' + generateBundleInfoStats(result.name, result.downlevel, chunk);
'\n' +
generateBundleInfoStats(
result.name,
result.downlevel,
chunk
);
}
}
@ -486,9 +562,14 @@ export function buildWebpackBrowser(
}
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 +=
'\n' + generateBundleStats({ ...chunk, size: asset && asset.size }, true);
'\n' +
generateBundleStats(
{ ...chunk, size: asset && asset.size },
true
);
}
}
@ -497,19 +578,25 @@ export function buildWebpackBrowser(
generateBuildStats(
(webpackStats && webpackStats.hash) || '<unknown>',
Date.now() - startTime,
true,
true
);
context.logger.info(bundleInfoText);
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) {
context.logger.error(statsErrorsToString(webpackStats, { colors: true }));
context.logger.error(
statsErrorsToString(webpackStats, { colors: true })
);
}
} else {
const { emittedFiles = [] } = firstBuild;
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) {
@ -517,7 +604,10 @@ export function buildWebpackBrowser(
host,
outputPath: resolve(
root,
join(normalize(options.outputPath), getIndexOutputFile(options)),
join(
normalize(options.outputPath),
getIndexOutputFile(options)
)
),
indexPath: join(root, getIndexInputFile(options)),
files,
@ -529,11 +619,13 @@ export function buildWebpackBrowser(
scripts: options.scripts,
styles: options.styles,
postTransform: transforms.indexHtml,
crossOrigin: options.crossOrigin,
crossOrigin: options.crossOrigin
})
.pipe(
map(() => ({ success: true })),
catchError(error => of({ success: false, error: mapErrorToMessage(error) })),
catchError(error =>
of({ success: false, error: mapErrorToMessage(error) })
)
)
.toPromise();
} else {
@ -552,11 +644,11 @@ export function buildWebpackBrowser(
projectRoot,
resolve(root, normalize(options.outputPath)),
options.baseHref || '/',
options.ngswConfigPath,
options.ngswConfigPath
).then(
() => ({ success: true }),
error => ({ success: false, error: mapErrorToMessage(error) }),
),
error => ({ success: false, error: mapErrorToMessage(error) })
)
);
} else {
return of(buildEvent);
@ -567,11 +659,14 @@ export function buildWebpackBrowser(
({
...event,
// If we use differential loading, both configs have the same outputs
outputPath: path.resolve(context.workspaceRoot, options.outputPath),
} as BrowserBuilderOutput),
),
outputPath: path.resolve(
context.workspaceRoot,
options.outputPath
)
} as BrowserBuilderOutput)
)
);
}),
})
);
}
@ -587,4 +682,6 @@ function mapErrorToMessage(error: unknown): string | 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
// CORRESPONDING JSON SCHEMA FILE, THEN RUN devkit-admin build (or bazel build ...).
@ -295,22 +294,22 @@ export interface Budget {
* The type of budget.
*/
export enum Type {
All = "all",
AllScript = "allScript",
Any = "any",
AnyComponentStyle = "anyComponentStyle",
AnyScript = "anyScript",
Bundle = "bundle",
Initial = "initial",
All = 'all',
AllScript = 'allScript',
Any = 'any',
AnyComponentStyle = 'anyComponentStyle',
AnyScript = 'anyScript',
Bundle = 'bundle',
Initial = 'initial'
}
/**
* Define the crossorigin attribute setting of elements that provide CORS support.
*/
export enum CrossOrigin {
Anonymous = "anonymous",
None = "none",
UseCredentials = "use-credentials",
Anonymous = 'anonymous',
None = 'none',
UseCredentials = 'use-credentials'
}
export interface FileReplacement {
@ -324,9 +323,9 @@ export interface FileReplacement {
* How to handle missing translations for i18n.
*/
export enum I18NMissingTranslation {
Error = "error",
Ignore = "ignore",
Warning = "warning",
Error = 'error',
Ignore = 'ignore',
Warning = 'warning'
}
/**
@ -368,10 +367,10 @@ export interface OptimizationClass {
* Define the output filename cache-busting hashing mode.
*/
export enum OutputHashing {
All = "all",
Bundles = "bundles",
Media = "media",
None = "none",
All = 'all',
Bundles = 'bundles',
Media = 'media',
None = 'none'
}
export type ExtraEntryPoint = ExtraEntryPointClass | string;

View File

@ -22,9 +22,11 @@ export class BuildBrowserFeatures {
constructor(
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;
}
@ -54,12 +56,11 @@ export class BuildBrowserFeatures {
return false;
}
const safariBrowsers = [
'safari 10.1',
'ios_saf 10.3',
];
const safariBrowsers = ['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
// a: feature is partially supported
// x: feature is prefixed
const criteria = [
'y',
'a',
];
const criteria = ['y', 'a'];
const data = feature(features[featureId]);
return !this._supportedBrowsers
.some(browser => {
return !this._supportedBrowsers.some(browser => {
const [agentId, version] = browser.split(' ');
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
// 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
*/
import { TestProjectHost } from '@angular-devkit/architect/testing';
import { getSystemPath, join } from '@angular-devkit/core';
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 workspaceRoot = join(
devkitRoot,
'tests/angular_devkit/build_angular/hello-world-app/');
'tests/angular_devkit/build_angular/hello-world-app/'
);
const host = new TestProjectHost(workspaceRoot);
@ -31,48 +31,48 @@ describe('BuildBrowserFeatures', () => {
describe('isDifferentialLoadingNeeded', () => {
it('should be true for for IE 9-11 and ES2015', () => {
host.writeMultipleFiles({
'browserslist': 'IE 9-11',
browserslist: 'IE 9-11'
});
const buildBrowserFeatures = new BuildBrowserFeatures(
workspaceRootSysPath,
ScriptTarget.ES2015,
ScriptTarget.ES2015
);
expect(buildBrowserFeatures.isDifferentialLoadingNeeded()).toBe(true);
});
it('should be false for Chrome and ES2015', () => {
host.writeMultipleFiles({
'browserslist': 'last 1 chrome version',
browserslist: 'last 1 chrome version'
});
const buildBrowserFeatures = new BuildBrowserFeatures(
workspaceRootSysPath,
ScriptTarget.ES2015,
ScriptTarget.ES2015
);
expect(buildBrowserFeatures.isDifferentialLoadingNeeded()).toBe(false);
});
it('detects no need for differential loading for target is ES5', () => {
host.writeMultipleFiles({
'browserslist': 'last 1 chrome version',
browserslist: 'last 1 chrome version'
});
const buildBrowserFeatures = new BuildBrowserFeatures(
workspaceRootSysPath,
ScriptTarget.ES5,
ScriptTarget.ES5
);
expect(buildBrowserFeatures.isDifferentialLoadingNeeded()).toBe(false);
});
it('should be false for Safari 10.1 when target is ES2015', () => {
host.writeMultipleFiles({
'browserslist': 'Safari 10.1',
browserslist: 'Safari 10.1'
});
const buildBrowserFeatures = new BuildBrowserFeatures(
workspaceRootSysPath,
ScriptTarget.ES2015,
ScriptTarget.ES2015
);
expect(buildBrowserFeatures.isDifferentialLoadingNeeded()).toBe(false);
});
@ -81,48 +81,48 @@ describe('BuildBrowserFeatures', () => {
describe('isFeatureSupported', () => {
it('should be true for es6-module and Safari 10.1', () => {
host.writeMultipleFiles({
'browserslist': 'Safari 10.1',
browserslist: 'Safari 10.1'
});
const buildBrowserFeatures = new BuildBrowserFeatures(
workspaceRootSysPath,
ScriptTarget.ES2015,
ScriptTarget.ES2015
);
expect(buildBrowserFeatures.isFeatureSupported('es6-module')).toBe(true);
});
it('should be false for es6-module and IE9', () => {
host.writeMultipleFiles({
'browserslist': 'IE 9',
browserslist: 'IE 9'
});
const buildBrowserFeatures = new BuildBrowserFeatures(
workspaceRootSysPath,
ScriptTarget.ES2015,
ScriptTarget.ES2015
);
expect(buildBrowserFeatures.isFeatureSupported('es6-module')).toBe(false);
});
it('should be true for es6-module and last 1 chrome version', () => {
host.writeMultipleFiles({
'browserslist': 'last 1 chrome version',
browserslist: 'last 1 chrome version'
});
const buildBrowserFeatures = new BuildBrowserFeatures(
workspaceRootSysPath,
ScriptTarget.ES2015,
ScriptTarget.ES2015
);
expect(buildBrowserFeatures.isFeatureSupported('es6-module')).toBe(true);
});
it('should be true for es6-module and Edge 18', () => {
host.writeMultipleFiles({
'browserslist': 'Edge 18',
browserslist: 'Edge 18'
});
const buildBrowserFeatures = new BuildBrowserFeatures(
workspaceRootSysPath,
ScriptTarget.ES2015,
ScriptTarget.ES2015
);
expect(buildBrowserFeatures.isFeatureSupported('es6-module')).toBe(true);
});
@ -131,63 +131,63 @@ describe('BuildBrowserFeatures', () => {
describe('isNoModulePolyfillNeeded', () => {
it('should be false for Safari 10.1 when target is ES5', () => {
host.writeMultipleFiles({
'browserslist': 'Safari 10.1',
browserslist: 'Safari 10.1'
});
const buildBrowserFeatures = new BuildBrowserFeatures(
workspaceRootSysPath,
ScriptTarget.ES5,
ScriptTarget.ES5
);
expect(buildBrowserFeatures.isNoModulePolyfillNeeded()).toBe(false);
});
it('should be false for Safari 10.1 when target is ES2015', () => {
host.writeMultipleFiles({
'browserslist': 'Safari 10.1',
browserslist: 'Safari 10.1'
});
const buildBrowserFeatures = new BuildBrowserFeatures(
workspaceRootSysPath,
ScriptTarget.ES2015,
ScriptTarget.ES2015
);
expect(buildBrowserFeatures.isNoModulePolyfillNeeded()).toBe(false);
});
it('should be true for Safari 9+ when target is ES2015', () => {
host.writeMultipleFiles({
'browserslist': 'Safari >= 9',
browserslist: 'Safari >= 9'
});
const buildBrowserFeatures = new BuildBrowserFeatures(
workspaceRootSysPath,
ScriptTarget.ES2015,
ScriptTarget.ES2015
);
expect(buildBrowserFeatures.isNoModulePolyfillNeeded()).toBe(true);
});
it('should be false for Safari 9+ when target is ES5', () => {
host.writeMultipleFiles({
'browserslist': 'Safari >= 9',
browserslist: 'Safari >= 9'
});
const buildBrowserFeatures = new BuildBrowserFeatures(
workspaceRootSysPath,
ScriptTarget.ES5,
ScriptTarget.ES5
);
expect(buildBrowserFeatures.isNoModulePolyfillNeeded()).toBe(false);
});
it('should be false when not supporting Safari 10.1 target is ES2015', () => {
host.writeMultipleFiles({
'browserslist': `
browserslist: `
Edge 18
IE 9
`,
`
});
const buildBrowserFeatures = new BuildBrowserFeatures(
workspaceRootSysPath,
ScriptTarget.ES2015,
ScriptTarget.ES2015
);
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.
*/
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);
if (resolvedOutputPath === root) {
throw new Error('Output path MUST not be project root directory!');
}
return host.exists(resolvedOutputPath).pipe(
concatMap(exists => exists ? host.delete(resolvedOutputPath) : EMPTY),
last(null, null),
concatMap(exists => (exists ? host.delete(resolvedOutputPath) : EMPTY)),
last(null, null)
);
}

View File

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

View File

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

View File

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

View File

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

View File

@ -9,10 +9,12 @@
import { OptimizationClass, OptimizationUnion } from '../browser/schema';
export function normalizeOptimization(
optimization: OptimizationUnion = false,
optimization: OptimizationUnion = false
): Required<OptimizationClass> {
return {
scripts: typeof optimization === 'object' ? !!optimization.scripts : optimization,
styles: typeof optimization === 'object' ? !!optimization.styles : optimization,
scripts:
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 {
const scripts = typeof sourceMap === 'object' ? sourceMap.scripts : sourceMap;
const styles = typeof sourceMap === 'object' ? sourceMap.styles : sourceMap;
const hidden = typeof sourceMap === 'object' && sourceMap.hidden || false;
const vendor = typeof sourceMap === 'object' && sourceMap.vendor || false;
const hidden = (typeof sourceMap === 'object' && sourceMap.hidden) || false;
const vendor = (typeof sourceMap === 'object' && sourceMap.vendor) || false;
return {
vendor,
hidden,
scripts,
styles,
styles
};
}

View File

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

View File

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

View File

@ -6,7 +6,7 @@
* 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'.
if (message.hash === '5d4b9a5c0a4e0f9977598437b0e85bcc') {
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 { SemVer, gte, satisfies } from 'semver';
export function assertCompatibleAngularVersion(projectRoot: string, logger: logging.LoggerApi) {
export function assertCompatibleAngularVersion(
projectRoot: string,
logger: logging.LoggerApi
) {
let angularCliPkgJson;
let angularPkgJson;
let rxjsPkgJson;
const resolveOptions = { paths: [projectRoot] };
try {
const angularPackagePath = require.resolve('@angular/core/package.json', resolveOptions);
const rxjsPackagePath = require.resolve('rxjs/package.json', resolveOptions);
const angularPackagePath = require.resolve(
'@angular/core/package.json',
resolveOptions
);
const rxjsPackagePath = require.resolve(
'rxjs/package.json',
resolveOptions
);
angularPkgJson = require(angularPackagePath);
rxjsPkgJson = require(rxjsPackagePath);
@ -28,7 +37,14 @@ export function assertCompatibleAngularVersion(projectRoot: string, logger: logg
process.exit(2);
}
if (!(angularPkgJson && angularPkgJson['version'] && rxjsPkgJson && rxjsPkgJson['version'])) {
if (
!(
angularPkgJson &&
angularPkgJson['version'] &&
rxjsPkgJson &&
rxjsPkgJson['version']
)
) {
logger.error(tags.stripIndents`
Cannot determine versions of "@angular/core" and/or "rxjs".
This likely means your local installation is broken. Please reinstall your packages.
@ -38,7 +54,10 @@ export function assertCompatibleAngularVersion(projectRoot: string, logger: logg
}
try {
const angularCliPkgPath = require.resolve('@angular/cli/package.json', resolveOptions);
const angularCliPkgPath = require.resolve(
'@angular/cli/package.json',
resolveOptions
);
angularCliPkgJson = require(angularCliPkgPath);
if (!(angularCliPkgJson && angularCliPkgJson['version'])) {
throw new Error();
@ -66,7 +85,11 @@ export function assertCompatibleAngularVersion(projectRoot: string, logger: logg
const angularVersion = new SemVer(angularPkgJson['version']);
const rxjsVersion = new SemVer(rxjsPkgJson['version']);
if (!satisfies(angularVersion, supportedAngularSemver, { includePrerelease: true })) {
if (
!satisfies(angularVersion, supportedAngularSemver, {
includePrerelease: true
})
) {
logger.error(
tags.stripIndents`
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.
https://angular-update-guide.firebaseapp.com/
` + '\n',
` + '\n'
);
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.
https://docs.google.com/document/d/12nlLt71VLKb-z3YaSGzUfx6mJbc34nsMXtByPUN35cg/edit#
` + '\n',
` + '\n'
);
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(
tags.stripIndents`
This project uses a temporary compatibility version of RxJs (${rxjsVersion}).
Please visit the link below to find instructions on how to update RxJs.
https://docs.google.com/document/d/12nlLt71VLKb-z3YaSGzUfx6mJbc34nsMXtByPUN35cg/edit#
` + '\n',
` + '\n'
);
}
}

View File

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

View File

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