From a927603d9d3a69dedf3b1c218873ce96e7e24e35 Mon Sep 17 00:00:00 2001 From: Alan Agius Date: Fri, 30 Nov 2018 10:22:38 +0100 Subject: [PATCH 1/2] feat(@angular/cli): update schema to match new `sourceMap` --- packages/angular/cli/lib/config/schema.json | 127 ++++++++++++++++++-- 1 file changed, 119 insertions(+), 8 deletions(-) diff --git a/packages/angular/cli/lib/config/schema.json b/packages/angular/cli/lib/config/schema.json index 99bc50ac4895..c4530ec95799 100644 --- a/packages/angular/cli/lib/config/schema.json +++ b/packages/angular/cli/lib/config/schema.json @@ -633,9 +633,39 @@ "default": false }, "sourceMap": { - "type": "boolean", "description": "Output sourcemaps.", - "default": true + "default": true, + "oneOf": [ + { + "type": "object", + "properties": { + "scripts": { + "type": "boolean", + "description": "Output sourcemaps for all scripts.", + "default": true + }, + "styles": { + "type": "boolean", + "description": "Output sourcemaps for all styles.", + "default": true + }, + "hidden": { + "type": "boolean", + "description": "Output sourcemaps used for error reporting tools.", + "default": false + }, + "vendor": { + "type": "boolean", + "description": "Resolve vendor packages sourcemaps.", + "default": false + } + }, + "additionalProperties": false + }, + { + "type": "boolean" + } + ] }, "vendorSourceMap": { "type": "boolean", @@ -1045,8 +1075,34 @@ "description": "Build using Ahead of Time compilation." }, "sourceMap": { - "type": "boolean", - "description": "Output sourcemaps." + "description": "Output sourcemaps.", + "default": true, + "oneOf": [ + { + "type": "object", + "properties": { + "scripts": { + "type": "boolean", + "description": "Output sourcemaps for all scripts.", + "default": true + }, + "styles": { + "type": "boolean", + "description": "Output sourcemaps for all styles.", + "default": true + }, + "vendor": { + "type": "boolean", + "description": "Resolve vendor packages sourcemaps.", + "default": false + } + }, + "additionalProperties": false + }, + { + "type": "boolean" + } + ] }, "vendorSourceMap": { "type": "boolean", @@ -1189,9 +1245,34 @@ "description": "Defines the build environment." }, "sourceMap": { - "type": "boolean", "description": "Output sourcemaps.", - "default": true + "default": true, + "oneOf": [ + { + "type": "object", + "properties": { + "scripts": { + "type": "boolean", + "description": "Output sourcemaps for all scripts.", + "default": true + }, + "styles": { + "type": "boolean", + "description": "Output sourcemaps for all styles.", + "default": true + }, + "vendor": { + "type": "boolean", + "description": "Resolve vendor packages sourcemaps.", + "default": false + } + }, + "additionalProperties": false + }, + { + "type": "boolean" + } + ] }, "progress": { "type": "boolean", @@ -1455,9 +1536,39 @@ "description": "The path where style resources will be placed, relative to outputPath." }, "sourceMap": { - "type": "boolean", "description": "Output sourcemaps.", - "default": true + "default": true, + "oneOf": [ + { + "type": "object", + "properties": { + "scripts": { + "type": "boolean", + "description": "Output sourcemaps for all scripts.", + "default": true + }, + "styles": { + "type": "boolean", + "description": "Output sourcemaps for all styles.", + "default": true + }, + "hidden": { + "type": "boolean", + "description": "Output sourcemaps used for error reporting tools.", + "default": false + }, + "vendor": { + "type": "boolean", + "description": "Resolve vendor packages sourcemaps.", + "default": false + } + }, + "additionalProperties": false + }, + { + "type": "boolean" + } + ] }, "vendorSourceMap": { "type": "boolean", From d64f2feb9eca243d50e7baa072604a498fb5dce2 Mon Sep 17 00:00:00 2001 From: Alan Agius Date: Fri, 30 Nov 2018 11:36:18 +0100 Subject: [PATCH 2/2] feat(@angular-devkit/build-angular): fine grain settings for sourceMaps This PR add more control over which sourceMaps you want, Now you can enable sourceMaps for scripts only, styles only or both. Also we added another functionality which are hidden sourcemaps. These are normaly used for error reporting tools. Fixes #7527 --- .../angular-cli-files/models/build-options.ts | 3 + .../models/webpack-configs/browser.ts | 33 ++-- .../models/webpack-configs/common.ts | 10 +- .../models/webpack-configs/server.ts | 18 +- .../models/webpack-configs/styles.ts | 10 +- .../models/webpack-configs/test.ts | 17 +- .../models/webpack-configs/typescript.ts | 4 +- .../models/webpack-configs/utils.ts | 23 +++ .../build_angular/src/browser/index.ts | 31 +++- .../build_angular/src/browser/schema.d.ts | 16 +- .../build_angular/src/browser/schema.json | 37 +++- .../build_angular/src/dev-server/index.ts | 41 ++--- .../build_angular/src/dev-server/schema.json | 38 +++- .../build_angular/src/karma/index.ts | 20 +- .../build_angular/src/karma/schema.d.ts | 20 +- .../build_angular/src/karma/schema.json | 30 ++- .../build_angular/src/server/index.ts | 19 +- .../build_angular/src/server/schema.d.ts | 11 +- .../build_angular/src/server/schema.json | 36 +++- .../build_angular/src/utils/index.ts | 1 + .../src/utils/normalize-source-maps.ts | 32 ++++ .../test/browser/source-map_spec_large.ts | 173 +++++++++++++++++- .../browser/vendor-source-map_spec_large.ts | 17 +- .../test/server/base_spec_large.ts | 53 ++++++ 24 files changed, 612 insertions(+), 81 deletions(-) create mode 100644 packages/angular_devkit/build_angular/src/utils/normalize-source-maps.ts diff --git a/packages/angular_devkit/build_angular/src/angular-cli-files/models/build-options.ts b/packages/angular_devkit/build_angular/src/angular-cli-files/models/build-options.ts index 8e8e6df74dde..649d287ad144 100644 --- a/packages/angular_devkit/build_angular/src/angular-cli-files/models/build-options.ts +++ b/packages/angular_devkit/build_angular/src/angular-cli-files/models/build-options.ts @@ -24,6 +24,9 @@ export interface BuildOptions { resourcesOutputPath?: string; aot?: boolean; sourceMap?: boolean; + scriptsSourceMap?: boolean; + stylesSourceMap?: boolean; + hiddenSourceMap?: boolean; vendorSourceMap?: boolean; evalSourceMap?: boolean; vendorChunk?: boolean; diff --git a/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/browser.ts b/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/browser.ts index 27c22ce0309b..0fc80f090891 100644 --- a/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/browser.ts +++ b/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/browser.ts @@ -10,7 +10,7 @@ import * as path from 'path'; import { IndexHtmlWebpackPlugin } from '../../plugins/index-html-webpack-plugin'; import { generateEntryPoints } from '../../utilities/package-chunk-sort'; import { WebpackConfigOptions } from '../build-options'; -import { normalizeExtraEntryPoints } from './utils'; +import { getSourceMapDevTool, normalizeExtraEntryPoints } from './utils'; const SubresourceIntegrityPlugin = require('webpack-subresource-integrity'); @@ -19,16 +19,11 @@ export function getBrowserConfig(wco: WebpackConfigOptions) { const { root, buildOptions } = wco; const extraPlugins = []; - let sourcemaps: string | false = false; - if (buildOptions.sourceMap) { - // See https://webpack.js.org/configuration/devtool/ for sourcemap types. - if (buildOptions.evalSourceMap && !buildOptions.optimization) { - // Produce eval sourcemaps for development with serve, which are faster. - sourcemaps = 'eval'; - } else { - // Produce full separate sourcemaps for production. - sourcemaps = 'source-map'; - } + let isEval = false; + // See https://webpack.js.org/configuration/devtool/ for sourcemap types. + if (buildOptions.sourceMap && buildOptions.evalSourceMap && !buildOptions.optimization) { + // Produce eval sourcemaps for development with serve, which are faster. + isEval = true; } if (buildOptions.index) { @@ -59,11 +54,25 @@ export function getBrowserConfig(wco: WebpackConfigOptions) { })); } + if (!isEval && buildOptions.sourceMap) { + const { + scriptsSourceMap = false, + stylesSourceMap = false, + hiddenSourceMap = false, + } = buildOptions; + + extraPlugins.push(getSourceMapDevTool( + scriptsSourceMap, + stylesSourceMap, + hiddenSourceMap, + )); + } + const globalStylesBundleNames = normalizeExtraEntryPoints(buildOptions.styles, 'styles') .map(style => style.bundleName); return { - devtool: sourcemaps, + devtool: isEval ? 'eval' : false, resolve: { mainFields: [ ...(wco.supportES2015 ? ['es2015'] : []), 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 699dafdbf688..147f770912ea 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 @@ -15,7 +15,7 @@ import { ScriptsWebpackPlugin } from '../../plugins/scripts-webpack-plugin'; import { findUp } from '../../utilities/find-up'; import { isDirectory } from '../../utilities/is-directory'; import { requireProjectModule } from '../../utilities/require-project-module'; -import { WebpackConfigOptions } from '../build-options'; +import { BuildOptions, WebpackConfigOptions } from '../build-options'; import { getOutputHashFormat, normalizeExtraEntryPoints } from './utils'; const ProgressPlugin = require('webpack/lib/ProgressPlugin'); @@ -102,7 +102,7 @@ export function getCommonConfig(wco: WebpackConfigOptions) { extraPlugins.push(new ScriptsWebpackPlugin({ name: bundleName, - sourceMap: buildOptions.sourceMap, + sourceMap: buildOptions.scriptsSourceMap, filename: `${path.basename(bundleName)}${hash}.js`, scripts: script.paths, basePath: projectRoot, @@ -174,7 +174,7 @@ export function getCommonConfig(wco: WebpackConfigOptions) { use: [ { loader: buildOptimizerLoader, - options: { sourceMap: buildOptions.sourceMap }, + options: { sourceMap: buildOptions.scriptsSourceMap }, }, ], }; @@ -297,12 +297,12 @@ export function getCommonConfig(wco: WebpackConfigOptions) { // TODO: check with Mike what this feature needs. new BundleBudgetPlugin({ budgets: buildOptions.budgets }), new CleanCssWebpackPlugin({ - sourceMap: buildOptions.sourceMap, + sourceMap: buildOptions.stylesSourceMap, // component styles retain their original file name test: (file) => /\.(?:css|scss|sass|less|styl)$/.test(file), }), new TerserPlugin({ - sourceMap: buildOptions.sourceMap, + sourceMap: buildOptions.scriptsSourceMap, parallel: true, cache: true, terserOptions, diff --git a/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/server.ts b/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/server.ts index ee12c70ed2b4..df1027e33354 100644 --- a/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/server.ts +++ b/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/server.ts @@ -7,6 +7,7 @@ */ import { Configuration } from 'webpack'; import { WebpackConfigOptions } from '../build-options'; +import { getSourceMapDevTool } from './utils'; /** @@ -15,8 +16,22 @@ import { WebpackConfigOptions } from '../build-options'; */ export function getServerConfig(wco: WebpackConfigOptions) { + const extraPlugins = []; + if (wco.buildOptions.sourceMap) { + const { + scriptsSourceMap = false, + stylesSourceMap = false, + hiddenSourceMap = false, + } = wco.buildOptions; + + extraPlugins.push(getSourceMapDevTool( + scriptsSourceMap, + stylesSourceMap, + hiddenSourceMap, + )); + } + const config: Configuration = { - devtool: wco.buildOptions.sourceMap ? 'source-map' : false, resolve: { mainFields: [ ...(wco.supportES2015 ? ['es2015'] : []), @@ -27,6 +42,7 @@ export function getServerConfig(wco: WebpackConfigOptions) { output: { libraryTarget: 'commonjs', }, + plugins: extraPlugins, node: false, }; 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 451febeaca3b..972bda036dc7 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 @@ -40,7 +40,8 @@ export function getStylesConfig(wco: WebpackConfigOptions) { const entryPoints: { [key: string]: string[] } = {}; const globalStylePaths: string[] = []; const extraPlugins = []; - const cssSourceMap = buildOptions.sourceMap; + + const cssSourceMap = buildOptions.stylesSourceMap; // Determine hashing format. const hashFormat = getOutputHashFormat(buildOptions.outputHashing as string); @@ -187,7 +188,7 @@ export function getStylesConfig(wco: WebpackConfigOptions) { options: { ident: 'embedded', plugins: postcssPluginCreator, - sourceMap: cssSourceMap ? 'inline' : false, + sourceMap: cssSourceMap && !buildOptions.hiddenSourceMap ? 'inline' : false, }, }, ...(use as webpack.Loader[]), @@ -208,7 +209,10 @@ export function getStylesConfig(wco: WebpackConfigOptions) { options: { ident: buildOptions.extractCss ? 'extracted' : 'embedded', plugins: postcssPluginCreator, - sourceMap: cssSourceMap && !buildOptions.extractCss ? 'inline' : cssSourceMap, + sourceMap: cssSourceMap + && !buildOptions.extractCss + && !buildOptions.hiddenSourceMap + ? 'inline' : cssSourceMap, }, }, ...(use as webpack.Loader[]), diff --git a/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/test.ts b/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/test.ts index 4c8b05aa6008..f54aca248c54 100644 --- a/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/test.ts +++ b/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/test.ts @@ -10,6 +10,7 @@ import * as glob from 'glob'; import * as path from 'path'; import * as webpack from 'webpack'; import { WebpackConfigOptions, WebpackTestOptions } from '../build-options'; +import { getSourceMapDevTool } from './utils'; /** @@ -55,6 +56,20 @@ export function getTestConfig( }); } + if (wco.buildOptions.sourceMap) { + const { + scriptsSourceMap = false, + stylesSourceMap = false, + } = wco.buildOptions; + + extraPlugins.push(getSourceMapDevTool( + scriptsSourceMap, + stylesSourceMap, + false, + true, + )); + } + return { mode: 'development', resolve: { @@ -63,7 +78,7 @@ export function getTestConfig( 'browser', 'module', 'main', ], }, - devtool: buildOptions.sourceMap ? 'inline-source-map' : 'eval', + devtool: buildOptions.sourceMap ? false : 'eval', entry: { main: path.resolve(root, buildOptions.main), }, diff --git a/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/typescript.ts b/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/typescript.ts index f03f522f8417..b6303a853e13 100644 --- a/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/typescript.ts +++ b/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/typescript.ts @@ -75,7 +75,7 @@ function _createAotPlugin( locale: buildOptions.i18nLocale, platform: buildOptions.platform === 'server' ? PLATFORM.Server : PLATFORM.Browser, missingTranslation: buildOptions.i18nMissingTranslation, - sourceMap: buildOptions.sourceMap, + sourceMap: buildOptions.scriptsSourceMap, additionalLazyModules, hostReplacementPaths, nameLazyFiles: buildOptions.namedChunks, @@ -108,7 +108,7 @@ export function getAotConfig( if (buildOptions.buildOptimizer) { loaders.unshift({ loader: buildOptimizerLoader, - options: { sourceMap: buildOptions.sourceMap } + options: { sourceMap: buildOptions.scriptsSourceMap } }); } 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 f32f3f8afabb..2fe43e7ea98a 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 @@ -11,6 +11,7 @@ import * as path from 'path'; import { basename, normalize } from '@angular-devkit/core'; import { ExtraEntryPoint, ExtraEntryPointObject } from '../../../browser/schema'; +import { SourceMapDevToolPlugin } from 'webpack'; export const ngAppResolve = (resolvePath: string): string => { return path.resolve(process.cwd(), resolvePath); @@ -66,3 +67,25 @@ export function normalizeExtraEntryPoints( return normalizedEntry; }) } + +export function getSourceMapDevTool( + scriptsSourceMap: boolean, + stylesSourceMap: boolean, + hiddenSourceMap = false, + inlineSourceMap = false, +): SourceMapDevToolPlugin { + const include = []; + if (scriptsSourceMap) { + include.push(/js$/); + } + + if (stylesSourceMap) { + include.push(/css$/); + } + + return new SourceMapDevToolPlugin({ + filename: inlineSourceMap ? undefined : '[file].map', + include, + append: hiddenSourceMap ? false : undefined, + }); +} diff --git a/packages/angular_devkit/build_angular/src/browser/index.ts b/packages/angular_devkit/build_angular/src/browser/index.ts index ab49981a9f0c..1f3b3fa468f9 100644 --- a/packages/angular_devkit/build_angular/src/browser/index.ts +++ b/packages/angular_devkit/build_angular/src/browser/index.ts @@ -34,8 +34,18 @@ import { statsToString, statsWarningsToString, } from '../angular-cli-files/utilities/stats'; -import { defaultProgress, normalizeAssetPatterns, normalizeFileReplacements } from '../utils'; -import { AssetPatternObject, BrowserBuilderSchema, CurrentFileReplacement } from './schema'; +import { + NormalizedSourceMaps, + defaultProgress, + normalizeAssetPatterns, + normalizeFileReplacements, + normalizeSourceMaps, +} from '../utils'; +import { + AssetPatternObject, + BrowserBuilderSchema, + CurrentFileReplacement, +} from './schema'; const SpeedMeasurePlugin = require('speed-measure-webpack-plugin'); const webpackMerge = require('webpack-merge'); @@ -45,7 +55,9 @@ const webpackMerge = require('webpack-merge'); // Right now this normalization has to be done in all other builders that make use of the // BrowserBuildSchema and BrowserBuilder.buildWebpackConfig. // It would really help if it happens during architect.validateBuilderOptions, or similar. -export interface NormalizedBrowserBuilderSchema extends BrowserBuilderSchema { +export interface NormalizedBrowserBuilderSchema extends + Pick>, + NormalizedSourceMaps { assets: AssetPatternObject[]; fileReplacements: CurrentFileReplacement[]; } @@ -55,7 +67,7 @@ export class BrowserBuilder implements Builder { constructor(public context: BuilderContext) { } run(builderConfig: BuilderConfiguration): Observable { - const options = builderConfig.options; + let options = builderConfig.options; const root = this.context.workspace.root; const projectRoot = resolve(root, builderConfig.root); const host = new virtualFs.AliasHost(this.context.host as virtualFs.Host); @@ -71,6 +83,17 @@ export class BrowserBuilder implements Builder { options.assets, host, root, projectRoot, builderConfig.sourceRoot)), // Replace the assets in options with the normalized version. tap((assetPatternObjects => options.assets = assetPatternObjects)), + tap(() => { + const normalizedOptions = normalizeSourceMaps(options.sourceMap); + // todo: remove when removing the deprecations + normalizedOptions.vendorSourceMap + = normalizedOptions.vendorSourceMap || !!options.vendorSourceMap; + + options = { + ...options, + ...normalizedOptions, + }; + }), concatMap(() => { let webpackConfig; try { diff --git a/packages/angular_devkit/build_angular/src/browser/schema.d.ts b/packages/angular_devkit/build_angular/src/browser/schema.d.ts index 33ed974acadd..ba27d8ab0418 100644 --- a/packages/angular_devkit/build_angular/src/browser/schema.d.ts +++ b/packages/angular_devkit/build_angular/src/browser/schema.d.ts @@ -69,10 +69,11 @@ export interface BrowserBuilderSchema { /** * Output sourcemaps. */ - sourceMap: boolean; + sourceMap: SourceMapOptions; /** * Resolve vendor packages sourcemaps. + * @deprecated - use `sourceMap.vendor` instead */ vendorSourceMap?: boolean; @@ -236,6 +237,19 @@ export interface BrowserBuilderSchema { profile: boolean; } +export type SourceMapOptions = boolean | SourceMapObject; + +export interface SourceMapObject { + /** Output sourcemaps used for error reports. */ + hidden?: boolean; + /** Resolve vendor packages sourcemaps */ + vendor?: boolean; + /** Output sourcemaps for all scripts */ + scripts?: boolean; + /** Output sourcemaps for all styles. */ + styles?: boolean; +} + export type AssetPattern = string | AssetPatternObject; export interface AssetPatternObject { diff --git a/packages/angular_devkit/build_angular/src/browser/schema.json b/packages/angular_devkit/build_angular/src/browser/schema.json index 2f0259214116..38c7e541dc43 100644 --- a/packages/angular_devkit/build_angular/src/browser/schema.json +++ b/packages/angular_devkit/build_angular/src/browser/schema.json @@ -82,13 +82,44 @@ "default": false }, "sourceMap": { - "type": "boolean", "description": "Output sourcemaps.", - "default": true + "default": true, + "oneOf": [ + { + "type": "object", + "properties": { + "scripts": { + "type": "boolean", + "description": "Output sourcemaps for all scripts.", + "default": true + }, + "styles": { + "type": "boolean", + "description": "Output sourcemaps for all styles.", + "default": true + }, + "hidden": { + "type": "boolean", + "description": "Output sourcemaps used for error reporting tools.", + "default": false + }, + "vendor": { + "type": "boolean", + "description": "Resolve vendor packages sourcemaps.", + "default": false + } + }, + "additionalProperties": false + }, + { + "type": "boolean" + } + ] }, "vendorSourceMap": { "type": "boolean", "description": "Resolve vendor packages sourcemaps.", + "x-deprecated": true, "default": false }, "evalSourceMap": { @@ -415,4 +446,4 @@ ] } } -} +} \ No newline at end of file diff --git a/packages/angular_devkit/build_angular/src/dev-server/index.ts b/packages/angular_devkit/build_angular/src/dev-server/index.ts index 409115dacb54..f72fe3caa3be 100644 --- a/packages/angular_devkit/build_angular/src/dev-server/index.ts +++ b/packages/angular_devkit/build_angular/src/dev-server/index.ts @@ -24,11 +24,15 @@ import * as WebpackDevServer from 'webpack-dev-server'; import { checkPort } from '../angular-cli-files/utilities/check-port'; import { BrowserBuilder, NormalizedBrowserBuilderSchema, getBrowserLoggingCb } from '../browser/'; import { BrowserBuilderSchema } from '../browser/schema'; -import { normalizeAssetPatterns, normalizeFileReplacements } from '../utils'; +import { normalizeAssetPatterns, normalizeFileReplacements, normalizeSourceMaps } from '../utils'; const opn = require('opn'); -export interface DevServerBuilderOptions { +export interface DevServerBuilderOptions extends Pick { browserTarget: string; port: number; host: string; @@ -45,21 +49,6 @@ export interface DevServerBuilderOptions { watch: boolean; hmrWarning: boolean; servePathDefaultWarning: boolean; - - // These options come from the browser builder and are provided here for convenience. - optimization?: boolean; - aot?: boolean; - sourceMap?: boolean; - vendorSourceMap?: boolean; - /**@deprecated */ - evalSourceMap?: boolean; - vendorChunk?: boolean; - commonChunk?: boolean; - baseHref?: string; - deployUrl?: string; - progress?: boolean; - poll?: number; - verbose?: boolean; } type DevServerBuilderOptionsKeys = Extract; @@ -88,14 +77,24 @@ export class DevServerBuilder implements Builder { browserOptions.assets, host, root, projectRoot, builderConfig.sourceRoot)), // Replace the assets in options with the normalized version. tap((assetPatternObjects => browserOptions.assets = assetPatternObjects)), + tap(() => { + const normalizedOptions = normalizeSourceMaps(browserOptions.sourceMap); + // todo: remove when removing the deprecations + normalizedOptions.vendorSourceMap + = normalizedOptions.vendorSourceMap || !!browserOptions.vendorSourceMap; + + browserOptions = { + ...browserOptions, + ...normalizedOptions, + }; + }), concatMap(() => { const webpackConfig = this.buildWebpackConfig( root, projectRoot, host, browserOptions as NormalizedBrowserBuilderSchema); let webpackDevServerConfig: WebpackDevServer.Configuration; try { - webpackDevServerConfig = this._buildServerConfig( - root, projectRoot, options, browserOptions); + webpackDevServerConfig = this._buildServerConfig(root, options, browserOptions); } catch (err) { return throwError(err); } @@ -172,7 +171,8 @@ export class DevServerBuilder implements Builder { return buildEvent; }), - ); + // using more than 10 operators will cause rxjs to loose the types + ) as Observable; } buildWebpackConfig( @@ -190,7 +190,6 @@ export class DevServerBuilder implements Builder { private _buildServerConfig( root: Path, - projectRoot: Path, options: DevServerBuilderOptions, browserOptions: BrowserBuilderSchema, ) { diff --git a/packages/angular_devkit/build_angular/src/dev-server/schema.json b/packages/angular_devkit/build_angular/src/dev-server/schema.json index e2f5099fe589..c69da96418a2 100644 --- a/packages/angular_devkit/build_angular/src/dev-server/schema.json +++ b/packages/angular_devkit/build_angular/src/dev-server/schema.json @@ -92,12 +92,44 @@ "description": "Build using Ahead of Time compilation." }, "sourceMap": { - "type": "boolean", - "description": "Output sourcemaps." + "description": "Output sourcemaps.", + "default": true, + "oneOf": [ + { + "type": "object", + "properties": { + "scripts": { + "type": "boolean", + "description": "Output sourcemaps for all scripts.", + "default": true + }, + "styles": { + "type": "boolean", + "description": "Output sourcemaps for all styles.", + "default": true + }, + "hidden": { + "type": "boolean", + "description": "Output sourcemaps used for error reporting tools.", + "default": false + }, + "vendor": { + "type": "boolean", + "description": "Resolve vendor packages sourcemaps.", + "default": false + } + }, + "additionalProperties": false + }, + { + "type": "boolean" + } + ] }, "vendorSourceMap": { "type": "boolean", "description": "Resolve vendor packages sourcemaps.", + "x-deprecated": true, "default": false }, "evalSourceMap": { @@ -134,4 +166,4 @@ "required": [ "browserTarget" ] -} +} \ No newline at end of file diff --git a/packages/angular_devkit/build_angular/src/karma/index.ts b/packages/angular_devkit/build_angular/src/karma/index.ts index 1bd80e91196e..431506ed59ec 100644 --- a/packages/angular_devkit/build_angular/src/karma/index.ts +++ b/packages/angular_devkit/build_angular/src/karma/index.ts @@ -27,7 +27,12 @@ import { import { readTsconfig } from '../angular-cli-files/utilities/read-tsconfig'; import { requireProjectModule } from '../angular-cli-files/utilities/require-project-module'; import { AssetPatternObject, CurrentFileReplacement } from '../browser/schema'; -import { defaultProgress, normalizeAssetPatterns, normalizeFileReplacements } from '../utils'; +import { + defaultProgress, + normalizeAssetPatterns, + normalizeFileReplacements, + normalizeSourceMaps, +} from '../utils'; import { KarmaBuilderSchema } from './schema'; const webpackMerge = require('webpack-merge'); @@ -41,7 +46,7 @@ export class KarmaBuilder implements Builder { constructor(public context: BuilderContext) { } run(builderConfig: BuilderConfiguration): Observable { - const options = builderConfig.options; + let options = builderConfig.options; const root = this.context.workspace.root; const projectRoot = resolve(root, builderConfig.root); const host = new virtualFs.AliasHost(this.context.host as virtualFs.Host); @@ -53,6 +58,17 @@ export class KarmaBuilder implements Builder { options.assets, host, root, projectRoot, builderConfig.sourceRoot)), // Replace the assets in options with the normalized version. tap((assetPatternObjects => options.assets = assetPatternObjects)), + tap(() => { + const normalizedOptions = normalizeSourceMaps(options.sourceMap); + // todo: remove when removing the deprecations + normalizedOptions.vendorSourceMap + = normalizedOptions.vendorSourceMap || !!options.vendorSourceMap; + + options = { + ...options, + ...normalizedOptions, + }; + }), concatMap(() => new Observable(obs => { const karma = requireProjectModule(getSystemPath(projectRoot), 'karma'); const karmaConfig = getSystemPath(resolve(root, normalize(options.karmaConfig))); diff --git a/packages/angular_devkit/build_angular/src/karma/schema.d.ts b/packages/angular_devkit/build_angular/src/karma/schema.d.ts index 4ea24b6afe19..632e6a55a5c4 100644 --- a/packages/angular_devkit/build_angular/src/karma/schema.d.ts +++ b/packages/angular_devkit/build_angular/src/karma/schema.d.ts @@ -7,17 +7,20 @@ */ import { BrowserBuilderSchema } from '../browser/schema'; - -// TODO: in TS 2.8 use Extract instead of Pick to make a subset of another type. export interface KarmaBuilderSchema extends Pick { /** * The name of the Karma configuration file.. */ karmaConfig: string; + /** + * Output sourcemaps. + */ + sourceMap: KarmaSourceMapOptions; + /** * Override which browsers tests are run against. */ @@ -38,3 +41,14 @@ export interface KarmaBuilderSchema extends Pick { constructor(public context: BuilderContext) { } run(builderConfig: BuilderConfiguration): Observable { - const options = builderConfig.options; + let options = builderConfig.options; const root = this.context.workspace.root; const projectRoot = resolve(root, builderConfig.root); const host = new virtualFs.AliasHost(this.context.host as virtualFs.Host); @@ -51,8 +51,19 @@ export class ServerBuilder implements Builder { concatMap(() => options.deleteOutputPath ? this._deleteOutputDir(root, normalize(options.outputPath), this.context.host) : of(null)), - concatMap(() => normalizeFileReplacements(options.fileReplacements, host, root)), - tap(fileReplacements => options.fileReplacements = fileReplacements), + concatMap(() => normalizeFileReplacements(options.fileReplacements, host, root)), + tap(fileReplacements => options.fileReplacements = fileReplacements), + tap(() => { + const normalizedOptions = normalizeSourceMaps(options.sourceMap); + // todo: remove when removing the deprecations + normalizedOptions.vendorSourceMap + = normalizedOptions.vendorSourceMap || !!options.vendorSourceMap; + + options = { + ...options, + ...normalizedOptions, + }; + }), concatMap(() => { const webpackConfig = this.buildWebpackConfig(root, projectRoot, host, options); diff --git a/packages/angular_devkit/build_angular/src/server/schema.d.ts b/packages/angular_devkit/build_angular/src/server/schema.d.ts index 0addbe5222e0..35acca4b0ae7 100644 --- a/packages/angular_devkit/build_angular/src/server/schema.d.ts +++ b/packages/angular_devkit/build_angular/src/server/schema.d.ts @@ -6,17 +6,13 @@ * found in the LICENSE file at https://angular.io/license */ -import { FileReplacement } from '../browser/schema'; +import { BrowserBuilderSchema, FileReplacement, SourceMapOptions } from '../browser/schema'; export interface BuildWebpackServerSchema { /** * The name of the TypeScript configuration file. */ tsConfig: string; - /** - * Output sourcemaps. - */ - sourceMap?: boolean; /** * Adds more details to output logging. */ @@ -37,8 +33,13 @@ export interface BuildWebpackServerSchema { * Use a separate bundle containing only vendor libraries. */ vendorChunk?: boolean; + /** + * Output sourcemaps. + */ + sourceMap: SourceMapOptions; /** * Resolve vendor packages sourcemaps. + * @deprecated use sourceMap.vendor */ vendorSourceMap?: boolean; /** diff --git a/packages/angular_devkit/build_angular/src/server/schema.json b/packages/angular_devkit/build_angular/src/server/schema.json index de93450c3297..3cd83727fd61 100644 --- a/packages/angular_devkit/build_angular/src/server/schema.json +++ b/packages/angular_devkit/build_angular/src/server/schema.json @@ -50,13 +50,44 @@ "default": "" }, "sourceMap": { - "type": "boolean", "description": "Output sourcemaps.", - "default": true + "default": true, + "oneOf": [ + { + "type": "object", + "properties": { + "scripts": { + "type": "boolean", + "description": "Output sourcemaps for all scripts.", + "default": true + }, + "styles": { + "type": "boolean", + "description": "Output sourcemaps for all styles.", + "default": true + }, + "hidden": { + "type": "boolean", + "description": "Output sourcemaps used for error reporting tools.", + "default": false + }, + "vendor": { + "type": "boolean", + "description": "Resolve vendor packages sourcemaps.", + "default": false + } + }, + "additionalProperties": false + }, + { + "type": "boolean" + } + ] }, "vendorSourceMap": { "type": "boolean", "description": "Resolve vendor packages sourcemaps.", + "x-deprecated": true, "default": false }, "evalSourceMap": { @@ -220,6 +251,5 @@ } ] } - } } \ No newline at end of file diff --git a/packages/angular_devkit/build_angular/src/utils/index.ts b/packages/angular_devkit/build_angular/src/utils/index.ts index be8b36d7091a..e4b77f57dcbe 100644 --- a/packages/angular_devkit/build_angular/src/utils/index.ts +++ b/packages/angular_devkit/build_angular/src/utils/index.ts @@ -10,3 +10,4 @@ export * from './default-progress'; export * from './run-module-as-observable-fork'; export * from './normalize-file-replacements'; export * from './normalize-asset-patterns'; +export * from './normalize-source-maps'; diff --git a/packages/angular_devkit/build_angular/src/utils/normalize-source-maps.ts b/packages/angular_devkit/build_angular/src/utils/normalize-source-maps.ts new file mode 100644 index 000000000000..41392886b8b8 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/utils/normalize-source-maps.ts @@ -0,0 +1,32 @@ +/** + * @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 { SourceMapOptions } from '../browser/schema'; + +export interface NormalizedSourceMaps { + sourceMap: boolean; + scriptsSourceMap: boolean; + stylesSourceMap: boolean; + hiddenSourceMap: boolean; + vendorSourceMap: boolean; +} + +export function normalizeSourceMaps(sourceMap: SourceMapOptions): NormalizedSourceMaps { + const scriptsSourceMap = !!(typeof sourceMap === 'object' ? sourceMap.scripts : sourceMap); + const stylesSourceMap = !!(typeof sourceMap === 'object' ? sourceMap.styles : sourceMap); + const hiddenSourceMap = typeof sourceMap === 'object' && !!sourceMap.hidden; + const vendorSourceMap = typeof sourceMap === 'object' && !!sourceMap.vendor; + + return { + sourceMap: stylesSourceMap || scriptsSourceMap, + vendorSourceMap, + hiddenSourceMap, + scriptsSourceMap, + stylesSourceMap, + }; +} diff --git a/packages/angular_devkit/build_angular/test/browser/source-map_spec_large.ts b/packages/angular_devkit/build_angular/test/browser/source-map_spec_large.ts index 4708376f1f7f..64d172c6c921 100644 --- a/packages/angular_devkit/build_angular/test/browser/source-map_spec_large.ts +++ b/packages/angular_devkit/build_angular/test/browser/source-map_spec_large.ts @@ -9,9 +9,10 @@ import { runTargetSpec } from '@angular-devkit/architect/testing'; import { join, normalize, virtualFs } from '@angular-devkit/core'; import { tap } from 'rxjs/operators'; +import { BrowserBuilderSchema, OutputHashing } from '../../src'; import { browserTargetSpec, host } from '../utils'; - +// tslint:disable-next-line:no-big-function describe('Browser Builder source map', () => { const outputPath = normalize('dist'); @@ -19,13 +20,38 @@ describe('Browser Builder source map', () => { afterEach(done => host.restore().toPromise().then(done, done.fail)); it('works', (done) => { - const overrides = { sourceMap: true }; + const overrides: Partial = { + sourceMap: true, + extractCss: true, + styles: ['src/styles.css'], + }; + + host.writeMultipleFiles({ + 'src/styles.css': `div { flex: 1 }`, + }); runTargetSpec(host, browserTargetSpec, overrides).pipe( tap((buildEvent) => expect(buildEvent.success).toBe(true)), tap(() => { - const fileName = join(outputPath, 'main.js.map'); - expect(host.scopedSync().exists(fileName)).toBe(true); + const scriptsFileName = join(outputPath, 'main.js.map'); + expect(host.scopedSync().exists(scriptsFileName)).toBe(true); + + const stylesFileName = join(outputPath, 'styles.css.map'); + expect(host.scopedSync().exists(stylesFileName)).toBe(true); + }), + ).toPromise().then(done, done.fail); + }); + + it('works with outputHashing', (done) => { + const overrides: Partial = { + sourceMap: true, + outputHashing: OutputHashing.All, + }; + + runTargetSpec(host, browserTargetSpec, overrides).pipe( + tap((buildEvent) => expect(buildEvent.success).toBe(true)), + tap(() => { + expect(host.fileMatchExists(outputPath, /main\.[0-9a-f]{20}\.js.map/)).toBeTruthy(); }), ).toPromise().then(done, done.fail); }); @@ -55,4 +81,143 @@ describe('Browser Builder source map', () => { }), ).toPromise().then(done, done.fail); }); + + it('supports hidden sourcemaps', (done) => { + const overrides: Partial = { + sourceMap: { + hidden: true, + styles: true, + scripts: true, + }, + extractCss: true, + styles: ['src/styles.scss'], + }; + + host.writeMultipleFiles({ + 'src/styles.scss': `div { flex: 1 }`, + }); + + + runTargetSpec(host, browserTargetSpec, overrides).pipe( + tap((buildEvent) => expect(buildEvent.success).toBe(true)), + tap(() => { + expect(host.scopedSync().exists(join(outputPath, 'main.js.map'))).toBe(true); + expect(host.scopedSync().exists(join(outputPath, 'styles.css.map'))).toBe(true); + + const scriptContent = virtualFs.fileBufferToString( + host.scopedSync().read(join(outputPath, 'main.js')), + ); + expect(scriptContent).not.toContain('sourceMappingURL=main.js.map'); + + const styleContent = virtualFs.fileBufferToString( + host.scopedSync().read(join(outputPath, 'styles.css')), + ); + expect(styleContent).not.toContain('sourceMappingURL=styles.css.map'); + }), + ).toPromise().then(done, done.fail); + }); + + it('supports styles only sourcemaps', (done) => { + const overrides: Partial = { + sourceMap: { + styles: true, + scripts: false, + }, + extractCss: true, + styles: ['src/styles.scss'], + }; + + host.writeMultipleFiles({ + 'src/styles.scss': `div { flex: 1 }`, + }); + + runTargetSpec(host, browserTargetSpec, overrides).pipe( + tap((buildEvent) => expect(buildEvent.success).toBe(true)), + tap(() => { + expect(host.scopedSync().exists(join(outputPath, 'main.js.map'))).toBe(false); + expect(host.scopedSync().exists(join(outputPath, 'styles.css.map'))).toBe(true); + + const scriptContent = virtualFs.fileBufferToString( + host.scopedSync().read(join(outputPath, 'main.js')), + ); + expect(scriptContent).not.toContain('sourceMappingURL=main.js.map'); + + const styleContent = virtualFs.fileBufferToString( + host.scopedSync().read(join(outputPath, 'styles.css')), + ); + + expect(styleContent).toContain('sourceMappingURL=styles.css.map'); + }), + ).toPromise().then(done, done.fail); + }); + + it('supports scripts only sourcemaps', (done) => { + const overrides: Partial = { + sourceMap: { + styles: false, + scripts: true, + }, + extractCss: true, + styles: ['src/styles.scss'], + }; + + host.writeMultipleFiles({ + 'src/styles.scss': `div { flex: 1 }`, + }); + + runTargetSpec(host, browserTargetSpec, overrides).pipe( + tap((buildEvent) => expect(buildEvent.success).toBe(true)), + tap(() => { + expect(host.scopedSync().exists(join(outputPath, 'main.js.map'))).toBe(true); + expect(host.scopedSync().exists(join(outputPath, 'styles.css.map'))).toBe(false); + + const scriptContent = virtualFs.fileBufferToString( + host.scopedSync().read(join(outputPath, 'main.js')), + ); + expect(scriptContent).toContain('sourceMappingURL=main.js.map'); + + const styleContent = virtualFs.fileBufferToString( + host.scopedSync().read(join(outputPath, 'styles.css')), + ); + expect(styleContent).not.toContain('sourceMappingURL=styles.css.map'); + }), + ).toPromise().then(done, done.fail); + }); + + it('should not inline component styles sourcemaps when hidden', (done) => { + const overrides: Partial = { + sourceMap: { + hidden: true, + styles: true, + scripts: true, + }, + extractCss: true, + styles: ['src/styles.scss'], + }; + + host.writeMultipleFiles({ + 'src/styles.scss': `div { flex: 1 }`, + 'src/app/app.component.css': `p { color: red; }`, + }); + + runTargetSpec(host, browserTargetSpec, overrides).pipe( + tap((buildEvent) => expect(buildEvent.success).toBe(true)), + tap(() => { + expect(host.scopedSync().exists(join(outputPath, 'main.js.map'))).toBe(true); + expect(host.scopedSync().exists(join(outputPath, 'styles.css.map'))).toBe(true); + + const scriptContent = virtualFs.fileBufferToString( + host.scopedSync().read(join(outputPath, 'main.js')), + ); + + expect(scriptContent).not.toContain('sourceMappingURL=main.js.map'); + expect(scriptContent).not.toContain('sourceMappingURL=data:application/json'); + + const styleContent = virtualFs.fileBufferToString( + host.scopedSync().read(join(outputPath, 'styles.css')), + ); + expect(styleContent).not.toContain('sourceMappingURL=styles.css.map'); + }), + ).toPromise().then(done, done.fail); + }); }); diff --git a/packages/angular_devkit/build_angular/test/browser/vendor-source-map_spec_large.ts b/packages/angular_devkit/build_angular/test/browser/vendor-source-map_spec_large.ts index 45bc61914039..00f7f52002df 100644 --- a/packages/angular_devkit/build_angular/test/browser/vendor-source-map_spec_large.ts +++ b/packages/angular_devkit/build_angular/test/browser/vendor-source-map_spec_large.ts @@ -10,6 +10,7 @@ import { runTargetSpec } from '@angular-devkit/architect/testing'; import { join, normalize, virtualFs } from '@angular-devkit/core'; import * as path from 'path'; import { tap } from 'rxjs/operators'; +import { BrowserBuilderSchema } from '../../src/browser/schema'; import { browserTargetSpec, host } from '../utils'; describe('Browser Builder external source map', () => { @@ -19,7 +20,13 @@ describe('Browser Builder external source map', () => { afterEach(done => host.restore().toPromise().then(done, done.fail)); it('works', (done) => { - const overrides = { sourceMap: true, vendorSourceMap: true }; + const overrides: Partial = { + sourceMap: { + scripts: true, + styles: true, + vendor: true, + }, + }; runTargetSpec(host, browserTargetSpec, overrides).pipe( tap((buildEvent) => expect(buildEvent.success).toBe(true)), @@ -35,7 +42,13 @@ describe('Browser Builder external source map', () => { }); it('does not map sourcemaps from external library when disabled', (done) => { - const overrides = { sourceMap: true, vendorSourceMap: false }; + const overrides: Partial = { + sourceMap: { + scripts: true, + styles: true, + vendor: false, + }, + }; runTargetSpec(host, browserTargetSpec, overrides).pipe( tap((buildEvent) => expect(buildEvent.success).toBe(true)), diff --git a/packages/angular_devkit/build_angular/test/server/base_spec_large.ts b/packages/angular_devkit/build_angular/test/server/base_spec_large.ts index 845666bf5e05..67c941150413 100644 --- a/packages/angular_devkit/build_angular/test/server/base_spec_large.ts +++ b/packages/angular_devkit/build_angular/test/server/base_spec_large.ts @@ -9,6 +9,7 @@ import { runTargetSpec } from '@angular-devkit/architect/testing'; import { join, normalize, virtualFs } from '@angular-devkit/core'; import { take, tap } from 'rxjs/operators'; +import { BuildWebpackServerSchema } from '../../src/server/schema'; import { host } from '../utils'; @@ -47,6 +48,58 @@ describe('Server Builder', () => { ).toPromise().then(done, done.fail); }); + it('supports scripts only sourcemaps', (done) => { + const overrides: Partial = { + sourceMap: { + styles: false, + scripts: true, + }, + }; + + host.writeMultipleFiles({ + 'src/app/app.component.css': `p { color: red; }`, + }); + + runTargetSpec(host, { project: 'app', target: 'server' }, overrides).pipe( + tap((buildEvent) => expect(buildEvent.success).toBe(true)), + tap(() => { + expect(host.scopedSync().exists(join(outputPath, 'main.js.map'))).toBe(true); + + const scriptContent = virtualFs.fileBufferToString( + host.scopedSync().read(join(outputPath, 'main.js')), + ); + expect(scriptContent).toContain('sourceMappingURL=main.js.map'); + expect(scriptContent).not.toContain('sourceMappingURL=data:application/json'); + }), + ).toPromise().then(done, done.fail); + }); + + it('supports component styles sourcemaps', (done) => { + const overrides: Partial = { + sourceMap: { + styles: true, + scripts: true, + }, + }; + + host.writeMultipleFiles({ + 'src/app/app.component.css': `p { color: red; }`, + }); + + runTargetSpec(host, { project: 'app', target: 'server' }, overrides).pipe( + tap((buildEvent) => expect(buildEvent.success).toBe(true)), + tap(() => { + expect(host.scopedSync().exists(join(outputPath, 'main.js.map'))).toBe(true); + + const scriptContent = virtualFs.fileBufferToString( + host.scopedSync().read(join(outputPath, 'main.js')), + ); + expect(scriptContent).toContain('sourceMappingURL=main.js.map'); + expect(scriptContent).toContain('sourceMappingURL=data:application/json'); + }), + ).toPromise().then(done, done.fail); + }); + it('runs watch mode', (done) => { const overrides = { watch: true };