From 766c14698473fe333168c06e3b88c7303e868acf Mon Sep 17 00:00:00 2001 From: Charles Lyding <19598772+clydin@users.noreply.github.com> Date: Tue, 25 Apr 2023 10:43:40 -0400 Subject: [PATCH] fix(@angular-devkit/build-angular): add sourcemap `x_google_ignoreList` support for esbuild builder When using the esbuild-based browser application builder with source maps enabled, the Chrome DevTools `x_google_ignoreList` extension will be added to JavaScript source maps. This extension supports an improved developer experience with Chrome DevTools. For more information, please see https://developer.chrome.com/articles/x-google-ignore-list/ (cherry picked from commit b0d04f77b3720b15d51085061f13865d1f7ff65c) --- .../browser-esbuild/global-scripts.ts | 2 + .../src/builders/browser-esbuild/index.ts | 2 + .../sourcemap-ignorelist-plugin.ts | 85 +++++++++++++++++++ .../tests/options/sourcemap_spec.ts | 13 +++ 4 files changed, 102 insertions(+) create mode 100644 packages/angular_devkit/build_angular/src/builders/browser-esbuild/sourcemap-ignorelist-plugin.ts diff --git a/packages/angular_devkit/build_angular/src/builders/browser-esbuild/global-scripts.ts b/packages/angular_devkit/build_angular/src/builders/browser-esbuild/global-scripts.ts index 151fdd310fc7..a222a974f5ef 100644 --- a/packages/angular_devkit/build_angular/src/builders/browser-esbuild/global-scripts.ts +++ b/packages/angular_devkit/build_angular/src/builders/browser-esbuild/global-scripts.ts @@ -11,6 +11,7 @@ import MagicString, { Bundle } from 'magic-string'; import assert from 'node:assert'; import { readFile } from 'node:fs/promises'; import { NormalizedBrowserOptions } from './options'; +import { createSourcemapIngorelistPlugin } from './sourcemap-ignorelist-plugin'; /** * Create an esbuild 'build' options object for all global scripts defined in the user provied @@ -53,6 +54,7 @@ export function createGlobalScriptsBundleOptions(options: NormalizedBrowserOptio platform: 'neutral', preserveSymlinks, plugins: [ + createSourcemapIngorelistPlugin(), { name: 'angular-global-scripts', setup(build) { diff --git a/packages/angular_devkit/build_angular/src/builders/browser-esbuild/index.ts b/packages/angular_devkit/build_angular/src/builders/browser-esbuild/index.ts index 6b2fc2b3a46e..d66f2d7ff708 100644 --- a/packages/angular_devkit/build_angular/src/builders/browser-esbuild/index.ts +++ b/packages/angular_devkit/build_angular/src/builders/browser-esbuild/index.ts @@ -31,6 +31,7 @@ import { LoadResultCache } from './load-result-cache'; import { BrowserEsbuildOptions, NormalizedBrowserOptions, normalizeOptions } from './options'; import { shutdownSassWorkerPool } from './sass-plugin'; import { Schema as BrowserBuilderOptions } from './schema'; +import { createSourcemapIngorelistPlugin } from './sourcemap-ignorelist-plugin'; import { createStylesheetBundleOptions } from './stylesheets'; import type { ChangedFiles } from './watcher'; @@ -369,6 +370,7 @@ function createCodeBundleOptions( platform: 'browser', preserveSymlinks, plugins: [ + createSourcemapIngorelistPlugin(), createCompilerPlugin( // JS/TS options { diff --git a/packages/angular_devkit/build_angular/src/builders/browser-esbuild/sourcemap-ignorelist-plugin.ts b/packages/angular_devkit/build_angular/src/builders/browser-esbuild/sourcemap-ignorelist-plugin.ts new file mode 100644 index 000000000000..c5fdac9c016a --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/browser-esbuild/sourcemap-ignorelist-plugin.ts @@ -0,0 +1,85 @@ +/** + * @license + * Copyright Google LLC 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 type { Plugin } from 'esbuild'; + +/** + * The field identifier for the sourcemap Chrome Devtools ignore list extension. + * + * Following the naming conventions from https://sourcemaps.info/spec.html#h.ghqpj1ytqjbm + */ +const IGNORE_LIST_ID = 'x_google_ignoreList'; + +/** + * Minimal sourcemap object required to create the ignore list. + */ +interface SourceMap { + sources: string[]; + [IGNORE_LIST_ID]?: number[]; +} + +/** + * Creates an esbuild plugin that updates generated sourcemaps to include the Chrome + * DevTools ignore list extension. All source files that originate from a node modules + * directory are added to the ignore list by this plugin. + * + * For more information, see https://developer.chrome.com/articles/x-google-ignore-list/ + * @returns An esbuild plugin. + */ +export function createSourcemapIngorelistPlugin(): Plugin { + return { + name: 'angular-sourcemap-ignorelist', + setup(build): void { + if (!build.initialOptions.sourcemap) { + return; + } + + build.onEnd((result) => { + if (!result.outputFiles) { + return; + } + + for (const file of result.outputFiles) { + // Only process sourcemap files + if (!file.path.endsWith('.map')) { + continue; + } + + const contents = Buffer.from(file.contents); + + // Avoid parsing sourcemaps that have no node modules references + if (!contents.includes('node_modules/')) { + continue; + } + + const map = JSON.parse(contents.toString('utf-8')) as SourceMap; + const ignoreList = []; + + // Check and store the index of each source originating from a node modules directory + for (let index = 0; index < map.sources.length; ++index) { + if ( + map.sources[index].startsWith('node_modules/') || + map.sources[index].includes('/node_modules/') + ) { + ignoreList.push(index); + } + } + + // Avoid regenerating the source map if nothing changed + if (ignoreList.length === 0) { + continue; + } + + // Update the sourcemap in the output file + map[IGNORE_LIST_ID] = ignoreList; + file.contents = Buffer.from(JSON.stringify(map), 'utf-8'); + } + }); + }, + }; +} diff --git a/packages/angular_devkit/build_angular/src/builders/browser-esbuild/tests/options/sourcemap_spec.ts b/packages/angular_devkit/build_angular/src/builders/browser-esbuild/tests/options/sourcemap_spec.ts index c0ced0923466..570c5091374d 100644 --- a/packages/angular_devkit/build_angular/src/builders/browser-esbuild/tests/options/sourcemap_spec.ts +++ b/packages/angular_devkit/build_angular/src/builders/browser-esbuild/tests/options/sourcemap_spec.ts @@ -123,5 +123,18 @@ describeBuilder(buildEsbuildBrowser, BROWSER_BUILDER_INFO, (harness) => { harness.expectFile('dist/main.js.map').content.toContain('/core/index.ts'); harness.expectFile('dist/main.js.map').content.toContain('/common/index.ts'); }); + + it('should add "x_google_ignoreList" extension to script sourcemap files when true', async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + sourceMap: true, + }); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + + harness.expectFile('dist/main.js.map').content.toContain('"x_google_ignoreList"'); + }); }); });