From 850c3ac352a4c6b893f49f0262fa39afc87ee769 Mon Sep 17 00:00:00 2001 From: Alan Date: Tue, 2 Jul 2019 12:32:09 +0200 Subject: [PATCH 1/2] feat(@angular-devkit/build-angular): deprecate scripts and styles `lazy` option in favor of`inject` The lazy option inside the script and style option is confusing as this option doesn't lazy load a bundle but rather it doesn't inject/reference the script in the HTML. While this option is an enabler for lazy loading, the users will still need to handle on how how this bundle will be lazy loaded. There are also potential use cases beyond lazy loading for the option. Closes #14814 --- packages/angular/cli/lib/config/schema.json | 10 ++ .../models/webpack-configs/common.ts | 154 +++++++++-------- .../models/webpack-configs/styles.ts | 118 +++++++------ .../models/webpack-configs/utils.ts | 40 +++-- .../utilities/package-chunk-sort.ts | 16 +- .../build_angular/src/browser/schema.json | 8 +- .../build_angular/src/karma/schema.json | 8 +- .../test/browser/output-hashing_spec_large.ts | 34 ++-- .../test/browser/scripts-array_spec_large.ts | 4 +- .../test/browser/styles_spec_large.ts | 83 ++++----- .../e2e/tests/basic/scripts-array.ts | 163 ++++++++++-------- .../e2e/tests/basic/styles-array.ts | 88 ++++++---- .../e2e/tests/build/styles/extract-css.ts | 160 ++++++++++------- 13 files changed, 511 insertions(+), 375 deletions(-) diff --git a/packages/angular/cli/lib/config/schema.json b/packages/angular/cli/lib/config/schema.json index cf93bf4e4dad..8a3117b98f1d 100644 --- a/packages/angular/cli/lib/config/schema.json +++ b/packages/angular/cli/lib/config/schema.json @@ -995,6 +995,11 @@ "type": "boolean", "description": "If the bundle will be lazy loaded.", "default": false + }, + "inject": { + "type": "boolean", + "description": "If the bundle will be referenced in the HTML file.", + "default": true } }, "additionalProperties": false, @@ -1516,6 +1521,11 @@ "type": "boolean", "description": "If the bundle will be lazy loaded.", "default": false + }, + "inject": { + "type": "boolean", + "description": "If the bundle will be referenced in the HTML file.", + "default": true } }, "additionalProperties": false, diff --git a/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/common.ts b/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/common.ts index ec8318348e78..a553b2d98508 100644 --- a/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/common.ts +++ b/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/common.ts @@ -72,11 +72,9 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration { tsConfig.options.target || ScriptTarget.ES5, ); if ((buildOptions.scriptTargetOverride || tsConfig.options.target) === ScriptTarget.ES5) { - if (buildOptions.es5BrowserSupport || - ( - buildOptions.es5BrowserSupport === undefined && - buildBrowserFeatures.isEs5SupportNeeded() - ) + if ( + buildOptions.es5BrowserSupport || + (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 @@ -91,7 +89,7 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration { : [noModuleScript]; } - // For differential loading we don't need to generate a seperate polyfill file + // For 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 = buildBrowserFeatures.isDifferentialLoadingNeeded() ? 'polyfills' @@ -120,9 +118,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`), - })); + extraPlugins.push( + new debug.ProfilingPlugin({ + outputPath: path.resolve(root, `chrome-profiler-events${targetInFileName}.json`), + }), + ); } // determine hashing format @@ -130,50 +130,54 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration { // process global scripts if (buildOptions.scripts.length > 0) { - const globalScriptsByBundleName = normalizeExtraEntryPoints(buildOptions.scripts, 'scripts') - .reduce((prev: { bundleName: string, paths: string[], lazy: 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.lazy && !curr.lazy) { - // All entries have to be lazy for the bundle to be lazy. - throw new Error(`The ${curr.bundleName} bundle is mixing lazy and non-lazy scripts.`); - } - - existingEntry.paths.push(resolvedPath); - } else { - prev.push({ - bundleName, - paths: [resolvedPath], - lazy: curr.lazy || false, - }); + 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.`, + ); } - return prev; - }, []); + existingEntry.paths.push(resolvedPath); + } else { + prev.push({ + bundleName, + paths: [resolvedPath], + inject: curr.inject, + }); + } + return prev; + }, []); // Add a new asset for each entry. - globalScriptsByBundleName.forEach((script) => { + globalScriptsByBundleName.forEach(script => { // Lazy scripts don't get a hash, otherwise they can't be loaded by name. - const hash = script.lazy ? '' : hashFormat.script; + const hash = script.inject ? hashFormat.script : ''; const bundleName = script.bundleName; - extraPlugins.push(new ScriptsWebpackPlugin({ - name: bundleName, - sourceMap: scriptsSourceMap, - filename: `${path.basename(bundleName)}${hash}.js`, - scripts: script.paths, - basePath: projectRoot, - })); + extraPlugins.push( + new ScriptsWebpackPlugin({ + name: bundleName, + sourceMap: scriptsSourceMap, + filename: `${path.basename(bundleName)}${hash}.js`, + scripts: script.paths, + 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 + '/'; @@ -198,8 +202,10 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration { const copyWebpackPluginOptions = { ignore: ['.gitkeep', '**/.DS_Store', '**/Thumbs.db'] }; - const copyWebpackPluginInstance = new CopyWebpackPlugin(copyWebpackPluginPatterns, - copyWebpackPluginOptions); + const copyWebpackPluginInstance = new CopyWebpackPlugin( + copyWebpackPluginPatterns, + copyWebpackPluginOptions, + ); extraPlugins.push(copyWebpackPluginInstance); } @@ -208,20 +214,24 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration { } if (buildOptions.showCircularDependencies) { - extraPlugins.push(new CircularDependencyPlugin({ - exclude: /([\\\/]node_modules[\\\/])|(ngfactory\.js$)/, - })); + extraPlugins.push( + new CircularDependencyPlugin({ + exclude: /([\\\/]node_modules[\\\/])|(ngfactory\.js$)/, + }), + ); } if (buildOptions.statsJson) { - extraPlugins.push(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); - }); - } - }); + extraPlugins.push( + 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); + }); + } + })(), + ); } if (buildOptions.namedChunks) { @@ -266,7 +276,7 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration { : 'rxjs/_esm5/path-mapping'; const rxPaths = require(require.resolve(rxjsPathMappingImport, { paths: [projectRoot] })); alias = rxPaths(nodeModules); - } catch { } + } catch {} const extraMinimizers = []; if (stylesOptimization) { @@ -274,7 +284,7 @@ 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), }), ); } @@ -307,15 +317,18 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration { }, // On server, we don't want to compress anything. We still set the ngDevMode = false for it // to remove dev code, and ngI18nClosureMode to remove Closure compiler i18n code - compress: (buildOptions.platform == 'server' ? { - global_defs: angularGlobalDefinitions, - } : { - pure_getters: buildOptions.buildOptimizer, - // PURE comments work best with 3 passes. - // See https://github.com/webpack/webpack/issues/2899#issuecomment-317425926. - passes: buildOptions.buildOptimizer ? 3 : 1, - global_defs: angularGlobalDefinitions, - }), + compress: + buildOptions.platform == 'server' + ? { + global_defs: angularGlobalDefinitions, + } + : { + pure_getters: buildOptions.buildOptimizer, + // PURE comments work best with 3 passes. + // See https://github.com/webpack/webpack/issues/2899#issuecomment-317425926. + passes: buildOptions.buildOptimizer ? 3 : 1, + global_defs: angularGlobalDefinitions, + }, // We also want to avoid mangling on server. ...(buildOptions.platform == 'server' ? { mangle: false } : {}), }; @@ -330,8 +343,10 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration { ); } - if (wco.tsConfig.options.target !== undefined && - wco.tsConfig.options.target >= ScriptTarget.ES2017) { + if ( + wco.tsConfig.options.target !== undefined && + wco.tsConfig.options.target >= ScriptTarget.ES2017 + ) { wco.logger.warn(tags.stripIndent` WARNING: Zone.js does not support native async/await in ES2017. These blocks are not intercepted by zone.js and will not triggering change detection. @@ -340,18 +355,13 @@ 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', - ], + modules: [wco.tsConfig.options.baseUrl || projectRoot, 'node_modules'], alias, }, resolveLoader: { diff --git a/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/styles.ts b/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/styles.ts index b7e13bb67203..12d99fbef39a 100644 --- a/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/styles.ts +++ b/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/styles.ts @@ -34,7 +34,7 @@ const postcssImports = require('postcss-import'); * require('node-sass') * require('sass-loader') */ - +// tslint:disable-next-line:no-big-function export function getStylesConfig(wco: WebpackConfigOptions) { const { root, buildOptions } = wco; const entryPoints: { [key: string]: string[] } = {}; @@ -46,10 +46,10 @@ export function getStylesConfig(wco: WebpackConfigOptions) { // Determine hashing format. const hashFormat = getOutputHashFormat(buildOptions.outputHashing as string); - const postcssPluginCreator = function (loader: webpack.loader.LoaderContext) { + const postcssPluginCreator = function(loader: webpack.loader.LoaderContext) { return [ postcssImports({ - resolve: (url: string) => url.startsWith('~') ? url.substr(1) : url, + resolve: (url: string) => (url.startsWith('~') ? url.substr(1) : url), load: (filename: string) => { return new Promise((resolve, reject) => { loader.fs.readFile(filename, (err: Error, data: Buffer) => { @@ -81,12 +81,14 @@ export function getStylesConfig(wco: WebpackConfigOptions) { const includePaths: string[] = []; let lessPathOptions: { paths?: string[] } = {}; - if (buildOptions.stylePreprocessorOptions - && buildOptions.stylePreprocessorOptions.includePaths - && buildOptions.stylePreprocessorOptions.includePaths.length > 0 + if ( + buildOptions.stylePreprocessorOptions && + buildOptions.stylePreprocessorOptions.includePaths && + buildOptions.stylePreprocessorOptions.includePaths.length > 0 ) { buildOptions.stylePreprocessorOptions.includePaths.forEach((includePath: string) => - includePaths.push(path.resolve(root, includePath))); + includePaths.push(path.resolve(root, includePath)), + ); lessPathOptions = { paths: includePaths, }; @@ -105,8 +107,8 @@ export function getStylesConfig(wco: WebpackConfigOptions) { entryPoints[style.bundleName] = [resolvedPath]; } - // Add lazy styles to the list. - if (style.lazy) { + // Add non injected styles to the list. + if (!style.inject) { chunkNames.push(style.bundleName); } @@ -131,7 +133,7 @@ export function getStylesConfig(wco: WebpackConfigOptions) { try { // tslint:disable-next-line:no-implicit-dependencies fiber = require('fibers'); - } catch { } + } catch {} } // set base rules to derive final rules from @@ -139,38 +141,44 @@ export function getStylesConfig(wco: WebpackConfigOptions) { { test: /\.css$/, use: [] }, { test: /\.scss$|\.sass$/, - use: [{ - loader: 'sass-loader', - options: { - implementation: sassImplementation, - fiber, - sourceMap: cssSourceMap, - // bootstrap-sass requires a minimum precision of 8 - precision: 8, - includePaths, + use: [ + { + loader: 'sass-loader', + options: { + implementation: sassImplementation, + fiber, + sourceMap: cssSourceMap, + // bootstrap-sass requires a minimum precision of 8 + precision: 8, + includePaths, + }, }, - }], + ], }, { test: /\.less$/, - use: [{ - loader: 'less-loader', - options: { - sourceMap: cssSourceMap, - javascriptEnabled: true, - ...lessPathOptions, + use: [ + { + loader: 'less-loader', + options: { + sourceMap: cssSourceMap, + javascriptEnabled: true, + ...lessPathOptions, + }, }, - }], + ], }, { test: /\.styl$/, - use: [{ - loader: 'stylus-loader', - options: { - sourceMap: cssSourceMap, - paths: includePaths, + use: [ + { + loader: 'stylus-loader', + options: { + sourceMap: cssSourceMap, + paths: includePaths, + }, }, - }], + ], }, ]; @@ -194,28 +202,30 @@ export function getStylesConfig(wco: WebpackConfigOptions) { // load global css as css files if (globalStylePaths.length > 0) { - rules.push(...baseRules.map(({ test, use }) => { - return { - include: globalStylePaths, - test, - use: [ - buildOptions.extractCss ? MiniCssExtractPlugin.loader : 'style-loader', - RawCssLoader, - { - loader: 'postcss-loader', - options: { - ident: buildOptions.extractCss ? 'extracted' : 'embedded', - plugins: postcssPluginCreator, - sourceMap: cssSourceMap - && !buildOptions.extractCss - && !buildOptions.sourceMap.hidden - ? 'inline' : cssSourceMap, + rules.push( + ...baseRules.map(({ test, use }) => { + return { + include: globalStylePaths, + test, + use: [ + buildOptions.extractCss ? MiniCssExtractPlugin.loader : 'style-loader', + RawCssLoader, + { + loader: 'postcss-loader', + options: { + ident: buildOptions.extractCss ? 'extracted' : 'embedded', + plugins: postcssPluginCreator, + sourceMap: + cssSourceMap && !buildOptions.extractCss && !buildOptions.sourceMap.hidden + ? 'inline' + : cssSourceMap, + }, }, - }, - ...(use as webpack.Loader[]), - ], - }; - })); + ...(use as webpack.Loader[]), + ], + }; + }), + ); } if (buildOptions.extractCss) { diff --git a/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/utils.ts b/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/utils.ts index e0afd2ad53ef..d9a3e5da7594 100644 --- a/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/utils.ts +++ b/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/utils.ts @@ -23,32 +23,45 @@ export interface HashFormat { export function getOutputHashFormat(option: string, length = 20): HashFormat { /* tslint:disable:max-line-length */ const hashFormats: { [option: string]: HashFormat } = { - none: { chunk: '', extract: '', file: '' , script: '' }, - media: { chunk: '', extract: '', file: `.[hash:${length}]`, script: '' }, - bundles: { chunk: `.[chunkhash:${length}]`, extract: `.[contenthash:${length}]`, file: '' , script: `.[hash:${length}]` }, - all: { chunk: `.[chunkhash:${length}]`, extract: `.[contenthash:${length}]`, file: `.[hash:${length}]`, script: `.[hash:${length}]` }, + none: { chunk: '', extract: '', file: '', script: '' }, + media: { chunk: '', extract: '', file: `.[hash:${length}]`, script: '' }, + bundles: { + chunk: `.[chunkhash:${length}]`, + extract: `.[contenthash:${length}]`, + file: '', + script: `.[hash:${length}]`, + }, + all: { + chunk: `.[chunkhash:${length}]`, + extract: `.[contenthash:${length}]`, + file: `.[hash:${length}]`, + script: `.[hash:${length}]`, + }, }; /* tslint:enable:max-line-length */ return hashFormats[option] || hashFormats['none']; } -export type NormalizedEntryPoint = ExtraEntryPointClass & { bundleName: string }; +// todo: replace with Omit when we update to TS 3.5 +type Omit = Pick>; +export type NormalizedEntryPoint = Required>; export function normalizeExtraEntryPoints( extraEntryPoints: ExtraEntryPoint[], - defaultBundleName: string + defaultBundleName: string, ): NormalizedEntryPoint[] { return extraEntryPoints.map(entry => { let normalizedEntry; - if (typeof entry === 'string') { - normalizedEntry = { input: entry, lazy: false, bundleName: defaultBundleName }; + normalizedEntry = { input: entry, inject: true, bundleName: defaultBundleName }; } else { + const { lazy, inject = true, ...newEntry } = entry; + const injectNormalized = entry.lazy !== undefined ? !entry.lazy : inject; let bundleName; if (entry.bundleName) { bundleName = entry.bundleName; - } else if (entry.lazy) { + } 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, '')), @@ -57,11 +70,11 @@ export function normalizeExtraEntryPoints( bundleName = defaultBundleName; } - normalizedEntry = {...entry, bundleName}; + normalizedEntry = { ...newEntry, inject: injectNormalized, bundleName }; } return normalizedEntry; - }) + }); } export function getSourceMapDevTool( @@ -93,8 +106,9 @@ export function getEsVersionForFileName( scriptTargetOverride: ScriptTarget | undefined, esVersionInFileName = false, ): string { - return scriptTargetOverride && esVersionInFileName ? - '-' + ScriptTarget[scriptTargetOverride].toLowerCase() : ''; + return scriptTargetOverride && esVersionInFileName + ? '-' + ScriptTarget[scriptTargetOverride].toLowerCase() + : ''; } export function isPolyfillsEntry(name: string) { diff --git a/packages/angular_devkit/build_angular/src/angular-cli-files/utilities/package-chunk-sort.ts b/packages/angular_devkit/build_angular/src/angular-cli-files/utilities/package-chunk-sort.ts index 3b2262575d70..29e5e17ac34e 100644 --- a/packages/angular_devkit/build_angular/src/angular-cli-files/utilities/package-chunk-sort.ts +++ b/packages/angular_devkit/build_angular/src/angular-cli-files/utilities/package-chunk-sort.ts @@ -8,17 +8,17 @@ import { ExtraEntryPoint } from '../../browser/schema'; import { normalizeExtraEntryPoints } from '../models/webpack-configs/utils'; -export function generateEntryPoints( - appConfig: { styles: ExtraEntryPoint[], scripts: ExtraEntryPoint[] }, -) { - +export function generateEntryPoints(appConfig: { + styles: ExtraEntryPoint[]; + scripts: ExtraEntryPoint[]; +}) { // Add all styles/scripts, except lazy-loaded ones. const extraEntryPoints = ( extraEntryPoints: ExtraEntryPoint[], defaultBundleName: string, ): string[] => { const entryPoints = normalizeExtraEntryPoints(extraEntryPoints, defaultBundleName) - .filter(entry => !entry.lazy) + .filter(entry => entry.inject) .map(entry => entry.bundleName); // remove duplicates @@ -35,9 +35,9 @@ export function generateEntryPoints( 'main', ]; - const duplicates = [...new Set( - entryPoints.filter(x => entryPoints.indexOf(x) !== entryPoints.lastIndexOf(x)), - )]; + const duplicates = [ + ...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(`', '`)}'.`); diff --git a/packages/angular_devkit/build_angular/src/browser/schema.json b/packages/angular_devkit/build_angular/src/browser/schema.json index 57122978fea1..12967f11c7d0 100644 --- a/packages/angular_devkit/build_angular/src/browser/schema.json +++ b/packages/angular_devkit/build_angular/src/browser/schema.json @@ -428,7 +428,13 @@ "lazy": { "type": "boolean", "description": "If the bundle will be lazy loaded.", - "default": false + "default": false, + "x-deprecated": "Use 'inject' option with 'false' value instead." + }, + "inject": { + "type": "boolean", + "description": "If the bundle will be referenced in the HTML file.", + "default": true } }, "additionalProperties": false, diff --git a/packages/angular_devkit/build_angular/src/karma/schema.json b/packages/angular_devkit/build_angular/src/karma/schema.json index f5b8b64fddab..9bf96efe307e 100644 --- a/packages/angular_devkit/build_angular/src/karma/schema.json +++ b/packages/angular_devkit/build_angular/src/karma/schema.json @@ -258,7 +258,13 @@ "lazy": { "type": "boolean", "description": "If the bundle will be lazy loaded.", - "default": false + "default": false, + "x-deprecated": "Use 'inject' option with 'false' value instead." + }, + "inject": { + "type": "boolean", + "description": "If the bundle will be referenced in the HTML file.", + "default": true } }, "additionalProperties": false, diff --git a/packages/angular_devkit/build_angular/test/browser/output-hashing_spec_large.ts b/packages/angular_devkit/build_angular/test/browser/output-hashing_spec_large.ts index 4ca1779f3448..c99cad5b62bf 100644 --- a/packages/angular_devkit/build_angular/test/browser/output-hashing_spec_large.ts +++ b/packages/angular_devkit/build_angular/test/browser/output-hashing_spec_large.ts @@ -9,7 +9,11 @@ import { Architect } from '@angular-devkit/architect'; import { normalize } from '@angular-devkit/core'; import { - browserBuild, createArchitect, host, lazyModuleFiles, lazyModuleStringImport, + browserBuild, + createArchitect, + host, + lazyModuleFiles, + lazyModuleStringImport, } from '../utils'; describe('Browser Builder output hashing', () => { @@ -28,13 +32,16 @@ describe('Browser Builder output hashing', () => { function generateFileHashMap(): Map { const hashes = new Map(); - host.scopedSync().list(normalize('./dist')).forEach(name => { - const matches = name.match(OUTPUT_RE); - if (matches) { - const [, module, hash] = matches; - hashes.set(module, hash); - } - }); + host + .scopedSync() + .list(normalize('./dist')) + .forEach(name => { + const matches = name.match(OUTPUT_RE); + if (matches) { + const [, module, hash] = matches; + hashes.set(module, hash); + } + }); return hashes; } @@ -48,7 +55,8 @@ describe('Browser Builder output hashing', () => { if (hash == oldHashes.get(module)) { if (shouldChange.includes(module)) { throw new Error( - `Module "${module}" did not change hash (${hash}), but was expected to.`); + `Module "${module}" did not change hash (${hash}), but was expected to.`, + ); } } else if (!shouldChange.includes(module)) { throw new Error(`Module "${module}" changed hash (${hash}), but was not expected to.`); @@ -153,11 +161,11 @@ describe('Browser Builder output hashing', () => { expect(host.fileMatchExists('dist', /spectrum\.[0-9a-f]{20}\.png/)).toBeFalsy(); }); - it('does not hash lazy styles', async () => { + it('does not hash non injected styles', async () => { const overrides = { outputHashing: 'all', extractCss: true, - styles: [{ input: 'src/styles.css', lazy: true }], + styles: [{ input: 'src/styles.css', inject: false }], }; await browserBuild(architect, host, target, overrides); @@ -168,12 +176,12 @@ describe('Browser Builder output hashing', () => { expect(host.scopedSync().exists(normalize('dist/styles.css.map'))).toBe(true); }); - it('does not hash lazy styles when optimization is enabled', async () => { + it('does not hash non injected styles when optimization is enabled', async () => { const overrides = { outputHashing: 'all', extractCss: true, optimization: true, - styles: [{ input: 'src/styles.css', lazy: true }], + styles: [{ input: 'src/styles.css', inject: false }], }; await browserBuild(architect, host, target, overrides); diff --git a/packages/angular_devkit/build_angular/test/browser/scripts-array_spec_large.ts b/packages/angular_devkit/build_angular/test/browser/scripts-array_spec_large.ts index 48aa593dbdc9..615c3bf8b7c3 100644 --- a/packages/angular_devkit/build_angular/test/browser/scripts-array_spec_large.ts +++ b/packages/angular_devkit/build_angular/test/browser/scripts-array_spec_large.ts @@ -33,9 +33,9 @@ describe('Browser Builder scripts array', () => { 'src/binput-script.js', 'src/ainput-script.js', 'src/cinput-script.js', - { input: 'src/lazy-script.js', bundleName: 'lazy-script', lazy: true }, + { input: 'src/lazy-script.js', bundleName: 'lazy-script', inject: false }, { input: 'src/pre-rename-script.js', bundleName: 'renamed-script' }, - { input: 'src/pre-rename-lazy-script.js', bundleName: 'renamed-lazy-script', lazy: true }, + { input: 'src/pre-rename-lazy-script.js', bundleName: 'renamed-lazy-script', inject: false }, ]; const target = { project: 'app', target: 'build' }; diff --git a/packages/angular_devkit/build_angular/test/browser/styles_spec_large.ts b/packages/angular_devkit/build_angular/test/browser/styles_spec_large.ts index 242bcddcd32a..0bbfd62e544b 100644 --- a/packages/angular_devkit/build_angular/test/browser/styles_spec_large.ts +++ b/packages/angular_devkit/build_angular/test/browser/styles_spec_large.ts @@ -11,7 +11,6 @@ import { Architect } from '@angular-devkit/architect'; import { logging, normalize, tags } from '@angular-devkit/core'; import { browserBuild, createArchitect, host } from '../utils'; - describe('Browser Builder styles', () => { const extensionsWithImportSupport = ['css', 'scss', 'less', 'styl']; const extensionsWithVariableSupport = ['scss', 'less', 'styl']; @@ -32,9 +31,9 @@ describe('Browser Builder styles', () => { it('supports global styles', async () => { const styles = [ 'src/input-style.css', - { input: 'src/lazy-style.css', bundleName: 'lazy-style', lazy: true }, + { input: 'src/lazy-style.css', bundleName: 'lazy-style', inject: false }, { input: 'src/pre-rename-style.css', bundleName: 'renamed-style' }, - { input: 'src/pre-rename-lazy-style.css', bundleName: 'renamed-lazy-style', lazy: true }, + { input: 'src/pre-rename-lazy-style.css', bundleName: 'renamed-lazy-style', inject: false }, ] as {}; const cssMatches: { [path: string]: string } = { 'styles.css': '.input-style', @@ -43,8 +42,9 @@ describe('Browser Builder styles', () => { 'renamed-lazy-style.css': '.pre-rename-lazy-style', }; const cssIndexMatches: { [path: string]: string } = { - 'index.html': '' - + '', + 'index.html': + '' + + '', }; const jsMatches: { [path: string]: string } = { 'styles.js': '.input-style', @@ -53,12 +53,13 @@ describe('Browser Builder styles', () => { 'renamed-lazy-style.js': '.pre-rename-lazy-style', }; const jsIndexMatches: { [path: string]: string } = { - 'index.html': '' - + '' - + '' - + '' - + '' - + '', + 'index.html': + '' + + '' + + '' + + '' + + '' + + '', }; host.writeMultipleFiles({ @@ -144,16 +145,16 @@ describe('Browser Builder styles', () => { const matches: { [path: string]: RegExp } = { 'styles.css': new RegExp( // The global style should be there - /p\s*{\s*background-color: #f00;\s*}(.|\n|\r)*/.source - // The global style via import should be there - + /body\s*{\s*background-color: #00f;\s*}/.source, + /p\s*{\s*background-color: #f00;\s*}(.|\n|\r)*/.source + + // The global style via import should be there + /body\s*{\s*background-color: #00f;\s*}/.source, ), 'styles.css.map': /"mappings":".+"/, 'main.js': new RegExp( // The component style should be there - /h1(.|\n|\r)*background:\s*#000(.|\n|\r)*/.source - // The component style via import should be there - + /.outer(.|\n|\r)*.inner(.|\n|\r)*background:\s*#[fF]+/.source, + /h1(.|\n|\r)*background:\s*#000(.|\n|\r)*/.source + + // The component style via import should be there + /.outer(.|\n|\r)*.inner(.|\n|\r)*background:\s*#[fF]+/.source, ), }; @@ -188,8 +189,11 @@ describe('Browser Builder styles', () => { @import "@angular/material/prebuilt-themes/indigo-pink.css"; `, }); - host.replaceInFile('src/app/app.component.ts', './app.component.css', - `./app.component.${ext}`); + host.replaceInFile( + 'src/app/app.component.ts', + './app.component.css', + `./app.component.${ext}`, + ); const overrides = { extractCss: true, @@ -213,7 +217,6 @@ describe('Browser Builder styles', () => { extensionsWithVariableSupport.forEach(ext => { it(`supports ${ext} includePaths`, async () => { - let variableAssignment = ''; let variablereference = ''; if (ext === 'scss') { @@ -244,8 +247,11 @@ describe('Browser Builder styles', () => { 'main.js': /h2.*{.*color: #f00;.*}/, }; - host.replaceInFile('src/app/app.component.ts', './app.component.css', - `./app.component.${ext}`); + host.replaceInFile( + 'src/app/app.component.ts', + './app.component.css', + `./app.component.${ext}`, + ); const overrides = { extractCss: true, @@ -291,7 +297,7 @@ describe('Browser Builder styles', () => { /* normal-comment */ /*! important-comment */ div { flex: 1 }`, - 'browserslist': 'IE 10', + browserslist: 'IE 10', }); const overrides = { extractCss: true, optimization: false }; @@ -299,8 +305,7 @@ describe('Browser Builder styles', () => { expect(await files['styles.css']).toContain(tags.stripIndents` /* normal-comment */ /*! important-comment */ - div { -ms-flex: 1; flex: 1 }`, - ); + div { -ms-flex: 1; flex: 1 }`); }); it(`minimizes css`, async () => { @@ -345,14 +350,12 @@ describe('Browser Builder styles', () => { expect(main).toContain(`url('/assets/component-img-absolute.svg')`); expect(main).toContain(`url('component-img-relative.png')`); - expect(host.scopedSync().exists(normalize('dist/assets/global-img-absolute.svg'))) - .toBe(true); - expect(host.scopedSync().exists(normalize('dist/global-img-relative.png'))) - .toBe(true); - expect(host.scopedSync().exists(normalize('dist/assets/component-img-absolute.svg'))) - .toBe(true); - expect(host.scopedSync().exists(normalize('dist/component-img-relative.png'))) - .toBe(true); + expect(host.scopedSync().exists(normalize('dist/assets/global-img-absolute.svg'))).toBe(true); + expect(host.scopedSync().exists(normalize('dist/global-img-relative.png'))).toBe(true); + expect(host.scopedSync().exists(normalize('dist/assets/component-img-absolute.svg'))).toBe( + true, + ); + expect(host.scopedSync().exists(normalize('dist/component-img-relative.png'))).toBe(true); // Check urls with deploy-url scheme are used as is. files = (await browserBuild(architect, host, target, { @@ -456,14 +459,12 @@ describe('Browser Builder styles', () => { expect(styles).toContain(`url('global-img-relative.png')`); expect(main).toContain(`url('/assets/component-img-absolute.svg')`); expect(main).toContain(`url('component-img-relative.png')`); - expect(host.scopedSync().exists(normalize('dist/assets/global-img-absolute.svg'))) - .toBe(true); - expect(host.scopedSync().exists(normalize('dist/global-img-relative.png'))) - .toBe(true); - expect(host.scopedSync().exists(normalize('dist/assets/component-img-absolute.svg'))) - .toBe(true); - expect(host.scopedSync().exists(normalize('dist/component-img-relative.png'))) - .toBe(true); + expect(host.scopedSync().exists(normalize('dist/assets/global-img-absolute.svg'))).toBe(true); + expect(host.scopedSync().exists(normalize('dist/global-img-relative.png'))).toBe(true); + expect(host.scopedSync().exists(normalize('dist/assets/component-img-absolute.svg'))).toBe( + true, + ); + expect(host.scopedSync().exists(normalize('dist/component-img-relative.png'))).toBe(true); // Check urls with deploy-url scheme are used as is. files = (await browserBuild(architect, host, target, { diff --git a/tests/legacy-cli/e2e/tests/basic/scripts-array.ts b/tests/legacy-cli/e2e/tests/basic/scripts-array.ts index 061b36f38d03..a3a59832068f 100644 --- a/tests/legacy-cli/e2e/tests/basic/scripts-array.ts +++ b/tests/legacy-cli/e2e/tests/basic/scripts-array.ts @@ -4,7 +4,7 @@ import { writeMultipleFiles, expectFileToMatch, appendToFile, - expectFileMatchToExist + expectFileMatchToExist, } from '../../utils/fs'; import { ng } from '../../utils/process'; import { updateJsonFile } from '../../utils/project'; @@ -13,46 +13,58 @@ import * as fs from 'fs'; import * as path from 'path'; // tslint:disable:max-line-length -export default function () { - return writeMultipleFiles({ - 'src/string-script.js': 'console.log(\'string-script\'); var number = 1+1;', - 'src/zstring-script.js': 'console.log(\'zstring-script\');', - 'src/fstring-script.js': 'console.log(\'fstring-script\');', - 'src/ustring-script.js': 'console.log(\'ustring-script\');', - 'src/bstring-script.js': 'console.log(\'bstring-script\');', - 'src/astring-script.js': 'console.log(\'astring-script\');', - 'src/cstring-script.js': 'console.log(\'cstring-script\');', - 'src/input-script.js': 'console.log(\'input-script\');', - 'src/lazy-script.js': 'console.log(\'lazy-script\');', - 'src/pre-rename-script.js': 'console.log(\'pre-rename-script\');', - 'src/pre-rename-lazy-script.js': 'console.log(\'pre-rename-lazy-script\');', - }) - .then(() => appendToFile('src/main.ts', 'import \'./string-script.js\';')) - .then(() => updateJsonFile('angular.json', configJson => { - const appArchitect = configJson.projects['test-project'].architect; - appArchitect.build.options.scripts = [ - { input: 'src/string-script.js' }, - { input: 'src/zstring-script.js' }, - { input: 'src/fstring-script.js' }, - { input: 'src/ustring-script.js' }, - { input: 'src/bstring-script.js' }, - { input: 'src/astring-script.js' }, - { input: 'src/cstring-script.js' }, - { input: 'src/input-script.js' }, - { input: 'src/lazy-script.js', lazy: true }, - { input: 'src/pre-rename-script.js', bundleName: 'renamed-script' }, - { input: 'src/pre-rename-lazy-script.js', bundleName: 'renamed-lazy-script', lazy: true } - ]; - })) - .then(() => ng('build', '--extract-css')) - // files were created successfully - .then(() => expectFileToMatch('dist/test-project/scripts.js', 'string-script')) - .then(() => expectFileToMatch('dist/test-project/scripts.js', 'input-script')) - .then(() => expectFileToMatch('dist/test-project/lazy-script.js', 'lazy-script')) - .then(() => expectFileToMatch('dist/test-project/renamed-script.js', 'pre-rename-script')) - .then(() => expectFileToMatch('dist/test-project/renamed-lazy-script.js', 'pre-rename-lazy-script')) - // index.html lists the right bundles - .then(() => expectFileToMatch('dist/test-project/index.html', oneLineTrim` +export default function() { + return ( + writeMultipleFiles({ + 'src/string-script.js': "console.log('string-script'); var number = 1+1;", + 'src/zstring-script.js': "console.log('zstring-script');", + 'src/fstring-script.js': "console.log('fstring-script');", + 'src/ustring-script.js': "console.log('ustring-script');", + 'src/bstring-script.js': "console.log('bstring-script');", + 'src/astring-script.js': "console.log('astring-script');", + 'src/cstring-script.js': "console.log('cstring-script');", + 'src/input-script.js': "console.log('input-script');", + 'src/lazy-script.js': "console.log('lazy-script');", + 'src/pre-rename-script.js': "console.log('pre-rename-script');", + 'src/pre-rename-lazy-script.js': "console.log('pre-rename-lazy-script');", + }) + .then(() => appendToFile('src/main.ts', "import './string-script.js';")) + .then(() => + updateJsonFile('angular.json', configJson => { + const appArchitect = configJson.projects['test-project'].architect; + appArchitect.build.options.scripts = [ + { input: 'src/string-script.js' }, + { input: 'src/zstring-script.js' }, + { input: 'src/fstring-script.js' }, + { input: 'src/ustring-script.js' }, + { input: 'src/bstring-script.js' }, + { input: 'src/astring-script.js' }, + { input: 'src/cstring-script.js' }, + { input: 'src/input-script.js' }, + { input: 'src/lazy-script.js', inject: false }, + { input: 'src/pre-rename-script.js', bundleName: 'renamed-script' }, + { + input: 'src/pre-rename-lazy-script.js', + bundleName: 'renamed-lazy-script', + inject: false, + }, + ]; + }), + ) + .then(() => ng('build', '--extract-css')) + // files were created successfully + .then(() => expectFileToMatch('dist/test-project/scripts.js', 'string-script')) + .then(() => expectFileToMatch('dist/test-project/scripts.js', 'input-script')) + .then(() => expectFileToMatch('dist/test-project/lazy-script.js', 'lazy-script')) + .then(() => expectFileToMatch('dist/test-project/renamed-script.js', 'pre-rename-script')) + .then(() => + expectFileToMatch('dist/test-project/renamed-lazy-script.js', 'pre-rename-lazy-script'), + ) + // index.html lists the right bundles + .then(() => + expectFileToMatch( + 'dist/test-project/index.html', + oneLineTrim` @@ -63,37 +75,44 @@ export default function () { - `)) - // Ensure scripts can be separately imported from the app. - .then(() => expectFileToMatch('dist/test-project/main-es5.js', 'console.log(\'string-script\');')) - .then(() => expectFileToMatch('dist/test-project/main-es2015.js', 'console.log(\'string-script\');')); - // TODO(architect): disabled until --prod is added. - // Verify uglify, sourcemaps and hashes. Lazy scripts should not get hashes. - // .then(() => ng('build', '--prod', '--source-map')) - // .then(() => expectFileMatchToExist('dist', /scripts\.[0-9a-f]{20}\.js/)) - // .then(fileName => expectFileToMatch(`dist/${fileName}`, 'var number=2;')) - // .then(() => expectFileMatchToExist('dist', /scripts\.[0-9a-f]{20}\.js\.map/)) - // .then(() => expectFileMatchToExist('dist', /renamed-script\.[0-9a-f]{20}\.js/)) - // .then(() => expectFileMatchToExist('dist', /renamed-script\.[0-9a-f]{20}\.js.map/)) - // .then(() => expectFileToMatch('dist/test-project/lazy-script.js', 'lazy-script')) - // .then(() => expectFileToMatch('dist/test-project/enamed-lazy-script.js', 'pre-rename-lazy-script')) + `, + ), + ) + // Ensure scripts can be separately imported from the app. + .then(() => + expectFileToMatch('dist/test-project/main-es5.js', "console.log('string-script');"), + ) + .then(() => + expectFileToMatch('dist/test-project/main-es2015.js', "console.log('string-script');"), + ) + ); + // TODO(architect): disabled until --prod is added. + // Verify uglify, sourcemaps and hashes. Lazy scripts should not get hashes. + // .then(() => ng('build', '--prod', '--source-map')) + // .then(() => expectFileMatchToExist('dist', /scripts\.[0-9a-f]{20}\.js/)) + // .then(fileName => expectFileToMatch(`dist/${fileName}`, 'var number=2;')) + // .then(() => expectFileMatchToExist('dist', /scripts\.[0-9a-f]{20}\.js\.map/)) + // .then(() => expectFileMatchToExist('dist', /renamed-script\.[0-9a-f]{20}\.js/)) + // .then(() => expectFileMatchToExist('dist', /renamed-script\.[0-9a-f]{20}\.js.map/)) + // .then(() => expectFileToMatch('dist/test-project/lazy-script.js', 'lazy-script')) + // .then(() => expectFileToMatch('dist/test-project/enamed-lazy-script.js', 'pre-rename-lazy-script')) - // // Expect order to be preserved. - // .then(() => { - // const [fileName] = fs.readdirSync('dist') - // .filter(name => name.match(/^scripts\..*\.js$/)); + // // Expect order to be preserved. + // .then(() => { + // const [fileName] = fs.readdirSync('dist') + // .filter(name => name.match(/^scripts\..*\.js$/)); - // const content = fs.readFileSync(path.join('dist', fileName), 'utf-8'); - // const re = new RegExp(/['"]string-script['"].*/.source - // + /['"]zstring-script['"].*/.source - // + /['"]fstring-script['"].*/.source - // + /['"]ustring-script['"].*/.source - // + /['"]bstring-script['"].*/.source - // + /['"]astring-script['"].*/.source - // + /['"]cstring-script['"].*/.source - // + /['"]input-script['"]/.source); - // if (!content.match(re)) { - // throw new Error('Scripts are not included in order.'); - // } - // }); + // const content = fs.readFileSync(path.join('dist', fileName), 'utf-8'); + // const re = new RegExp(/['"]string-script['"].*/.source + // + /['"]zstring-script['"].*/.source + // + /['"]fstring-script['"].*/.source + // + /['"]ustring-script['"].*/.source + // + /['"]bstring-script['"].*/.source + // + /['"]astring-script['"].*/.source + // + /['"]cstring-script['"].*/.source + // + /['"]input-script['"]/.source); + // if (!content.match(re)) { + // throw new Error('Scripts are not included in order.'); + // } + // }); } diff --git a/tests/legacy-cli/e2e/tests/basic/styles-array.ts b/tests/legacy-cli/e2e/tests/basic/styles-array.ts index 0261b23de5bd..93565e3c1fca 100644 --- a/tests/legacy-cli/e2e/tests/basic/styles-array.ts +++ b/tests/legacy-cli/e2e/tests/basic/styles-array.ts @@ -1,47 +1,58 @@ // TODO(architect): edit the architect config instead of the cli config. -import { - writeMultipleFiles, - expectFileToMatch -} from '../../utils/fs'; +import { writeMultipleFiles, expectFileToMatch } from '../../utils/fs'; import { ng } from '../../utils/process'; import { updateJsonFile } from '../../utils/project'; import { oneLineTrim } from 'common-tags'; -export default function () { - return writeMultipleFiles({ - 'src/string-style.css': '.string-style { color: red }', - 'src/input-style.css': '.input-style { color: red }', - 'src/lazy-style.css': '.lazy-style { color: red }', - 'src/pre-rename-style.css': '.pre-rename-style { color: red }', - 'src/pre-rename-lazy-style.css': '.pre-rename-lazy-style { color: red }', - }) - .then(() => updateJsonFile('angular.json', workspaceJson => { - const appArchitect = workspaceJson.projects['test-project'].architect; - appArchitect.build.options.styles = [ - { input: 'src/string-style.css' }, - { input: 'src/input-style.css' }, - { input: 'src/lazy-style.css', lazy: true }, - { input: 'src/pre-rename-style.css', bundleName: 'renamed-style' }, - { input: 'src/pre-rename-lazy-style.css', bundleName: 'renamed-lazy-style', lazy: true } - ]; - })) - .then(() => ng('build', '--extract-css')) - // files were created successfully - .then(() => expectFileToMatch('dist/test-project/styles.css', '.string-style')) - .then(() => expectFileToMatch('dist/test-project/styles.css', '.input-style')) - .then(() => expectFileToMatch('dist/test-project/lazy-style.css', '.lazy-style')) - .then(() => expectFileToMatch('dist/test-project/renamed-style.css', '.pre-rename-style')) - .then(() => expectFileToMatch( - 'dist/test-project/renamed-lazy-style.css', - '.pre-rename-lazy-style', - )) - // index.html lists the right bundles - .then(() => expectFileToMatch('dist/test-project/index.html', oneLineTrim` +export default function() { + return ( + writeMultipleFiles({ + 'src/string-style.css': '.string-style { color: red }', + 'src/input-style.css': '.input-style { color: red }', + 'src/lazy-style.css': '.lazy-style { color: red }', + 'src/pre-rename-style.css': '.pre-rename-style { color: red }', + 'src/pre-rename-lazy-style.css': '.pre-rename-lazy-style { color: red }', + }) + .then(() => + updateJsonFile('angular.json', workspaceJson => { + const appArchitect = workspaceJson.projects['test-project'].architect; + appArchitect.build.options.styles = [ + { input: 'src/string-style.css' }, + { input: 'src/input-style.css' }, + { input: 'src/lazy-style.css', inject: false }, + { input: 'src/pre-rename-style.css', bundleName: 'renamed-style' }, + { + input: 'src/pre-rename-lazy-style.css', + bundleName: 'renamed-lazy-style', + inject: false, + }, + ]; + }), + ) + .then(() => ng('build', '--extract-css')) + // files were created successfully + .then(() => expectFileToMatch('dist/test-project/styles.css', '.string-style')) + .then(() => expectFileToMatch('dist/test-project/styles.css', '.input-style')) + .then(() => expectFileToMatch('dist/test-project/lazy-style.css', '.lazy-style')) + .then(() => expectFileToMatch('dist/test-project/renamed-style.css', '.pre-rename-style')) + .then(() => + expectFileToMatch('dist/test-project/renamed-lazy-style.css', '.pre-rename-lazy-style'), + ) + // index.html lists the right bundles + .then(() => + expectFileToMatch( + 'dist/test-project/index.html', + oneLineTrim` - `)) - .then(() => expectFileToMatch('dist/test-project/index.html', oneLineTrim` + `, + ), + ) + .then(() => + expectFileToMatch( + 'dist/test-project/index.html', + oneLineTrim` @@ -50,5 +61,8 @@ export default function () { - `)); + `, + ), + ) + ); } diff --git a/tests/legacy-cli/e2e/tests/build/styles/extract-css.ts b/tests/legacy-cli/e2e/tests/build/styles/extract-css.ts index bfb970699528..89c213820aba 100644 --- a/tests/legacy-cli/e2e/tests/build/styles/extract-css.ts +++ b/tests/legacy-cli/e2e/tests/build/styles/extract-css.ts @@ -1,56 +1,77 @@ -import { - writeMultipleFiles, - expectFileToExist, - expectFileToMatch -} from '../../../utils/fs'; +import { writeMultipleFiles, expectFileToExist, expectFileToMatch } from '../../../utils/fs'; import { ng } from '../../../utils/process'; import { updateJsonFile } from '../../../utils/project'; import { expectToFail } from '../../../utils/utils'; import { oneLineTrim } from 'common-tags'; -export default function () { +export default function() { // TODO(architect): Delete this test. It is now in devkit/build-angular. - return Promise.resolve() - .then(() => writeMultipleFiles({ - 'src/string-style.css': '.string-style { color: red }', - 'src/input-style.css': '.input-style { color: red }', - 'src/lazy-style.css': '.lazy-style { color: red }', - 'src/pre-rename-style.css': '.pre-rename-style { color: red }', - 'src/pre-rename-lazy-style.css': '.pre-rename-lazy-style { color: red }', - })) - .then(() => updateJsonFile('angular.json', workspaceJson => { - const appArchitect = workspaceJson.projects['test-project'].architect; - appArchitect.build.options.styles = [ - { input: 'src/string-style.css' }, - { input: 'src/input-style.css' }, - { input: 'src/lazy-style.css', lazy: true }, - { input: 'src/pre-rename-style.css', bundleName: 'renamed-style' }, - { input: 'src/pre-rename-lazy-style.css', bundleName: 'renamed-lazy-style', lazy: true } - ]; - })) - .then(() => ng('build', '--extract-css')) - // files were created successfully - .then(() => expectFileToMatch('dist/test-project/styles.css', '.string-style')) - .then(() => expectFileToMatch('dist/test-project/styles.css', '.input-style')) - .then(() => expectFileToMatch('dist/test-project/lazy-style.css', '.lazy-style')) - .then(() => expectFileToMatch('dist/test-project/renamed-style.css', '.pre-rename-style')) - .then(() => expectFileToMatch('dist/test-project/renamed-lazy-style.css', '.pre-rename-lazy-style')) - // there are no js entry points for css only bundles - .then(() => expectToFail(() => expectFileToExist('dist/test-project/style-es5.js'))) - .then(() => expectToFail(() => expectFileToExist('dist/test-project/lazy-style-es5.js'))) - .then(() => expectToFail(() => expectFileToExist('dist/test-project/renamed-style-es5.js'))) - .then(() => expectToFail(() => expectFileToExist('dist/test-project/renamed-lazy-style-es5.js'))) - .then(() => expectToFail(() => expectFileToExist('dist/test-project/style-es2015.js'))) - .then(() => expectToFail(() => expectFileToExist('dist/test-project/lazy-style-es2015.js'))) - .then(() => expectToFail(() => expectFileToExist('dist/test-project/renamed-style-es2015.js'))) - .then(() => expectToFail(() => expectFileToExist('dist/test-project/renamed-lazy-style-es2015.js'))) - // index.html lists the right bundles - .then(() => expectFileToMatch('dist/test-project/index.html', new RegExp(oneLineTrim` + return ( + Promise.resolve() + .then(() => + writeMultipleFiles({ + 'src/string-style.css': '.string-style { color: red }', + 'src/input-style.css': '.input-style { color: red }', + 'src/lazy-style.css': '.lazy-style { color: red }', + 'src/pre-rename-style.css': '.pre-rename-style { color: red }', + 'src/pre-rename-lazy-style.css': '.pre-rename-lazy-style { color: red }', + }), + ) + .then(() => + updateJsonFile('angular.json', workspaceJson => { + const appArchitect = workspaceJson.projects['test-project'].architect; + appArchitect.build.options.styles = [ + { input: 'src/string-style.css' }, + { input: 'src/input-style.css' }, + { input: 'src/lazy-style.css', inject: false }, + { input: 'src/pre-rename-style.css', bundleName: 'renamed-style' }, + { + input: 'src/pre-rename-lazy-style.css', + bundleName: 'renamed-lazy-style', + inject: false, + }, + ]; + }), + ) + .then(() => ng('build', '--extract-css')) + // files were created successfully + .then(() => expectFileToMatch('dist/test-project/styles.css', '.string-style')) + .then(() => expectFileToMatch('dist/test-project/styles.css', '.input-style')) + .then(() => expectFileToMatch('dist/test-project/lazy-style.css', '.lazy-style')) + .then(() => expectFileToMatch('dist/test-project/renamed-style.css', '.pre-rename-style')) + .then(() => + expectFileToMatch('dist/test-project/renamed-lazy-style.css', '.pre-rename-lazy-style'), + ) + // there are no js entry points for css only bundles + .then(() => expectToFail(() => expectFileToExist('dist/test-project/style-es5.js'))) + .then(() => expectToFail(() => expectFileToExist('dist/test-project/lazy-style-es5.js'))) + .then(() => expectToFail(() => expectFileToExist('dist/test-project/renamed-style-es5.js'))) + .then(() => + expectToFail(() => expectFileToExist('dist/test-project/renamed-lazy-style-es5.js')), + ) + .then(() => expectToFail(() => expectFileToExist('dist/test-project/style-es2015.js'))) + .then(() => expectToFail(() => expectFileToExist('dist/test-project/lazy-style-es2015.js'))) + .then(() => + expectToFail(() => expectFileToExist('dist/test-project/renamed-style-es2015.js')), + ) + .then(() => + expectToFail(() => expectFileToExist('dist/test-project/renamed-lazy-style-es2015.js')), + ) + // index.html lists the right bundles + .then(() => + expectFileToMatch( + 'dist/test-project/index.html', + new RegExp(oneLineTrim` - `))) - .then(() => expectFileToMatch('dist/test-project/index.html', oneLineTrim` + `), + ), + ) + .then(() => + expectFileToMatch( + 'dist/test-project/index.html', + oneLineTrim` @@ -59,22 +80,36 @@ export default function () { - `)) - // also check when css isn't extracted - .then(() => ng('build', '--no-extract-css')) - // files were created successfully - .then(() => expectFileToMatch('dist/test-project/styles-es5.js', '.string-style')) - .then(() => expectFileToMatch('dist/test-project/styles-es5.js', '.input-style')) - .then(() => expectFileToMatch('dist/test-project/lazy-style-es5.js', '.lazy-style')) - .then(() => expectFileToMatch('dist/test-project/renamed-style-es5.js', '.pre-rename-style')) - .then(() => expectFileToMatch('dist/test-project/renamed-lazy-style-es5.js', '.pre-rename-lazy-style')) - .then(() => expectFileToMatch('dist/test-project/styles-es2015.js', '.string-style')) - .then(() => expectFileToMatch('dist/test-project/styles-es2015.js', '.input-style')) - .then(() => expectFileToMatch('dist/test-project/lazy-style-es2015.js', '.lazy-style')) - .then(() => expectFileToMatch('dist/test-project/renamed-style-es2015.js', '.pre-rename-style')) - .then(() => expectFileToMatch('dist/test-project/renamed-lazy-style-es2015.js', '.pre-rename-lazy-style')) - // index.html lists the right bundles - .then(() => expectFileToMatch('dist/test-project/index.html', oneLineTrim` + `, + ), + ) + // also check when css isn't extracted + .then(() => ng('build', '--no-extract-css')) + // files were created successfully + .then(() => expectFileToMatch('dist/test-project/styles-es5.js', '.string-style')) + .then(() => expectFileToMatch('dist/test-project/styles-es5.js', '.input-style')) + .then(() => expectFileToMatch('dist/test-project/lazy-style-es5.js', '.lazy-style')) + .then(() => expectFileToMatch('dist/test-project/renamed-style-es5.js', '.pre-rename-style')) + .then(() => + expectFileToMatch('dist/test-project/renamed-lazy-style-es5.js', '.pre-rename-lazy-style'), + ) + .then(() => expectFileToMatch('dist/test-project/styles-es2015.js', '.string-style')) + .then(() => expectFileToMatch('dist/test-project/styles-es2015.js', '.input-style')) + .then(() => expectFileToMatch('dist/test-project/lazy-style-es2015.js', '.lazy-style')) + .then(() => + expectFileToMatch('dist/test-project/renamed-style-es2015.js', '.pre-rename-style'), + ) + .then(() => + expectFileToMatch( + 'dist/test-project/renamed-lazy-style-es2015.js', + '.pre-rename-lazy-style', + ), + ) + // index.html lists the right bundles + .then(() => + expectFileToMatch( + 'dist/test-project/index.html', + oneLineTrim` @@ -87,5 +122,8 @@ export default function () { - `)); + `, + ), + ) + ); } From d7def72d9e58284d140187889b28746af6716fd8 Mon Sep 17 00:00:00 2001 From: Alan Date: Tue, 2 Jul 2019 12:37:35 +0200 Subject: [PATCH 2/2] feat(@schematics/angular): add migration from `lazy` option to `inject` --- .../migrations/migration-collection.json | 5 + .../angular/migrations/update-9/index.ts | 18 +++ .../update-9/update-workspace-config.ts | 123 +++++++++++++++++ .../update-9/update-workspace-config_spec.ts | 128 ++++++++++++++++++ 4 files changed, 274 insertions(+) create mode 100644 packages/schematics/angular/migrations/update-9/index.ts create mode 100644 packages/schematics/angular/migrations/update-9/update-workspace-config.ts create mode 100644 packages/schematics/angular/migrations/update-9/update-workspace-config_spec.ts diff --git a/packages/schematics/angular/migrations/migration-collection.json b/packages/schematics/angular/migrations/migration-collection.json index 3cd1b2c82c79..f2f7a02f7551 100644 --- a/packages/schematics/angular/migrations/migration-collection.json +++ b/packages/schematics/angular/migrations/migration-collection.json @@ -39,6 +39,11 @@ "version": "8.0.0-beta.14", "factory": "./update-8/#updateLazyModulePaths", "description": "Update an Angular CLI project to version 8." + }, + "migration-09": { + "version": "9.0.0-beta.0", + "factory": "./update-9", + "description": "Update an Angular CLI project to version 9." } } } diff --git a/packages/schematics/angular/migrations/update-9/index.ts b/packages/schematics/angular/migrations/update-9/index.ts new file mode 100644 index 000000000000..eb6d7869a4d2 --- /dev/null +++ b/packages/schematics/angular/migrations/update-9/index.ts @@ -0,0 +1,18 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { Rule, chain } from '@angular-devkit/schematics'; +import { UpdateWorkspaceConfig } from './update-workspace-config'; + +export default function(): Rule { + return () => { + return chain([ + UpdateWorkspaceConfig(), + ]); + }; +} diff --git a/packages/schematics/angular/migrations/update-9/update-workspace-config.ts b/packages/schematics/angular/migrations/update-9/update-workspace-config.ts new file mode 100644 index 000000000000..3dc9f637ce53 --- /dev/null +++ b/packages/schematics/angular/migrations/update-9/update-workspace-config.ts @@ -0,0 +1,123 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ +import { + JsonAstObject, + JsonParseMode, + parseJsonAst, +} from '@angular-devkit/core'; +import { Rule, Tree, UpdateRecorder } from '@angular-devkit/schematics'; +import { + findPropertyInAstObject, + insertPropertyInAstObjectInOrder, + removePropertyInAstObject, +} from '../../utility/json-utils'; + +export function UpdateWorkspaceConfig(): Rule { + return (tree: Tree) => { + let workspaceConfigPath = 'angular.json'; + let angularConfigContent = tree.read(workspaceConfigPath); + + if (!angularConfigContent) { + workspaceConfigPath = '.angular.json'; + angularConfigContent = tree.read(workspaceConfigPath); + + if (!angularConfigContent) { + return; + } + } + + const angularJson = parseJsonAst(angularConfigContent.toString(), JsonParseMode.Loose); + if (angularJson.kind !== 'object') { + return; + } + + const projects = findPropertyInAstObject(angularJson, 'projects'); + if (!projects || projects.kind !== 'object') { + return; + } + + // For all projects + const recorder = tree.beginUpdate(workspaceConfigPath); + for (const project of projects.properties) { + const projectConfig = project.value; + if (projectConfig.kind !== 'object') { + break; + } + + const architect = findPropertyInAstObject(projectConfig, 'architect'); + if (!architect || architect.kind !== 'object') { + break; + } + + const buildTarget = findPropertyInAstObject(architect, 'build'); + if (buildTarget && buildTarget.kind === 'object') { + const builder = findPropertyInAstObject(buildTarget, 'builder'); + // Projects who's build builder is not build-angular:browser + if (builder && builder.kind === 'string' && builder.value === '@angular-devkit/build-angular:browser') { + updateOption('styles', recorder, buildTarget); + updateOption('scripts', recorder, buildTarget); + } + } + + const testTarget = findPropertyInAstObject(architect, 'test'); + if (testTarget && testTarget.kind === 'object') { + const builder = findPropertyInAstObject(testTarget, 'builder'); + // Projects who's build builder is not build-angular:browser + if (builder && builder.kind === 'string' && builder.value === '@angular-devkit/build-angular:karma') { + updateOption('styles', recorder, testTarget); + updateOption('scripts', recorder, testTarget); + } + } + } + + tree.commitUpdate(recorder); + + return tree; + }; +} + +/** + * Helper to retreive all the options in various configurations + */ +function getAllOptions(builderConfig: JsonAstObject): JsonAstObject[] { + const options = []; + const configurations = findPropertyInAstObject(builderConfig, 'configurations'); + if (configurations && configurations.kind === 'object') { + options.push(...configurations.properties.map(x => x.value)); + } + + options.push(findPropertyInAstObject(builderConfig, 'options')); + + return options.filter(o => o && o.kind === 'object') as JsonAstObject[]; +} + +function updateOption(property: 'scripts' | 'styles', recorder: UpdateRecorder, builderConfig: JsonAstObject) { + const options = getAllOptions(builderConfig); + + for (const option of options) { + const propertyOption = findPropertyInAstObject(option, property); + if (!propertyOption || propertyOption.kind !== 'array') { + continue; + } + + for (const node of propertyOption.elements) { + if (!node || node.kind !== 'object') { + // skip non complex objects + continue; + } + + const lazy = findPropertyInAstObject(node, 'lazy'); + removePropertyInAstObject(recorder, node, 'lazy'); + + // if lazy was not true, it is redundant hence, don't add it + if (lazy && lazy.kind === 'true') { + insertPropertyInAstObjectInOrder(recorder, node, 'inject', false, 0); + } + } + } +} diff --git a/packages/schematics/angular/migrations/update-9/update-workspace-config_spec.ts b/packages/schematics/angular/migrations/update-9/update-workspace-config_spec.ts new file mode 100644 index 000000000000..64865e83c83b --- /dev/null +++ b/packages/schematics/angular/migrations/update-9/update-workspace-config_spec.ts @@ -0,0 +1,128 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { EmptyTree } from '@angular-devkit/schematics'; +import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing'; + +function readWorkspaceConfig(tree: UnitTestTree) { + return JSON.parse(tree.readContent('/angular.json')); +} + +const scriptsWithLazy = [ + { bundleName: 'one', input: 'one.js', lazy: false }, + { bundleName: 'two', input: 'two.js', lazy: true }, + { bundleName: 'tree', input: 'tree.js' }, + 'four.js', +] + +const scriptsExpectWithLazy = [ + { bundleName: 'one', input: 'one.js' }, + { bundleName: 'two', inject: false, input: 'two.js' }, + { bundleName: 'tree', input: 'tree.js' }, + 'four.js', +] + +const stylesWithLazy = [ + { bundleName: 'one', input: 'one.css', lazy: false }, + { bundleName: 'two', input: 'two.css', lazy: true }, + { bundleName: 'tree', input: 'tree.css' }, + 'four.css', +] + +const stylesExpectWithLazy = [ + { bundleName: 'one', input: 'one.css' }, + { bundleName: 'two', inject: false, input: 'two.css' }, + { bundleName: 'tree', input: 'tree.css' }, + 'four.css', +] + +const workspacePath = '/angular.json'; + +// tslint:disable:no-big-function +describe('Migration to version 9', () => { + describe('Migrate workspace config', () => { + const schematicRunner = new SchematicTestRunner( + 'migrations', + require.resolve('../migration-collection.json'), + ); + + let tree: UnitTestTree; + + beforeEach(async () => { + tree = new UnitTestTree(new EmptyTree()); + tree = await schematicRunner + .runExternalSchematicAsync( + require.resolve('../../collection.json'), + 'ng-new', + { + name: 'migration-test', + version: '1.2.3', + directory: '.', + }, + tree, + ) + .toPromise(); + }); + + it('should update scripts in build target', () => { + let config = readWorkspaceConfig(tree); + let build = config.projects['migration-test'].architect.build; + build.options.scripts = scriptsWithLazy; + build.configurations.production.scripts = scriptsWithLazy; + + tree.overwrite(workspacePath, JSON.stringify(config)); + const tree2 = schematicRunner.runSchematic('migration-09', {}, tree.branch()); + config = readWorkspaceConfig(tree2); + build = config.projects['migration-test'].architect.build; + expect(build.options.scripts).toEqual(scriptsExpectWithLazy); + expect(build.configurations.production.scripts).toEqual(scriptsExpectWithLazy); + }); + + it('should update styles in build target', () => { + let config = readWorkspaceConfig(tree); + let build = config.projects['migration-test'].architect.build; + build.options.styles = stylesWithLazy; + build.configurations.production.styles = stylesWithLazy; + + tree.overwrite(workspacePath, JSON.stringify(config)); + const tree2 = schematicRunner.runSchematic('migration-09', {}, tree.branch()); + config = readWorkspaceConfig(tree2); + build = config.projects['migration-test'].architect.build; + expect(build.options.styles).toEqual(stylesExpectWithLazy); + expect(build.configurations.production.styles).toEqual(stylesExpectWithLazy); + }); + + it('should update scripts in test target', () => { + let config = readWorkspaceConfig(tree); + let test = config.projects['migration-test'].architect.test; + test.options.scripts = scriptsWithLazy; + test.configurations = { production: { scripts: scriptsWithLazy } }; + + tree.overwrite(workspacePath, JSON.stringify(config)); + const tree2 = schematicRunner.runSchematic('migration-09', {}, tree.branch()); + config = readWorkspaceConfig(tree2); + test = config.projects['migration-test'].architect.test; + expect(test.options.scripts).toEqual(scriptsExpectWithLazy); + expect(test.configurations.production.scripts).toEqual(scriptsExpectWithLazy); + }); + + it('should update styles in test target', () => { + let config = readWorkspaceConfig(tree); + let test = config.projects['migration-test'].architect.test; + test.options.styles = stylesWithLazy; + test.configurations = { production: { styles: stylesWithLazy } }; + + tree.overwrite(workspacePath, JSON.stringify(config)); + const tree2 = schematicRunner.runSchematic('migration-09', {}, tree.branch()); + config = readWorkspaceConfig(tree2); + test = config.projects['migration-test'].architect.test; + expect(test.options.styles).toEqual(stylesExpectWithLazy); + expect(test.configurations.production.styles).toEqual(stylesExpectWithLazy); + }); + }); +});