| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent cf24008 commit 1585ac0
3 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -10,6 +10,10 @@ import { Architect } from '@angular-devkit/architect'; | |||
| 10 | 10 | import * as path from 'path'; | |
| 11 | 11 | import { browserBuild, createArchitect, host } from '../../../testing/test-utils'; | |
| 12 | 12 | ||
| 13 | + // Following the naming conventions from | ||
| 14 | + // https://sourcemaps.info/spec.html#h.ghqpj1ytqjbm | ||
| 15 | + const IGNORE_LIST = 'x_google_devtoolsIgnore'; | ||
| 16 | + | ||
| 13 | 17 | describe('Browser Builder external source map', () => { | |
| 14 | 18 | const target = { project: 'app', target: 'build' }; | |
| 15 | 19 | let architect: Architect; | |
@@ -50,3 +54,90 @@ describe('Browser Builder external source map', () => { | |||
| 50 | 54 | expect(hasTsSourcePaths).toBe(false, `vendor.js.map not should have '.ts' extentions`); | |
| 51 | 55 | }); | |
| 52 | 56 | }); | |
| 57 | + | ||
| 58 | + describe('Identifying third-party code in source maps', () => { | ||
| 59 | + interface SourceMap { | ||
| 60 | + sources: string[]; | ||
| 61 | + [IGNORE_LIST]: number[]; | ||
| 62 | + } | ||
| 63 | + | ||
| 64 | + const target = { project: 'app', target: 'build' }; | ||
| 65 | + let architect: Architect; | ||
| 66 | + | ||
| 67 | + beforeEach(async () => { | ||
| 68 | + await host.initialize().toPromise(); | ||
| 69 | + architect = (await createArchitect(host.root())).architect; | ||
| 70 | + }); | ||
| 71 | + afterEach(async () => host.restore().toPromise()); | ||
| 72 | + | ||
| 73 | + it('is a noop when vendoring processing is enabled', async () => { | ||
| 74 | + const overrides = { | ||
| 75 | + sourceMap: { | ||
| 76 | + scripts: true, | ||
| 77 | + vendor: true, | ||
| 78 | + }, | ||
| 79 | + }; | ||
| 80 | + | ||
| 81 | + const { files } = await browserBuild(architect, host, target, overrides); | ||
| 82 | + const mainMap: SourceMap = JSON.parse(await files['main.js.map']); | ||
| 83 | + const polyfillsMap: SourceMap = JSON.parse(await files['polyfills.js.map']); | ||
| 84 | + const runtimeMap: SourceMap = JSON.parse(await files['runtime.js.map']); | ||
| 85 | + const vendorMap: SourceMap = JSON.parse(await files['vendor.js.map']); | ||
| 86 | + | ||
| 87 | + expect(mainMap[IGNORE_LIST]).toBeUndefined(); | ||
| 88 | + expect(polyfillsMap[IGNORE_LIST]).toBeUndefined(); | ||
| 89 | + expect(runtimeMap[IGNORE_LIST]).toBeUndefined(); | ||
| 90 | + expect(vendorMap[IGNORE_LIST]).toBeUndefined(); | ||
| 91 | + }); | ||
| 92 | + | ||
| 93 | + it('specifies which sources are third party when vendor processing is disabled', async () => { | ||
| 94 | + const overrides = { | ||
| 95 | + sourceMap: { | ||
| 96 | + scripts: true, | ||
| 97 | + vendor: false, | ||
| 98 | + }, | ||
| 99 | + }; | ||
| 100 | + | ||
| 101 | + const { files } = await browserBuild(architect, host, target, overrides); | ||
| 102 | + const mainMap: SourceMap = JSON.parse(await files['main.js.map']); | ||
| 103 | + const polyfillsMap: SourceMap = JSON.parse(await files['polyfills.js.map']); | ||
| 104 | + const runtimeMap: SourceMap = JSON.parse(await files['runtime.js.map']); | ||
| 105 | + const vendorMap: SourceMap = JSON.parse(await files['vendor.js.map']); | ||
| 106 | + | ||
| 107 | + expect(mainMap[IGNORE_LIST]).not.toBeUndefined(); | ||
| 108 | + expect(polyfillsMap[IGNORE_LIST]).not.toBeUndefined(); | ||
| 109 | + expect(runtimeMap[IGNORE_LIST]).not.toBeUndefined(); | ||
| 110 | + expect(vendorMap[IGNORE_LIST]).not.toBeUndefined(); | ||
| 111 | + | ||
| 112 | + expect(mainMap[IGNORE_LIST].length).toEqual(0); | ||
| 113 | + expect(polyfillsMap[IGNORE_LIST].length).not.toEqual(0); | ||
| 114 | + expect(runtimeMap[IGNORE_LIST].length).not.toEqual(0); | ||
| 115 | + expect(vendorMap[IGNORE_LIST].length).not.toEqual(0); | ||
| 116 | + | ||
| 117 | + const thirdPartyInMain = mainMap.sources.some((s) => s.includes('node_modules')); | ||
| 118 | + const thirdPartyInPolyfills = polyfillsMap.sources.some((s) => s.includes('node_modules')); | ||
| 119 | + const thirdPartyInRuntime = runtimeMap.sources.some((s) => s.includes('webpack')); | ||
| 120 | + const thirdPartyInVendor = vendorMap.sources.some((s) => s.includes('node_modules')); | ||
| 121 | + expect(thirdPartyInMain).toBe(false, `main.js.map should not include any node modules`); | ||
| 122 | + expect(thirdPartyInPolyfills).toBe(true, `polyfills.js.map should include some node modules`); | ||
| 123 | + expect(thirdPartyInRuntime).toBe(true, `runtime.js.map should include some webpack code`); | ||
| 124 | + expect(thirdPartyInVendor).toBe(true, `vendor.js.map should include some node modules`); | ||
| 125 | + | ||
| 126 | + // All sources in the main map are first-party. | ||
| 127 | + expect(mainMap.sources.filter((_, i) => !mainMap[IGNORE_LIST].includes(i))).toEqual([ | ||
| 128 | + './src/app/app.component.ts', | ||
| 129 | + './src/app/app.module.ts', | ||
| 130 | + './src/environments/environment.ts', | ||
| 131 | + './src/main.ts', | ||
| 132 | + ]); | ||
| 133 | + | ||
| 134 | + // Only some sources in the polyfills map are first-party. | ||
| 135 | + expect(polyfillsMap.sources.filter((_, i) => !polyfillsMap[IGNORE_LIST].includes(i))).toEqual([ | ||
| 136 | + './src/polyfills.ts', | ||
| 137 | + ]); | ||
| 138 | + | ||
| 139 | + // None of the sources in the runtime and vendor maps are first-party. | ||
| 140 | + expect(runtimeMap.sources.filter((_, i) => !runtimeMap[IGNORE_LIST].includes(i))).toEqual([]); | ||
| 141 | + expect(vendorMap.sources.filter((_, i) => !vendorMap[IGNORE_LIST].includes(i))).toEqual([]); | ||
| 142 | + }); | ||
| 143 | + }); | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -29,6 +29,7 @@ import { | |||
| 29 | 29 | JsonStatsPlugin, | |
| 30 | 30 | ScriptsWebpackPlugin, | |
| 31 | 31 | } from '../plugins'; | |
| 32 | + import { DevToolsIgnorePlugin } from '../plugins/devtools-ignore-plugin'; | ||
| 32 | 33 | import { NamedChunksPlugin } from '../plugins/named-chunks-plugin'; | |
| 33 | 34 | import { ProgressPlugin } from '../plugins/progress-plugin'; | |
| 34 | 35 | import { TransferSizePlugin } from '../plugins/transfer-size-plugin'; | |
@@ -45,6 +46,9 @@ import { | |||
| 45 | 46 | globalScriptsByBundleName, | |
| 46 | 47 | } from '../utils/helpers'; | |
| 47 | 48 | ||
| 49 | + const VENDORS_TEST = /[\\/]node_modules[\\/]/; | ||
| 50 | + const RUNTIME_TEST = /^webpack\//; | ||
| 51 | + | ||
| 48 | 52 | // eslint-disable-next-line max-lines-per-function | |
| 49 | 53 | export async function getCommonConfig(wco: WebpackConfigOptions): Promise<Configuration> { | |
| 50 | 54 | const { | |
@@ -190,6 +194,19 @@ export async function getCommonConfig(wco: WebpackConfigOptions): Promise<Config | |||
| 190 | 194 | include.push(/css$/); | |
| 191 | 195 | } | |
| 192 | 196 | ||
| 197 | + if (!vendorSourceMap) { | ||
| 198 | + extraPlugins.push( | ||
| 199 | + new DevToolsIgnorePlugin({ | ||
| 200 | + vendors: { | ||
| 201 | + test: VENDORS_TEST, | ||
| 202 | + }, | ||
| 203 | + runtime: !isPlatformServer && { | ||
| 204 | + test: RUNTIME_TEST, | ||
| 205 | + }, | ||
| 206 | + }), | ||
| 207 | + ); | ||
| 208 | + } | ||
| 209 | + | ||
| 193 | 210 | extraPlugins.push( | |
| 194 | 211 | new SourceMapDevToolPlugin({ | |
| 195 | 212 | filename: '[file].map', | |
@@ -434,7 +451,7 @@ export async function getCommonConfig(wco: WebpackConfigOptions): Promise<Config | |||
| 434 | 451 | name: 'vendor', | |
| 435 | 452 | chunks: (chunk) => chunk.name === 'main', | |
| 436 | 453 | enforce: true, | |
| 437 | - test: /[\\/]node_modules[\\/]/, | ||
| 454 | + test: VENDORS_TEST, | ||
| 438 | 455 | }, | |
| 439 | 456 | }, | |
| 440 | 457 | }, | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,83 @@ | |||
| 1 | + /** | ||
| 2 | + * @license | ||
| 3 | + * Copyright Google LLC All Rights Reserved. | ||
| 4 | + * | ||
| 5 | + * Use of this source code is governed by an MIT-style license that can be | ||
| 6 | + * found in the LICENSE file at https://angular.io/license | ||
| 7 | + */ | ||
| 8 | + | ||
| 9 | + import { Compilation, Compiler } from 'webpack'; | ||
| 10 | + | ||
| 11 | + // Following the naming conventions from | ||
| 12 | + // https://sourcemaps.info/spec.html#h.ghqpj1ytqjbm | ||
| 13 | + const IGNORE_LIST = 'x_google_devtoolsIgnore'; | ||
| 14 | + | ||
| 15 | + const PLUGIN_NAME = 'devtools-ignore-plugin'; | ||
| 16 | + | ||
| 17 | + interface Options { | ||
| 18 | + vendors?: false | { test: RegExp }; | ||
| 19 | + runtime?: false | { test: RegExp }; | ||
| 20 | + } | ||
| 21 | + | ||
| 22 | + interface SourceMap { | ||
| 23 | + sources: string[]; | ||
| 24 | + [IGNORE_LIST]: number[]; | ||
| 25 | + } | ||
| 26 | + | ||
| 27 | + /** | ||
| 28 | + * This plugin adds a field to source maps that identifies which sources are | ||
| 29 | + * vendored or runtime-injected (aka third-party) sources. These are consumed by | ||
| 30 | + * Chrome DevTools to automatically ignore-list sources. | ||
| 31 | + */ | ||
| 32 | + export class DevToolsIgnorePlugin { | ||
| 33 | + constructor(private options: Options) {} | ||
| 34 | + | ||
| 35 | + apply(compiler: Compiler) { | ||
| 36 | + const { RawSource } = compiler.webpack.sources; | ||
| 37 | + | ||
| 38 | + compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => { | ||
| 39 | + compilation.hooks.processAssets.tap( | ||
| 40 | + { | ||
| 41 | + name: PLUGIN_NAME, | ||
| 42 | + stage: Compilation.PROCESS_ASSETS_STAGE_DEV_TOOLING, | ||
| 43 | + additionalAssets: true, | ||
| 44 | + }, | ||
| 45 | + (assets) => { | ||
| 46 | + let vendorsTest: RegExp | null = null; | ||
| 47 | + let runtimeTest: RegExp | null = null; | ||
| 48 | + if (this.options.vendors) { | ||
| 49 | + vendorsTest = this.options.vendors.test; | ||
| 50 | + } | ||
| 51 | + if (this.options.runtime) { | ||
| 52 | + runtimeTest = this.options.runtime.test; | ||
| 53 | + } | ||
| 54 | + if (!vendorsTest && !runtimeTest) { | ||
| 55 | + return; | ||
| 56 | + } | ||
| 57 | + | ||
| 58 | + for (const [name, asset] of Object.entries(assets)) { | ||
| 59 | + // Instead of using `asset.map` we process the map files RawSource. | ||
| 60 | + // This is because `.map()` is slow and take several seconds. | ||
| 61 | + if (!name.endsWith('.map')) { | ||
| 62 | + // Ignore non map files | ||
| 63 | + continue; | ||
| 64 | + } | ||
| 65 | + | ||
| 66 | + const mapContent = asset.source().toString(); | ||
| 67 | + if (!mapContent) { | ||
| 68 | + continue; | ||
| 69 | + } | ||
| 70 | + | ||
| 71 | + const map = JSON.parse(mapContent) as SourceMap; | ||
| 72 | + | ||
| 73 | + map[IGNORE_LIST] = Object.entries(map.sources) | ||
| 74 | + .filter(([, source]) => vendorsTest?.test(source) || runtimeTest?.test(source)) | ||
| 75 | + .map(([index]) => +index); | ||
| 76 | + | ||
| 77 | + compilation.updateAsset(name, new RawSource(JSON.stringify(map))); | ||
| 78 | + } | ||
| 79 | + }, | ||
| 80 | + ); | ||
| 81 | + }); | ||
| 82 | + } | ||
| 83 | + } | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments