chore(nx): format new files
This commit is contained in:
parent
c2ed286d5a
commit
a24c31dd20
@ -81,16 +81,14 @@ export function getBaseWebpackPartial(
|
||||
}
|
||||
|
||||
if (options.extractLicenses) {
|
||||
extraPlugins.push(
|
||||
new LicenseWebpackPlugin({
|
||||
stats: {
|
||||
warnings: false,
|
||||
errors: false,
|
||||
},
|
||||
perChunkOutput: false,
|
||||
outputFilename: `3rdpartylicenses.txt`,
|
||||
}) as unknown as webpack.Plugin
|
||||
);
|
||||
extraPlugins.push((new LicenseWebpackPlugin({
|
||||
stats: {
|
||||
warnings: false,
|
||||
errors: false
|
||||
},
|
||||
perChunkOutput: false,
|
||||
outputFilename: `3rdpartylicenses.txt`
|
||||
}) as unknown) as webpack.Plugin);
|
||||
}
|
||||
|
||||
// process asset entries
|
||||
|
||||
@ -16,7 +16,7 @@ import {
|
||||
Budget,
|
||||
ExtraEntryPoint,
|
||||
OptimizationClass,
|
||||
SourceMapClass,
|
||||
SourceMapClass
|
||||
} from '../../browser/schema';
|
||||
import { NormalizedFileReplacement } from '../../utils/normalize-file-replacements';
|
||||
|
||||
|
||||
@ -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) {
|
||||
if (e.target === check) {
|
||||
support = true;
|
||||
} else if (!e.target.hasAttribute('nomodule') || !support) {
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
}, true);
|
||||
document.addEventListener(
|
||||
'beforeload',
|
||||
function(e) {
|
||||
if (e.target === check) {
|
||||
support = true;
|
||||
} else if (!e.target.hasAttribute('nomodule') || !support) {
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
},
|
||||
true
|
||||
);
|
||||
|
||||
check.type = 'module';
|
||||
check.src = '.';
|
||||
document.head.appendChild(check);
|
||||
check.remove();
|
||||
}
|
||||
}());
|
||||
})();
|
||||
|
||||
@ -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({
|
||||
stats: {
|
||||
warnings: false,
|
||||
errors: false,
|
||||
},
|
||||
perChunkOutput: false,
|
||||
outputFilename: `3rdpartylicenses.txt`,
|
||||
}));
|
||||
extraPlugins.push(
|
||||
new LicenseWebpackPlugin({
|
||||
stats: {
|
||||
warnings: false,
|
||||
errors: false
|
||||
},
|
||||
perChunkOutput: false,
|
||||
outputFilename: `3rdpartylicenses.txt`
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if (!isEval && (scriptsSourceMap || stylesSourceMap)) {
|
||||
extraPlugins.push(getSourceMapDevTool(
|
||||
!!scriptsSourceMap,
|
||||
!!stylesSourceMap,
|
||||
hiddenSourceMap,
|
||||
));
|
||||
extraPlugins.push(
|
||||
getSourceMapDevTool(
|
||||
!!scriptsSourceMap,
|
||||
!!stylesSourceMap,
|
||||
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
|
||||
};
|
||||
}
|
||||
|
||||
@ -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,30 +175,36 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
|
||||
// process global scripts
|
||||
const globalScriptsByBundleName = normalizeExtraEntryPoints(
|
||||
buildOptions.scripts,
|
||||
'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);
|
||||
if (existingEntry) {
|
||||
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.`,
|
||||
);
|
||||
'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);
|
||||
if (existingEntry) {
|
||||
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.`
|
||||
);
|
||||
}
|
||||
|
||||
existingEntry.paths.push(resolvedPath);
|
||||
} else {
|
||||
prev.push({
|
||||
bundleName,
|
||||
paths: [resolvedPath],
|
||||
inject: curr.inject
|
||||
});
|
||||
}
|
||||
|
||||
existingEntry.paths.push(resolvedPath);
|
||||
} else {
|
||||
prev.push({
|
||||
bundleName,
|
||||
paths: [resolvedPath],
|
||||
inject: curr.inject,
|
||||
});
|
||||
}
|
||||
|
||||
return prev;
|
||||
}, []);
|
||||
return prev;
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
if (globalScriptsByBundleName.length > 0) {
|
||||
// Add a new asset for each entry.
|
||||
@ -187,42 +219,51 @@ 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) => {
|
||||
// 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 + '/';
|
||||
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 + '/';
|
||||
|
||||
if (asset.output.startsWith('..')) {
|
||||
const message = 'An asset cannot be written to a location outside of the output path.';
|
||||
throw new Error(message);
|
||||
if (asset.output.startsWith('..')) {
|
||||
const message =
|
||||
'An asset cannot be written to a location outside of the output path.';
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
return {
|
||||
context: asset.input,
|
||||
// Now we remove starting slash to make Webpack place it from the output root.
|
||||
to: asset.output.replace(/^\//, ''),
|
||||
ignore: asset.ignore,
|
||||
from: {
|
||||
glob: asset.glob,
|
||||
dot: true
|
||||
}
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
context: asset.input,
|
||||
// Now we remove starting slash to make Webpack place it from the output root.
|
||||
to: asset.output.replace(/^\//, ''),
|
||||
ignore: asset.ignore,
|
||||
from: {
|
||||
glob: asset.glob,
|
||||
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
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
},
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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
|
||||
};
|
||||
}
|
||||
|
||||
@ -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))
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -61,20 +61,18 @@ function _createAotPlugin(
|
||||
|
||||
const i18nFileAndFormat = i18nExtract
|
||||
? {
|
||||
i18nOutFile: buildOptions.i18nFile,
|
||||
i18nOutFormat: buildOptions.i18nFormat,
|
||||
} : {
|
||||
i18nInFile: i18nInFile,
|
||||
i18nInFormat: buildOptions.i18nFormat,
|
||||
};
|
||||
i18nOutFile: buildOptions.i18nFile,
|
||||
i18nOutFormat: buildOptions.i18nFormat
|
||||
}
|
||||
: {
|
||||
i18nInFile: i18nInFile,
|
||||
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);
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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({
|
||||
globalObject: false,
|
||||
plugins: [getTypescriptWorkerPlugin(wco, workerTsConfigPath)],
|
||||
})],
|
||||
plugins: [
|
||||
new WorkerPlugin({
|
||||
globalObject: false,
|
||||
plugins: [getTypescriptWorkerPlugin(wco, workerTsConfigPath)]
|
||||
})
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
@ -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', () => {
|
||||
// In AOT compilations component styles get processed in child compilations.
|
||||
// tslint:disable-next-line: no-any
|
||||
const parentCompilation = (compilation.compiler as any).parentCompilation;
|
||||
if (!parentCompilation) {
|
||||
return;
|
||||
}
|
||||
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;
|
||||
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
|
||||
);
|
||||
this.runChecks(filteredBudgets, compilation);
|
||||
});
|
||||
});
|
||||
|
||||
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
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -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,95 +39,100 @@ 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[]) => {
|
||||
const cleancss = new CleanCSS({
|
||||
compatibility: 'ie9',
|
||||
level: {
|
||||
2: {
|
||||
skipProperties: [
|
||||
'transition', // Fixes #12408
|
||||
'font', // Fixes #9648
|
||||
],
|
||||
hook(
|
||||
compiler,
|
||||
(compilation: compilation.Compilation, chunks: compilation.Chunk[]) => {
|
||||
const cleancss = new CleanCSS({
|
||||
compatibility: 'ie9',
|
||||
level: {
|
||||
2: {
|
||||
skipProperties: [
|
||||
'transition', // Fixes #12408
|
||||
'font' // Fixes #9648
|
||||
]
|
||||
}
|
||||
},
|
||||
},
|
||||
inline: false,
|
||||
returnPromise: true,
|
||||
sourceMap: this._options.sourceMap,
|
||||
});
|
||||
|
||||
const files: string[] = [...compilation.additionalChunkAssets];
|
||||
|
||||
chunks.forEach(chunk => {
|
||||
if (chunk.files && chunk.files.length > 0) {
|
||||
files.push(...chunk.files);
|
||||
}
|
||||
});
|
||||
|
||||
const actions = files
|
||||
.filter(file => this._options.test(file))
|
||||
.map(async file => {
|
||||
const asset = compilation.assets[file] as Source;
|
||||
if (!asset) {
|
||||
return;
|
||||
}
|
||||
|
||||
let content: string;
|
||||
// tslint:disable-next-line: no-any
|
||||
let map: any;
|
||||
if (this._options.sourceMap && asset.sourceAndMap) {
|
||||
const sourceAndMap = asset.sourceAndMap();
|
||||
content = sourceAndMap.source;
|
||||
map = sourceAndMap.map;
|
||||
} else {
|
||||
content = asset.source();
|
||||
}
|
||||
|
||||
if (content.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const output = await cleancss.minify(content, map);
|
||||
|
||||
let hasWarnings = false;
|
||||
if (output.warnings && output.warnings.length > 0) {
|
||||
compilation.warnings.push(...output.warnings);
|
||||
hasWarnings = true;
|
||||
}
|
||||
|
||||
if (output.errors && output.errors.length > 0) {
|
||||
output.errors.forEach((error: string) => compilation.errors.push(new Error(error)));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// generally means invalid syntax so bail
|
||||
if (hasWarnings && output.stats.minifiedSize === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let newSource;
|
||||
if (output.sourceMap) {
|
||||
newSource = new SourceMapSource(
|
||||
output.styles,
|
||||
file,
|
||||
// tslint:disable-next-line: no-any
|
||||
output.sourceMap.toString() as any,
|
||||
content,
|
||||
map,
|
||||
);
|
||||
} else {
|
||||
newSource = new RawSource(output.styles);
|
||||
}
|
||||
|
||||
compilation.assets[file] = newSource;
|
||||
inline: false,
|
||||
returnPromise: true,
|
||||
sourceMap: this._options.sourceMap
|
||||
});
|
||||
|
||||
return Promise.all(actions);
|
||||
});
|
||||
const files: string[] = [...compilation.additionalChunkAssets];
|
||||
|
||||
chunks.forEach(chunk => {
|
||||
if (chunk.files && chunk.files.length > 0) {
|
||||
files.push(...chunk.files);
|
||||
}
|
||||
});
|
||||
|
||||
const actions = files
|
||||
.filter(file => this._options.test(file))
|
||||
.map(async file => {
|
||||
const asset = compilation.assets[file] as Source;
|
||||
if (!asset) {
|
||||
return;
|
||||
}
|
||||
|
||||
let content: string;
|
||||
// tslint:disable-next-line: no-any
|
||||
let map: any;
|
||||
if (this._options.sourceMap && asset.sourceAndMap) {
|
||||
const sourceAndMap = asset.sourceAndMap();
|
||||
content = sourceAndMap.source;
|
||||
map = sourceAndMap.map;
|
||||
} else {
|
||||
content = asset.source();
|
||||
}
|
||||
|
||||
if (content.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const output = await cleancss.minify(content, map);
|
||||
|
||||
let hasWarnings = false;
|
||||
if (output.warnings && output.warnings.length > 0) {
|
||||
compilation.warnings.push(...output.warnings);
|
||||
hasWarnings = true;
|
||||
}
|
||||
|
||||
if (output.errors && output.errors.length > 0) {
|
||||
output.errors.forEach((error: string) =>
|
||||
compilation.errors.push(new Error(error))
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// generally means invalid syntax so bail
|
||||
if (hasWarnings && output.stats.minifiedSize === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let newSource;
|
||||
if (output.sourceMap) {
|
||||
newSource = new SourceMapSource(
|
||||
output.styles,
|
||||
file,
|
||||
// tslint:disable-next-line: no-any
|
||||
output.sourceMap.toString() as any,
|
||||
content,
|
||||
map
|
||||
);
|
||||
} else {
|
||||
newSource = new RawSource(output.styles);
|
||||
}
|
||||
|
||||
compilation.assets[file] = newSource;
|
||||
});
|
||||
|
||||
return Promise.all(actions);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,17 +29,23 @@ 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) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
compilation.inputFileSystem.readFile(
|
||||
filename,
|
||||
(err: Error, data: Buffer) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
|
||||
return;
|
||||
return;
|
||||
}
|
||||
|
||||
resolve(stripBom(data.toString()));
|
||||
}
|
||||
|
||||
resolve(stripBom(data.toString()));
|
||||
});
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@ -54,60 +60,67 @@ export class IndexHtmlWebpackPlugin {
|
||||
noModuleEntrypoints: [],
|
||||
moduleEntrypoints: [],
|
||||
sri: false,
|
||||
...options,
|
||||
...options
|
||||
};
|
||||
}
|
||||
|
||||
apply(compiler: Compiler) {
|
||||
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);
|
||||
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);
|
||||
|
||||
// Get all files for selected entrypoints
|
||||
const files: FileInfo[] = [];
|
||||
const noModuleFiles: FileInfo[] = [];
|
||||
const moduleFiles: FileInfo[] = [];
|
||||
// Get all files for selected entrypoints
|
||||
const files: FileInfo[] = [];
|
||||
const noModuleFiles: FileInfo[] = [];
|
||||
const moduleFiles: FileInfo[] = [];
|
||||
|
||||
for (const [entryName, entrypoint] of compilation.entrypoints) {
|
||||
const entryFiles: FileInfo[] = ((entrypoint && entrypoint.getFiles()) || []).map(
|
||||
(f: string): FileInfo => ({
|
||||
name: entryName,
|
||||
file: f,
|
||||
extension: path.extname(f),
|
||||
}),
|
||||
);
|
||||
for (const [entryName, entrypoint] of compilation.entrypoints) {
|
||||
const entryFiles: FileInfo[] = (
|
||||
(entrypoint && entrypoint.getFiles()) ||
|
||||
[]
|
||||
).map(
|
||||
(f: string): FileInfo => ({
|
||||
name: entryName,
|
||||
file: f,
|
||||
extension: path.extname(f)
|
||||
})
|
||||
);
|
||||
|
||||
if (this._options.noModuleEntrypoints.includes(entryName)) {
|
||||
noModuleFiles.push(...entryFiles);
|
||||
} else if (this._options.moduleEntrypoints.includes(entryName)) {
|
||||
moduleFiles.push(...entryFiles);
|
||||
} else {
|
||||
files.push(...entryFiles);
|
||||
if (this._options.noModuleEntrypoints.includes(entryName)) {
|
||||
noModuleFiles.push(...entryFiles);
|
||||
} else if (this._options.moduleEntrypoints.includes(entryName)) {
|
||||
moduleFiles.push(...entryFiles);
|
||||
} else {
|
||||
files.push(...entryFiles);
|
||||
}
|
||||
}
|
||||
|
||||
const loadOutputFile = (name: string) =>
|
||||
compilation.assets[name].source();
|
||||
let indexSource = await augmentIndexHtml({
|
||||
input: this._options.input,
|
||||
inputContent,
|
||||
baseHref: this._options.baseHref,
|
||||
deployUrl: this._options.deployUrl,
|
||||
sri: this._options.sri,
|
||||
crossOrigin: this._options.crossOrigin,
|
||||
files,
|
||||
noModuleFiles,
|
||||
loadOutputFile,
|
||||
moduleFiles,
|
||||
entrypoints: this._options.entrypoints
|
||||
});
|
||||
|
||||
if (this._options.postTransform) {
|
||||
indexSource = await this._options.postTransform(indexSource);
|
||||
}
|
||||
|
||||
// Add to compilation assets
|
||||
compilation.assets[this._options.output] = new RawSource(indexSource);
|
||||
}
|
||||
|
||||
const loadOutputFile = (name: string) => compilation.assets[name].source();
|
||||
let indexSource = await augmentIndexHtml({
|
||||
input: this._options.input,
|
||||
inputContent,
|
||||
baseHref: this._options.baseHref,
|
||||
deployUrl: this._options.deployUrl,
|
||||
sri: this._options.sri,
|
||||
crossOrigin: this._options.crossOrigin,
|
||||
files,
|
||||
noModuleFiles,
|
||||
loadOutputFile,
|
||||
moduleFiles,
|
||||
entrypoints: this._options.entrypoints,
|
||||
});
|
||||
|
||||
if (this._options.postTransform) {
|
||||
indexSource = await this._options.postTransform(indexSource);
|
||||
}
|
||||
|
||||
// Add to compilation assets
|
||||
compilation.assets[this._options.output] = new RawSource(indexSource);
|
||||
});
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,171 +45,191 @@ async function resolve(
|
||||
}
|
||||
}
|
||||
|
||||
export default postcss.plugin('postcss-cli-resources', (options: PostcssCliResourcesOptions) => {
|
||||
const {
|
||||
deployUrl = '',
|
||||
baseHref = '',
|
||||
resourcesOutputPath = '',
|
||||
rebaseRootRelative = false,
|
||||
filename,
|
||||
loader,
|
||||
} = options;
|
||||
export default postcss.plugin(
|
||||
'postcss-cli-resources',
|
||||
(options: PostcssCliResourcesOptions) => {
|
||||
const {
|
||||
deployUrl = '',
|
||||
baseHref = '',
|
||||
resourcesOutputPath = '',
|
||||
rebaseRootRelative = false,
|
||||
filename,
|
||||
loader
|
||||
} = options;
|
||||
|
||||
const dedupeSlashes = (url: string) => url.replace(/\/\/+/g, '/');
|
||||
const dedupeSlashes = (url: string) => url.replace(/\/\/+/g, '/');
|
||||
|
||||
const process = async (inputUrl: string, context: string, resourceCache: Map<string, string>) => {
|
||||
// If root-relative, absolute or protocol relative url, leave as is
|
||||
if (/^((?:\w+:)?\/\/|data:|chrome:|#)/.test(inputUrl)) {
|
||||
return inputUrl;
|
||||
}
|
||||
|
||||
if (!rebaseRootRelative && /^\//.test(inputUrl)) {
|
||||
return inputUrl;
|
||||
}
|
||||
|
||||
// If starts with a caret, remove and return remainder
|
||||
// this supports bypassing asset processing
|
||||
if (inputUrl.startsWith('^')) {
|
||||
return inputUrl.substr(1);
|
||||
}
|
||||
|
||||
const cacheKey = path.resolve(context, inputUrl);
|
||||
const cachedUrl = resourceCache.get(cacheKey);
|
||||
if (cachedUrl) {
|
||||
return cachedUrl;
|
||||
}
|
||||
|
||||
if (inputUrl.startsWith('~')) {
|
||||
inputUrl = inputUrl.substr(1);
|
||||
}
|
||||
|
||||
if (inputUrl.startsWith('/')) {
|
||||
let outputUrl = '';
|
||||
if (deployUrl.match(/:\/\//) || deployUrl.startsWith('/')) {
|
||||
// If deployUrl is absolute or root relative, ignore baseHref & use deployUrl as is.
|
||||
outputUrl = `${deployUrl.replace(/\/$/, '')}${inputUrl}`;
|
||||
} else if (baseHref.match(/:\/\//)) {
|
||||
// If baseHref contains a scheme, include it as is.
|
||||
outputUrl = baseHref.replace(/\/$/, '') + dedupeSlashes(`/${deployUrl}/${inputUrl}`);
|
||||
} else {
|
||||
// Join together base-href, deploy-url and the original URL.
|
||||
outputUrl = dedupeSlashes(`/${baseHref}/${deployUrl}/${inputUrl}`);
|
||||
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;
|
||||
}
|
||||
|
||||
resourceCache.set(cacheKey, outputUrl);
|
||||
if (!rebaseRootRelative && /^\//.test(inputUrl)) {
|
||||
return inputUrl;
|
||||
}
|
||||
|
||||
return outputUrl;
|
||||
}
|
||||
// If starts with a caret, remove and return remainder
|
||||
// this supports bypassing asset processing
|
||||
if (inputUrl.startsWith('^')) {
|
||||
return inputUrl.substr(1);
|
||||
}
|
||||
|
||||
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);
|
||||
const cacheKey = path.resolve(context, inputUrl);
|
||||
const cachedUrl = resourceCache.get(cacheKey);
|
||||
if (cachedUrl) {
|
||||
return cachedUrl;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
resolve(result);
|
||||
});
|
||||
});
|
||||
if (inputUrl.startsWith('~')) {
|
||||
inputUrl = inputUrl.substr(1);
|
||||
}
|
||||
|
||||
const result = await resolve(pathname as string, context, resolver);
|
||||
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
loader.fs.readFile(result, (err: Error, content: Buffer) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let outputPath = interpolateName(
|
||||
{ resourcePath: result } as webpack.loader.LoaderContext,
|
||||
filename,
|
||||
{ content },
|
||||
);
|
||||
|
||||
if (resourcesOutputPath) {
|
||||
outputPath = path.posix.join(resourcesOutputPath, outputPath);
|
||||
}
|
||||
|
||||
loader.addDependency(result);
|
||||
loader.emitFile(outputPath, content, undefined);
|
||||
|
||||
let outputUrl = outputPath.replace(/\\/g, '/');
|
||||
if (hash || search) {
|
||||
outputUrl = url.format({ pathname: outputUrl, hash, search });
|
||||
}
|
||||
|
||||
if (deployUrl && loader.loaders[loader.loaderIndex].options.ident !== 'extracted') {
|
||||
outputUrl = url.resolve(deployUrl, outputUrl);
|
||||
if (inputUrl.startsWith('/')) {
|
||||
let outputUrl = '';
|
||||
if (deployUrl.match(/:\/\//) || deployUrl.startsWith('/')) {
|
||||
// If deployUrl is absolute or root relative, ignore baseHref & use deployUrl as is.
|
||||
outputUrl = `${deployUrl.replace(/\/$/, '')}${inputUrl}`;
|
||||
} else if (baseHref.match(/:\/\//)) {
|
||||
// If baseHref contains a scheme, include it as is.
|
||||
outputUrl =
|
||||
baseHref.replace(/\/$/, '') +
|
||||
dedupeSlashes(`/${deployUrl}/${inputUrl}`);
|
||||
} else {
|
||||
// Join together base-href, deploy-url and the original URL.
|
||||
outputUrl = dedupeSlashes(`/${baseHref}/${deployUrl}/${inputUrl}`);
|
||||
}
|
||||
|
||||
resourceCache.set(cacheKey, outputUrl);
|
||||
resolve(outputUrl);
|
||||
|
||||
return outputUrl;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
return;
|
||||
}
|
||||
resolve(result);
|
||||
});
|
||||
});
|
||||
|
||||
const result = await resolve(pathname as string, context, resolver);
|
||||
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
loader.fs.readFile(result, (err: Error, content: Buffer) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let outputPath = interpolateName(
|
||||
{ resourcePath: result } as webpack.loader.LoaderContext,
|
||||
filename,
|
||||
{ content }
|
||||
);
|
||||
|
||||
if (resourcesOutputPath) {
|
||||
outputPath = path.posix.join(resourcesOutputPath, outputPath);
|
||||
}
|
||||
|
||||
loader.addDependency(result);
|
||||
loader.emitFile(outputPath, content, undefined);
|
||||
|
||||
let outputUrl = outputPath.replace(/\\/g, '/');
|
||||
if (hash || search) {
|
||||
outputUrl = url.format({ pathname: outputUrl, hash, search });
|
||||
}
|
||||
|
||||
if (
|
||||
deployUrl &&
|
||||
loader.loaders[loader.loaderIndex].options.ident !== 'extracted'
|
||||
) {
|
||||
outputUrl = url.resolve(deployUrl, outputUrl);
|
||||
}
|
||||
|
||||
resourceCache.set(cacheKey, outputUrl);
|
||||
resolve(outputUrl);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
return (root) => {
|
||||
const urlDeclarations: Array<postcss.Declaration> = [];
|
||||
root.walkDecls(decl => {
|
||||
if (decl.value && decl.value.includes('url')) {
|
||||
urlDeclarations.push(decl);
|
||||
}
|
||||
});
|
||||
|
||||
if (urlDeclarations.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const resourceCache = new Map<string, string>();
|
||||
|
||||
return Promise.all(urlDeclarations.map(async decl => {
|
||||
const value = decl.value;
|
||||
const urlRegex = /url\(\s*(?:"([^"]+)"|'([^']+)'|(.+?))\s*\)/g;
|
||||
const segments: string[] = [];
|
||||
|
||||
let match;
|
||||
let lastIndex = 0;
|
||||
let modified = false;
|
||||
|
||||
// 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;
|
||||
|
||||
// tslint:disable-next-line:no-conditional-assignment
|
||||
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());
|
||||
continue;
|
||||
return root => {
|
||||
const urlDeclarations: Array<postcss.Declaration> = [];
|
||||
root.walkDecls(decl => {
|
||||
if (decl.value && decl.value.includes('url')) {
|
||||
urlDeclarations.push(decl);
|
||||
}
|
||||
});
|
||||
|
||||
if (lastIndex < match.index) {
|
||||
segments.push(value.slice(lastIndex, match.index));
|
||||
}
|
||||
|
||||
if (!processedUrl || originalUrl === processedUrl) {
|
||||
segments.push(match[0]);
|
||||
} else {
|
||||
segments.push(wrapUrl(processedUrl));
|
||||
modified = true;
|
||||
}
|
||||
|
||||
lastIndex = match.index + match[0].length;
|
||||
if (urlDeclarations.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (lastIndex < value.length) {
|
||||
segments.push(value.slice(lastIndex));
|
||||
}
|
||||
const resourceCache = new Map<string, string>();
|
||||
|
||||
if (modified) {
|
||||
decl.value = segments.join('');
|
||||
}
|
||||
}));
|
||||
};
|
||||
});
|
||||
return Promise.all(
|
||||
urlDeclarations.map(async decl => {
|
||||
const value = decl.value;
|
||||
const urlRegex = /url\(\s*(?:"([^"]+)"|'([^']+)'|(.+?))\s*\)/g;
|
||||
const segments: string[] = [];
|
||||
|
||||
let match;
|
||||
let lastIndex = 0;
|
||||
let modified = false;
|
||||
|
||||
// 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;
|
||||
|
||||
// tslint:disable-next-line:no-conditional-assignment
|
||||
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()
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (lastIndex < match.index) {
|
||||
segments.push(value.slice(lastIndex, match.index));
|
||||
}
|
||||
|
||||
if (!processedUrl || originalUrl === processedUrl) {
|
||||
segments.push(match[0]);
|
||||
} else {
|
||||
segments.push(wrapUrl(processedUrl));
|
||||
modified = true;
|
||||
}
|
||||
|
||||
lastIndex = match.index + match[0].length;
|
||||
}
|
||||
|
||||
if (lastIndex < value.length) {
|
||||
segments.push(value.slice(lastIndex));
|
||||
}
|
||||
|
||||
if (modified) {
|
||||
decl.value = segments.join('');
|
||||
}
|
||||
})
|
||||
);
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
@ -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;
|
||||
},
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@ -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) => {
|
||||
compilation.hooks.additionalAssets.tapAsync(
|
||||
'scripts-webpack-plugin',
|
||||
(callback: (err?: Error) => void) => action(compilation, callback),
|
||||
);
|
||||
});
|
||||
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)
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
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,29 +128,32 @@ export class ScriptsWebpackPlugin {
|
||||
|
||||
const sourceGetters = scripts.map(fullPath => {
|
||||
return new Promise<Source>((resolve, reject) => {
|
||||
compilation.inputFileSystem.readFile(fullPath, (err: Error, data: Buffer) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
|
||||
const content = data.toString();
|
||||
|
||||
let source;
|
||||
if (this.options.sourceMap) {
|
||||
// TODO: Look for source map file (for '.min' scripts, etc.)
|
||||
|
||||
let adjustedPath = fullPath;
|
||||
if (this.options.basePath) {
|
||||
adjustedPath = path.relative(this.options.basePath, fullPath);
|
||||
compilation.inputFileSystem.readFile(
|
||||
fullPath,
|
||||
(err: Error, data: Buffer) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
source = new OriginalSource(content, adjustedPath);
|
||||
} else {
|
||||
source = new RawSource(content);
|
||||
}
|
||||
|
||||
resolve(source);
|
||||
});
|
||||
const content = data.toString();
|
||||
|
||||
let source;
|
||||
if (this.options.sourceMap) {
|
||||
// TODO: Look for source map file (for '.min' scripts, etc.)
|
||||
|
||||
let adjustedPath = fullPath;
|
||||
if (this.options.basePath) {
|
||||
adjustedPath = path.relative(this.options.basePath, fullPath);
|
||||
}
|
||||
source = new OriginalSource(content, adjustedPath);
|
||||
} else {
|
||||
source = new RawSource(content);
|
||||
}
|
||||
|
||||
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 };
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -12,51 +12,57 @@
|
||||
// To be used together with ExtractTextPlugin.
|
||||
|
||||
export class SuppressExtractedTextChunksWebpackPlugin {
|
||||
constructor() { }
|
||||
constructor() {}
|
||||
|
||||
apply(compiler: any): void {
|
||||
compiler.hooks.compilation.tap('SuppressExtractedTextChunks', (compilation: any) => {
|
||||
// find which chunks have css only entry points
|
||||
const cssOnlyChunks: string[] = [];
|
||||
const entryPoints = compilation.options.entry;
|
||||
// determine which entry points are composed entirely of css files
|
||||
for (let entryPoint of Object.keys(entryPoints)) {
|
||||
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)) {
|
||||
cssOnlyChunks.push(entryPoint);
|
||||
compiler.hooks.compilation.tap(
|
||||
'SuppressExtractedTextChunks',
|
||||
(compilation: any) => {
|
||||
// find which chunks have css only entry points
|
||||
const cssOnlyChunks: string[] = [];
|
||||
const entryPoints = compilation.options.entry;
|
||||
// determine which entry points are composed entirely of css files
|
||||
for (let entryPoint of Object.keys(entryPoints)) {
|
||||
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
|
||||
)
|
||||
) {
|
||||
cssOnlyChunks.push(entryPoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Remove the js file for supressed chunks
|
||||
compilation.hooks.afterSeal.tap('SuppressExtractedTextChunks', () => {
|
||||
compilation.chunks
|
||||
.filter((chunk: any) => cssOnlyChunks.indexOf(chunk.name) !== -1)
|
||||
.forEach((chunk: any) => {
|
||||
let newFiles: string[] = [];
|
||||
chunk.files.forEach((file: string) => {
|
||||
if (file.match(/\.js(\.map)?$/)) {
|
||||
// remove js files
|
||||
delete compilation.assets[file];
|
||||
} else {
|
||||
newFiles.push(file);
|
||||
}
|
||||
// Remove the js file for supressed chunks
|
||||
compilation.hooks.afterSeal.tap('SuppressExtractedTextChunks', () => {
|
||||
compilation.chunks
|
||||
.filter((chunk: any) => cssOnlyChunks.indexOf(chunk.name) !== -1)
|
||||
.forEach((chunk: any) => {
|
||||
let newFiles: string[] = [];
|
||||
chunk.files.forEach((file: string) => {
|
||||
if (file.match(/\.js(\.map)?$/)) {
|
||||
// remove js files
|
||||
delete compilation.assets[file];
|
||||
} else {
|
||||
newFiles.push(file);
|
||||
}
|
||||
});
|
||||
chunk.files = newFiles;
|
||||
});
|
||||
chunk.files = newFiles;
|
||||
});
|
||||
});
|
||||
// Remove scripts tags with a css file as source, because HtmlWebpackPlugin will use
|
||||
// a css file as a script for chunks without js files.
|
||||
// TODO: Enable this once HtmlWebpackPlugin supports Webpack 4
|
||||
// compilation.plugin('html-webpack-plugin-alter-asset-tags',
|
||||
// (htmlPluginData: any, callback: any) => {
|
||||
// const filterFn = (tag: any) =>
|
||||
// !(tag.tagName === 'script' && tag.attributes.src.match(/\.css$/));
|
||||
// htmlPluginData.head = htmlPluginData.head.filter(filterFn);
|
||||
// htmlPluginData.body = htmlPluginData.body.filter(filterFn);
|
||||
// callback(null, htmlPluginData);
|
||||
// });
|
||||
});
|
||||
});
|
||||
// Remove scripts tags with a css file as source, because HtmlWebpackPlugin will use
|
||||
// a css file as a script for chunks without js files.
|
||||
// TODO: Enable this once HtmlWebpackPlugin supports Webpack 4
|
||||
// compilation.plugin('html-webpack-plugin-alter-asset-tags',
|
||||
// (htmlPluginData: any, callback: any) => {
|
||||
// const filterFn = (tag: any) =>
|
||||
// !(tag.tagName === 'script' && tag.attributes.src.match(/\.css$/));
|
||||
// htmlPluginData.head = htmlPluginData.head.filter(filterFn);
|
||||
// htmlPluginData.body = htmlPluginData.body.filter(filterFn);
|
||||
// callback(null, htmlPluginData);
|
||||
// });
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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,23 +20,26 @@ export function checkPort(port: number, host: string, basePort = 49152): Promise
|
||||
|
||||
const server = net.createServer();
|
||||
|
||||
server.once('error', (err: Error & {code: string}) => {
|
||||
if (err.code !== 'EADDRINUSE') {
|
||||
reject(err);
|
||||
} else if (port === 0) {
|
||||
_getPort(portNumber + 1);
|
||||
} 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.`),
|
||||
);
|
||||
}
|
||||
})
|
||||
.once('listening', () => {
|
||||
server.close();
|
||||
resolve(portNumber);
|
||||
})
|
||||
.listen(portNumber, host);
|
||||
server
|
||||
.once('error', (err: Error & { code: string }) => {
|
||||
if (err.code !== 'EADDRINUSE') {
|
||||
reject(err);
|
||||
} else if (port === 0) {
|
||||
_getPort(portNumber + 1);
|
||||
} 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.`
|
||||
)
|
||||
);
|
||||
}
|
||||
})
|
||||
.once('listening', () => {
|
||||
server.close();
|
||||
resolve(portNumber);
|
||||
})
|
||||
.listen(portNumber, host);
|
||||
}
|
||||
|
||||
_getPort(port || basePort);
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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];
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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,31 +97,29 @@ describe('augment-index-html', () => {
|
||||
`);
|
||||
});
|
||||
|
||||
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' },
|
||||
];
|
||||
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' }
|
||||
];
|
||||
|
||||
const es5JsFiles: FileInfo[] = [
|
||||
{ file: 'scripts.js', extension: '.js', name: 'scripts' },
|
||||
{ file: 'main-es5.js', extension: '.js', name: 'main' },
|
||||
];
|
||||
const es5JsFiles: FileInfo[] = [
|
||||
{ file: 'scripts.js', extension: '.js', name: 'scripts' },
|
||||
{ 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' },
|
||||
],
|
||||
moduleFiles: es2015JsFiles,
|
||||
noModuleFiles: es5JsFiles,
|
||||
});
|
||||
const source = augmentIndexHtml({
|
||||
...indexGeneratorOptions,
|
||||
files: [
|
||||
{ file: 'styles.css', extension: '.css', name: 'styles' },
|
||||
{ file: 'styles.css', extension: '.css', name: 'styles' }
|
||||
],
|
||||
moduleFiles: es2015JsFiles,
|
||||
noModuleFiles: es5JsFiles
|
||||
});
|
||||
|
||||
const html = await source;
|
||||
expect(html).toEqual(oneLineHtml`
|
||||
const html = await source;
|
||||
expect(html).toEqual(oneLineHtml`
|
||||
<html>
|
||||
<head>
|
||||
<base href="/">
|
||||
@ -130,6 +132,5 @@ describe('augment-index-html', () => {
|
||||
</body>
|
||||
</html>
|
||||
`);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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')
|
||||
);
|
||||
}
|
||||
|
||||
@ -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'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
}),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
);
|
||||
|
||||
@ -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 ...).
|
||||
|
||||
@ -9,324 +8,324 @@
|
||||
* Browser target options
|
||||
*/
|
||||
export interface Schema {
|
||||
/**
|
||||
* Build using Ahead of Time compilation.
|
||||
*/
|
||||
aot?: boolean;
|
||||
/**
|
||||
* List of static application assets.
|
||||
*/
|
||||
assets?: AssetPattern[];
|
||||
/**
|
||||
* Base url for the application being built.
|
||||
*/
|
||||
baseHref?: string;
|
||||
/**
|
||||
* Budget thresholds to ensure parts of your application stay within boundaries which you
|
||||
* set.
|
||||
*/
|
||||
budgets?: Budget[];
|
||||
/**
|
||||
* Enables '@angular-devkit/build-optimizer' optimizations when using the 'aot' option.
|
||||
*/
|
||||
buildOptimizer?: boolean;
|
||||
/**
|
||||
* Use a separate bundle containing code used across multiple bundles.
|
||||
*/
|
||||
commonChunk?: boolean;
|
||||
/**
|
||||
* Define the crossorigin attribute setting of elements that provide CORS support.
|
||||
*/
|
||||
crossOrigin?: CrossOrigin;
|
||||
/**
|
||||
* Delete the output path before building.
|
||||
*/
|
||||
deleteOutputPath?: boolean;
|
||||
/**
|
||||
* URL where files will be deployed.
|
||||
*/
|
||||
deployUrl?: string;
|
||||
/**
|
||||
* Enables conditionally loaded ES2015 polyfills.
|
||||
* @deprecated This will be determined from the list of supported browsers specified in the
|
||||
* 'browserslist' file.
|
||||
*/
|
||||
es5BrowserSupport?: boolean;
|
||||
/**
|
||||
* Output in-file eval sourcemaps.
|
||||
* @deprecated
|
||||
*/
|
||||
evalSourceMap?: boolean;
|
||||
/**
|
||||
* Concatenate modules with Rollup before bundling them with Webpack.
|
||||
*/
|
||||
experimentalRollupPass?: boolean;
|
||||
/**
|
||||
* Extract css from global styles into css files instead of js ones.
|
||||
*/
|
||||
extractCss?: boolean;
|
||||
/**
|
||||
* Extract all licenses in a separate file.
|
||||
*/
|
||||
extractLicenses?: boolean;
|
||||
/**
|
||||
* Replace files with other files in the build.
|
||||
*/
|
||||
fileReplacements?: FileReplacement[];
|
||||
/**
|
||||
* Run the TypeScript type checker in a forked process.
|
||||
*/
|
||||
forkTypeChecker?: boolean;
|
||||
/**
|
||||
* Localization file to use for i18n.
|
||||
* @deprecated Use 'locales' object in the project metadata instead.
|
||||
*/
|
||||
i18nFile?: string;
|
||||
/**
|
||||
* Format of the localization file specified with --i18n-file.
|
||||
* @deprecated No longer needed as the format will be determined automatically.
|
||||
*/
|
||||
i18nFormat?: string;
|
||||
/**
|
||||
* Locale to use for i18n.
|
||||
* @deprecated Use 'localize' instead.
|
||||
*/
|
||||
i18nLocale?: string;
|
||||
/**
|
||||
* How to handle missing translations for i18n.
|
||||
*/
|
||||
i18nMissingTranslation?: I18NMissingTranslation;
|
||||
/**
|
||||
* Configures the generation of the application's HTML index.
|
||||
*/
|
||||
index: IndexUnion;
|
||||
/**
|
||||
* List of additional NgModule files that will be lazy loaded. Lazy router modules will be
|
||||
* discovered automatically.
|
||||
* @deprecated 'SystemJsNgModuleLoader' is deprecated, and this is part of its usage. Use
|
||||
* 'import()' syntax instead.
|
||||
*/
|
||||
lazyModules?: string[];
|
||||
localize?: Localize;
|
||||
/**
|
||||
* The full path for the main entry point to the app, relative to the current workspace.
|
||||
*/
|
||||
main: string;
|
||||
/**
|
||||
* Use file name for lazy loaded chunks.
|
||||
*/
|
||||
namedChunks?: boolean;
|
||||
/**
|
||||
* Path to ngsw-config.json.
|
||||
*/
|
||||
ngswConfigPath?: string;
|
||||
/**
|
||||
* Enables optimization of the build output.
|
||||
*/
|
||||
optimization?: OptimizationUnion;
|
||||
/**
|
||||
* Define the output filename cache-busting hashing mode.
|
||||
*/
|
||||
outputHashing?: OutputHashing;
|
||||
/**
|
||||
* The full path for the new output directory, relative to the current workspace.
|
||||
*
|
||||
* By default, writes output to a folder named dist/ in the current project.
|
||||
*/
|
||||
outputPath: string;
|
||||
/**
|
||||
* Enable and define the file watching poll time period in milliseconds.
|
||||
*/
|
||||
poll?: number;
|
||||
/**
|
||||
* The full path for the polyfills file, relative to the current workspace.
|
||||
*/
|
||||
polyfills?: string;
|
||||
/**
|
||||
* Do not use the real path when resolving modules.
|
||||
*/
|
||||
preserveSymlinks?: boolean;
|
||||
/**
|
||||
* Output profile events for Chrome profiler.
|
||||
* @deprecated Use "NG_BUILD_PROFILING" environment variable instead.
|
||||
*/
|
||||
profile?: boolean;
|
||||
/**
|
||||
* Log progress to the console while building.
|
||||
*/
|
||||
progress?: boolean;
|
||||
/**
|
||||
* Change root relative URLs in stylesheets to include base HREF and deploy URL. Use only
|
||||
* for compatibility and transition. The behavior of this option is non-standard and will be
|
||||
* removed in the next major release.
|
||||
* @deprecated
|
||||
*/
|
||||
rebaseRootRelativeCssUrls?: boolean;
|
||||
/**
|
||||
* The path where style resources will be placed, relative to outputPath.
|
||||
*/
|
||||
resourcesOutputPath?: string;
|
||||
/**
|
||||
* Global scripts to be included in the build.
|
||||
*/
|
||||
scripts?: ExtraEntryPoint[];
|
||||
/**
|
||||
* Generates a service worker config for production builds.
|
||||
*/
|
||||
serviceWorker?: boolean;
|
||||
/**
|
||||
* Show circular dependency warnings on builds.
|
||||
*/
|
||||
showCircularDependencies?: boolean;
|
||||
/**
|
||||
* Flag to prevent building an app shell.
|
||||
* @deprecated
|
||||
*/
|
||||
skipAppShell?: boolean;
|
||||
/**
|
||||
* Output sourcemaps.
|
||||
*/
|
||||
sourceMap?: SourceMapUnion;
|
||||
/**
|
||||
* Generates a 'stats.json' file which can be analyzed using tools such as
|
||||
* 'webpack-bundle-analyzer'.
|
||||
*/
|
||||
statsJson?: boolean;
|
||||
/**
|
||||
* Options to pass to style preprocessors.
|
||||
*/
|
||||
stylePreprocessorOptions?: StylePreprocessorOptions;
|
||||
/**
|
||||
* Global styles to be included in the build.
|
||||
*/
|
||||
styles?: ExtraEntryPoint[];
|
||||
/**
|
||||
* Enables the use of subresource integrity validation.
|
||||
*/
|
||||
subresourceIntegrity?: boolean;
|
||||
/**
|
||||
* The full path for the TypeScript configuration file, relative to the current workspace.
|
||||
*/
|
||||
tsConfig: string;
|
||||
/**
|
||||
* Use a separate bundle containing only vendor libraries.
|
||||
*/
|
||||
vendorChunk?: boolean;
|
||||
/**
|
||||
* Resolve vendor packages sourcemaps.
|
||||
* @deprecated
|
||||
*/
|
||||
vendorSourceMap?: boolean;
|
||||
/**
|
||||
* Adds more details to output logging.
|
||||
*/
|
||||
verbose?: boolean;
|
||||
/**
|
||||
* Run build when files change.
|
||||
*/
|
||||
watch?: boolean;
|
||||
/**
|
||||
* TypeScript configuration for Web Worker modules.
|
||||
*/
|
||||
webWorkerTsConfig?: string;
|
||||
/**
|
||||
* Build using Ahead of Time compilation.
|
||||
*/
|
||||
aot?: boolean;
|
||||
/**
|
||||
* List of static application assets.
|
||||
*/
|
||||
assets?: AssetPattern[];
|
||||
/**
|
||||
* Base url for the application being built.
|
||||
*/
|
||||
baseHref?: string;
|
||||
/**
|
||||
* Budget thresholds to ensure parts of your application stay within boundaries which you
|
||||
* set.
|
||||
*/
|
||||
budgets?: Budget[];
|
||||
/**
|
||||
* Enables '@angular-devkit/build-optimizer' optimizations when using the 'aot' option.
|
||||
*/
|
||||
buildOptimizer?: boolean;
|
||||
/**
|
||||
* Use a separate bundle containing code used across multiple bundles.
|
||||
*/
|
||||
commonChunk?: boolean;
|
||||
/**
|
||||
* Define the crossorigin attribute setting of elements that provide CORS support.
|
||||
*/
|
||||
crossOrigin?: CrossOrigin;
|
||||
/**
|
||||
* Delete the output path before building.
|
||||
*/
|
||||
deleteOutputPath?: boolean;
|
||||
/**
|
||||
* URL where files will be deployed.
|
||||
*/
|
||||
deployUrl?: string;
|
||||
/**
|
||||
* Enables conditionally loaded ES2015 polyfills.
|
||||
* @deprecated This will be determined from the list of supported browsers specified in the
|
||||
* 'browserslist' file.
|
||||
*/
|
||||
es5BrowserSupport?: boolean;
|
||||
/**
|
||||
* Output in-file eval sourcemaps.
|
||||
* @deprecated
|
||||
*/
|
||||
evalSourceMap?: boolean;
|
||||
/**
|
||||
* Concatenate modules with Rollup before bundling them with Webpack.
|
||||
*/
|
||||
experimentalRollupPass?: boolean;
|
||||
/**
|
||||
* Extract css from global styles into css files instead of js ones.
|
||||
*/
|
||||
extractCss?: boolean;
|
||||
/**
|
||||
* Extract all licenses in a separate file.
|
||||
*/
|
||||
extractLicenses?: boolean;
|
||||
/**
|
||||
* Replace files with other files in the build.
|
||||
*/
|
||||
fileReplacements?: FileReplacement[];
|
||||
/**
|
||||
* Run the TypeScript type checker in a forked process.
|
||||
*/
|
||||
forkTypeChecker?: boolean;
|
||||
/**
|
||||
* Localization file to use for i18n.
|
||||
* @deprecated Use 'locales' object in the project metadata instead.
|
||||
*/
|
||||
i18nFile?: string;
|
||||
/**
|
||||
* Format of the localization file specified with --i18n-file.
|
||||
* @deprecated No longer needed as the format will be determined automatically.
|
||||
*/
|
||||
i18nFormat?: string;
|
||||
/**
|
||||
* Locale to use for i18n.
|
||||
* @deprecated Use 'localize' instead.
|
||||
*/
|
||||
i18nLocale?: string;
|
||||
/**
|
||||
* How to handle missing translations for i18n.
|
||||
*/
|
||||
i18nMissingTranslation?: I18NMissingTranslation;
|
||||
/**
|
||||
* Configures the generation of the application's HTML index.
|
||||
*/
|
||||
index: IndexUnion;
|
||||
/**
|
||||
* List of additional NgModule files that will be lazy loaded. Lazy router modules will be
|
||||
* discovered automatically.
|
||||
* @deprecated 'SystemJsNgModuleLoader' is deprecated, and this is part of its usage. Use
|
||||
* 'import()' syntax instead.
|
||||
*/
|
||||
lazyModules?: string[];
|
||||
localize?: Localize;
|
||||
/**
|
||||
* The full path for the main entry point to the app, relative to the current workspace.
|
||||
*/
|
||||
main: string;
|
||||
/**
|
||||
* Use file name for lazy loaded chunks.
|
||||
*/
|
||||
namedChunks?: boolean;
|
||||
/**
|
||||
* Path to ngsw-config.json.
|
||||
*/
|
||||
ngswConfigPath?: string;
|
||||
/**
|
||||
* Enables optimization of the build output.
|
||||
*/
|
||||
optimization?: OptimizationUnion;
|
||||
/**
|
||||
* Define the output filename cache-busting hashing mode.
|
||||
*/
|
||||
outputHashing?: OutputHashing;
|
||||
/**
|
||||
* The full path for the new output directory, relative to the current workspace.
|
||||
*
|
||||
* By default, writes output to a folder named dist/ in the current project.
|
||||
*/
|
||||
outputPath: string;
|
||||
/**
|
||||
* Enable and define the file watching poll time period in milliseconds.
|
||||
*/
|
||||
poll?: number;
|
||||
/**
|
||||
* The full path for the polyfills file, relative to the current workspace.
|
||||
*/
|
||||
polyfills?: string;
|
||||
/**
|
||||
* Do not use the real path when resolving modules.
|
||||
*/
|
||||
preserveSymlinks?: boolean;
|
||||
/**
|
||||
* Output profile events for Chrome profiler.
|
||||
* @deprecated Use "NG_BUILD_PROFILING" environment variable instead.
|
||||
*/
|
||||
profile?: boolean;
|
||||
/**
|
||||
* Log progress to the console while building.
|
||||
*/
|
||||
progress?: boolean;
|
||||
/**
|
||||
* Change root relative URLs in stylesheets to include base HREF and deploy URL. Use only
|
||||
* for compatibility and transition. The behavior of this option is non-standard and will be
|
||||
* removed in the next major release.
|
||||
* @deprecated
|
||||
*/
|
||||
rebaseRootRelativeCssUrls?: boolean;
|
||||
/**
|
||||
* The path where style resources will be placed, relative to outputPath.
|
||||
*/
|
||||
resourcesOutputPath?: string;
|
||||
/**
|
||||
* Global scripts to be included in the build.
|
||||
*/
|
||||
scripts?: ExtraEntryPoint[];
|
||||
/**
|
||||
* Generates a service worker config for production builds.
|
||||
*/
|
||||
serviceWorker?: boolean;
|
||||
/**
|
||||
* Show circular dependency warnings on builds.
|
||||
*/
|
||||
showCircularDependencies?: boolean;
|
||||
/**
|
||||
* Flag to prevent building an app shell.
|
||||
* @deprecated
|
||||
*/
|
||||
skipAppShell?: boolean;
|
||||
/**
|
||||
* Output sourcemaps.
|
||||
*/
|
||||
sourceMap?: SourceMapUnion;
|
||||
/**
|
||||
* Generates a 'stats.json' file which can be analyzed using tools such as
|
||||
* 'webpack-bundle-analyzer'.
|
||||
*/
|
||||
statsJson?: boolean;
|
||||
/**
|
||||
* Options to pass to style preprocessors.
|
||||
*/
|
||||
stylePreprocessorOptions?: StylePreprocessorOptions;
|
||||
/**
|
||||
* Global styles to be included in the build.
|
||||
*/
|
||||
styles?: ExtraEntryPoint[];
|
||||
/**
|
||||
* Enables the use of subresource integrity validation.
|
||||
*/
|
||||
subresourceIntegrity?: boolean;
|
||||
/**
|
||||
* The full path for the TypeScript configuration file, relative to the current workspace.
|
||||
*/
|
||||
tsConfig: string;
|
||||
/**
|
||||
* Use a separate bundle containing only vendor libraries.
|
||||
*/
|
||||
vendorChunk?: boolean;
|
||||
/**
|
||||
* Resolve vendor packages sourcemaps.
|
||||
* @deprecated
|
||||
*/
|
||||
vendorSourceMap?: boolean;
|
||||
/**
|
||||
* Adds more details to output logging.
|
||||
*/
|
||||
verbose?: boolean;
|
||||
/**
|
||||
* Run build when files change.
|
||||
*/
|
||||
watch?: boolean;
|
||||
/**
|
||||
* TypeScript configuration for Web Worker modules.
|
||||
*/
|
||||
webWorkerTsConfig?: string;
|
||||
}
|
||||
|
||||
export type AssetPattern = AssetPatternClass | string;
|
||||
|
||||
export interface AssetPatternClass {
|
||||
/**
|
||||
* The pattern to match.
|
||||
*/
|
||||
glob: string;
|
||||
/**
|
||||
* An array of globs to ignore.
|
||||
*/
|
||||
ignore?: string[];
|
||||
/**
|
||||
* The input directory path in which to apply 'glob'. Defaults to the project root.
|
||||
*/
|
||||
input: string;
|
||||
/**
|
||||
* Absolute path within the output.
|
||||
*/
|
||||
output: string;
|
||||
/**
|
||||
* The pattern to match.
|
||||
*/
|
||||
glob: string;
|
||||
/**
|
||||
* An array of globs to ignore.
|
||||
*/
|
||||
ignore?: string[];
|
||||
/**
|
||||
* The input directory path in which to apply 'glob'. Defaults to the project root.
|
||||
*/
|
||||
input: string;
|
||||
/**
|
||||
* Absolute path within the output.
|
||||
*/
|
||||
output: string;
|
||||
}
|
||||
|
||||
export interface Budget {
|
||||
/**
|
||||
* The baseline size for comparison.
|
||||
*/
|
||||
baseline?: string;
|
||||
/**
|
||||
* The threshold for error relative to the baseline (min & max).
|
||||
*/
|
||||
error?: string;
|
||||
/**
|
||||
* The maximum threshold for error relative to the baseline.
|
||||
*/
|
||||
maximumError?: string;
|
||||
/**
|
||||
* The maximum threshold for warning relative to the baseline.
|
||||
*/
|
||||
maximumWarning?: string;
|
||||
/**
|
||||
* The minimum threshold for error relative to the baseline.
|
||||
*/
|
||||
minimumError?: string;
|
||||
/**
|
||||
* The minimum threshold for warning relative to the baseline.
|
||||
*/
|
||||
minimumWarning?: string;
|
||||
/**
|
||||
* The name of the bundle.
|
||||
*/
|
||||
name?: string;
|
||||
/**
|
||||
* The type of budget.
|
||||
*/
|
||||
type: Type;
|
||||
/**
|
||||
* The threshold for warning relative to the baseline (min & max).
|
||||
*/
|
||||
warning?: string;
|
||||
/**
|
||||
* The baseline size for comparison.
|
||||
*/
|
||||
baseline?: string;
|
||||
/**
|
||||
* The threshold for error relative to the baseline (min & max).
|
||||
*/
|
||||
error?: string;
|
||||
/**
|
||||
* The maximum threshold for error relative to the baseline.
|
||||
*/
|
||||
maximumError?: string;
|
||||
/**
|
||||
* The maximum threshold for warning relative to the baseline.
|
||||
*/
|
||||
maximumWarning?: string;
|
||||
/**
|
||||
* The minimum threshold for error relative to the baseline.
|
||||
*/
|
||||
minimumError?: string;
|
||||
/**
|
||||
* The minimum threshold for warning relative to the baseline.
|
||||
*/
|
||||
minimumWarning?: string;
|
||||
/**
|
||||
* The name of the bundle.
|
||||
*/
|
||||
name?: string;
|
||||
/**
|
||||
* The type of budget.
|
||||
*/
|
||||
type: Type;
|
||||
/**
|
||||
* The threshold for warning relative to the baseline (min & max).
|
||||
*/
|
||||
warning?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
replace?: string;
|
||||
replaceWith?: string;
|
||||
src?: string;
|
||||
with?: string;
|
||||
replace?: string;
|
||||
replaceWith?: string;
|
||||
src?: string;
|
||||
with?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* How to handle missing translations for i18n.
|
||||
*/
|
||||
export enum I18NMissingTranslation {
|
||||
Error = "error",
|
||||
Ignore = "ignore",
|
||||
Warning = "warning",
|
||||
Error = 'error',
|
||||
Ignore = 'ignore',
|
||||
Warning = 'warning'
|
||||
}
|
||||
|
||||
/**
|
||||
@ -335,15 +334,15 @@ export enum I18NMissingTranslation {
|
||||
export type IndexUnion = IndexObject | string;
|
||||
|
||||
export interface IndexObject {
|
||||
/**
|
||||
* The path of a file to use for the application's generated HTML index.
|
||||
*/
|
||||
input: string;
|
||||
/**
|
||||
* The output path of the application's generated HTML index file. The full provided path
|
||||
* will be used and will be considered relative to the application's configured output path.
|
||||
*/
|
||||
output?: string;
|
||||
/**
|
||||
* The path of a file to use for the application's generated HTML index.
|
||||
*/
|
||||
input: string;
|
||||
/**
|
||||
* The output path of the application's generated HTML index file. The full provided path
|
||||
* will be used and will be considered relative to the application's configured output path.
|
||||
*/
|
||||
output?: string;
|
||||
}
|
||||
|
||||
export type Localize = string[] | boolean;
|
||||
@ -354,45 +353,45 @@ export type Localize = string[] | boolean;
|
||||
export type OptimizationUnion = boolean | OptimizationClass;
|
||||
|
||||
export interface OptimizationClass {
|
||||
/**
|
||||
* Enables optimization of the scripts output.
|
||||
*/
|
||||
scripts?: boolean;
|
||||
/**
|
||||
* Enables optimization of the styles output.
|
||||
*/
|
||||
styles?: boolean;
|
||||
/**
|
||||
* Enables optimization of the scripts output.
|
||||
*/
|
||||
scripts?: boolean;
|
||||
/**
|
||||
* Enables optimization of the styles output.
|
||||
*/
|
||||
styles?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
export interface ExtraEntryPointClass {
|
||||
/**
|
||||
* The bundle name for this extra entry point.
|
||||
*/
|
||||
bundleName?: string;
|
||||
/**
|
||||
* If the bundle will be referenced in the HTML file.
|
||||
*/
|
||||
inject?: boolean;
|
||||
/**
|
||||
* The file to include.
|
||||
*/
|
||||
input: string;
|
||||
/**
|
||||
* If the bundle will be lazy loaded.
|
||||
*/
|
||||
lazy?: boolean;
|
||||
/**
|
||||
* The bundle name for this extra entry point.
|
||||
*/
|
||||
bundleName?: string;
|
||||
/**
|
||||
* If the bundle will be referenced in the HTML file.
|
||||
*/
|
||||
inject?: boolean;
|
||||
/**
|
||||
* The file to include.
|
||||
*/
|
||||
input: string;
|
||||
/**
|
||||
* If the bundle will be lazy loaded.
|
||||
*/
|
||||
lazy?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -401,30 +400,30 @@ export interface ExtraEntryPointClass {
|
||||
export type SourceMapUnion = boolean | SourceMapClass;
|
||||
|
||||
export interface SourceMapClass {
|
||||
/**
|
||||
* Output sourcemaps used for error reporting tools.
|
||||
*/
|
||||
hidden?: boolean;
|
||||
/**
|
||||
* Output sourcemaps for all scripts.
|
||||
*/
|
||||
scripts?: boolean;
|
||||
/**
|
||||
* Output sourcemaps for all styles.
|
||||
*/
|
||||
styles?: boolean;
|
||||
/**
|
||||
* Resolve vendor packages sourcemaps.
|
||||
*/
|
||||
vendor?: boolean;
|
||||
/**
|
||||
* Output sourcemaps used for error reporting tools.
|
||||
*/
|
||||
hidden?: boolean;
|
||||
/**
|
||||
* Output sourcemaps for all scripts.
|
||||
*/
|
||||
scripts?: boolean;
|
||||
/**
|
||||
* Output sourcemaps for all styles.
|
||||
*/
|
||||
styles?: boolean;
|
||||
/**
|
||||
* Resolve vendor packages sourcemaps.
|
||||
*/
|
||||
vendor?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Options to pass to style preprocessors.
|
||||
*/
|
||||
export interface StylePreprocessorOptions {
|
||||
/**
|
||||
* Paths to include. Paths will be resolved to project root.
|
||||
*/
|
||||
includePaths?: string[];
|
||||
/**
|
||||
* Paths to include. Paths will be resolved to project root.
|
||||
*/
|
||||
includePaths?: string[];
|
||||
}
|
||||
|
||||
@ -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,24 +71,22 @@ 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 => {
|
||||
const [agentId, version] = browser.split(' ');
|
||||
return !this._supportedBrowsers.some(browser => {
|
||||
const [agentId, version] = browser.split(' ');
|
||||
|
||||
const browserData = data.stats[agentId];
|
||||
const featureStatus = (browserData && browserData[version]) as string | undefined;
|
||||
const browserData = data.stats[agentId];
|
||||
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'
|
||||
// as for such cases we should polyfill these features as needed
|
||||
return !featureStatus || !criteria.includes(featureStatus.charAt(0));
|
||||
});
|
||||
// We are only interested in the first character
|
||||
// Ex: when 'a #4 #5', we only need to check for 'a'
|
||||
// as for such cases we should polyfill these features as needed
|
||||
return !featureStatus || !criteria.includes(featureStatus.charAt(0));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
});
|
||||
|
||||
@ -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)
|
||||
);
|
||||
}
|
||||
|
||||
@ -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');
|
||||
|
||||
@ -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,47 +39,46 @@ export function normalizeAssetPatterns(
|
||||
return [];
|
||||
}
|
||||
|
||||
return assetPatterns
|
||||
.map(assetPattern => {
|
||||
// Normalize string asset patterns to objects.
|
||||
if (typeof assetPattern === 'string') {
|
||||
const assetPath = normalize(assetPattern);
|
||||
const resolvedAssetPath = resolve(root, assetPath);
|
||||
return assetPatterns.map(assetPattern => {
|
||||
// Normalize string asset patterns to objects.
|
||||
if (typeof assetPattern === 'string') {
|
||||
const assetPath = normalize(assetPattern);
|
||||
const resolvedAssetPath = resolve(root, assetPath);
|
||||
|
||||
// Check if the string asset is within sourceRoot.
|
||||
if (!resolvedAssetPath.startsWith(resolvedSourceRoot)) {
|
||||
throw new MissingAssetSourceRootException(assetPattern);
|
||||
}
|
||||
|
||||
let glob: string, input: Path, output: Path;
|
||||
let isDirectory = false;
|
||||
|
||||
try {
|
||||
isDirectory = host.isDirectory(resolvedAssetPath);
|
||||
} catch {
|
||||
isDirectory = true;
|
||||
}
|
||||
|
||||
if (isDirectory) {
|
||||
// Folders get a recursive star glob.
|
||||
glob = '**/*';
|
||||
// Input directory is their original path.
|
||||
input = assetPath;
|
||||
} else {
|
||||
// Files are their own glob.
|
||||
glob = basename(assetPath);
|
||||
// Input directory is their original dirname.
|
||||
input = dirname(assetPath);
|
||||
}
|
||||
|
||||
// Output directory for both is the relative path from source root to input.
|
||||
output = relative(resolvedSourceRoot, resolve(root, input));
|
||||
|
||||
// Return the asset pattern in object format.
|
||||
return { glob, input, output };
|
||||
} else {
|
||||
// It's already an AssetPatternObject, no need to convert.
|
||||
return assetPattern;
|
||||
// Check if the string asset is within sourceRoot.
|
||||
if (!resolvedAssetPath.startsWith(resolvedSourceRoot)) {
|
||||
throw new MissingAssetSourceRootException(assetPattern);
|
||||
}
|
||||
});
|
||||
|
||||
let glob: string, input: Path, output: Path;
|
||||
let isDirectory = false;
|
||||
|
||||
try {
|
||||
isDirectory = host.isDirectory(resolvedAssetPath);
|
||||
} catch {
|
||||
isDirectory = true;
|
||||
}
|
||||
|
||||
if (isDirectory) {
|
||||
// Folders get a recursive star glob.
|
||||
glob = '**/*';
|
||||
// Input directory is their original path.
|
||||
input = assetPath;
|
||||
} else {
|
||||
// Files are their own glob.
|
||||
glob = basename(assetPath);
|
||||
// Input directory is their original dirname.
|
||||
input = dirname(assetPath);
|
||||
}
|
||||
|
||||
// Output directory for both is the relative path from source root to input.
|
||||
output = relative(resolvedSourceRoot, resolve(root, input));
|
||||
|
||||
// Return the asset pattern in object format.
|
||||
return { glob, input, output };
|
||||
} else {
|
||||
// It's already an AssetPatternObject, no need to convert.
|
||||
return assetPattern;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -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 & {
|
||||
sourceMap: SourceMapClass;
|
||||
assets: AssetPatternClass[];
|
||||
fileReplacements: NormalizedFileReplacement[];
|
||||
optimization: OptimizationClass;
|
||||
};
|
||||
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 || []
|
||||
};
|
||||
}
|
||||
|
||||
@ -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?
|
||||
|
||||
@ -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
|
||||
};
|
||||
}
|
||||
|
||||
@ -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
|
||||
};
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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) => {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@ -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'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
// 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
|
||||
? {}
|
||||
: {
|
||||
...// 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
|
||||
? {}
|
||||
: {
|
||||
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 };
|
||||
|
||||
@ -95,16 +95,14 @@ export function getBaseWebpackPartial(
|
||||
}
|
||||
|
||||
if (options.extractLicenses) {
|
||||
extraPlugins.push(
|
||||
new LicenseWebpackPlugin({
|
||||
stats: {
|
||||
warnings: false,
|
||||
errors: false,
|
||||
},
|
||||
perChunkOutput: false,
|
||||
outputFilename: `3rdpartylicenses.txt`,
|
||||
}) as any
|
||||
);
|
||||
extraPlugins.push(new LicenseWebpackPlugin({
|
||||
stats: {
|
||||
warnings: false,
|
||||
errors: false
|
||||
},
|
||||
perChunkOutput: false,
|
||||
outputFilename: `3rdpartylicenses.txt`
|
||||
}) as any);
|
||||
}
|
||||
|
||||
// process asset entries
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user