Skip to content

Commit b0d04f7

Browse files
clydinangular-robot[bot]
authored andcommitted
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/
1 parent 0c9d137 commit b0d04f7

File tree

4 files changed

+102
-0
lines changed

4 files changed

+102
-0
lines changed

packages/angular_devkit/build_angular/src/builders/browser-esbuild/global-scripts.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import MagicString, { Bundle } from 'magic-string';
1111
import assert from 'node:assert';
1212
import { readFile } from 'node:fs/promises';
1313
import { NormalizedBrowserOptions } from './options';
14+
import { createSourcemapIngorelistPlugin } from './sourcemap-ignorelist-plugin';
1415

1516
/**
1617
* Create an esbuild 'build' options object for all global scripts defined in the user provied
@@ -53,6 +54,7 @@ export function createGlobalScriptsBundleOptions(options: NormalizedBrowserOptio
5354
platform: 'neutral',
5455
preserveSymlinks,
5556
plugins: [
57+
createSourcemapIngorelistPlugin(),
5658
{
5759
name: 'angular-global-scripts',
5860
setup(build) {

packages/angular_devkit/build_angular/src/builders/browser-esbuild/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import { LoadResultCache } from './load-result-cache';
3131
import { BrowserEsbuildOptions, NormalizedBrowserOptions, normalizeOptions } from './options';
3232
import { shutdownSassWorkerPool } from './sass-plugin';
3333
import { Schema as BrowserBuilderOptions } from './schema';
34+
import { createSourcemapIngorelistPlugin } from './sourcemap-ignorelist-plugin';
3435
import { createStylesheetBundleOptions } from './stylesheets';
3536
import type { ChangedFiles } from './watcher';
3637

@@ -369,6 +370,7 @@ function createCodeBundleOptions(
369370
platform: 'browser',
370371
preserveSymlinks,
371372
plugins: [
373+
createSourcemapIngorelistPlugin(),
372374
createCompilerPlugin(
373375
// JS/TS options
374376
{
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
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 type { Plugin } from 'esbuild';
10+
11+
/**
12+
* The field identifier for the sourcemap Chrome Devtools ignore list extension.
13+
*
14+
* Following the naming conventions from https://sourcemaps.info/spec.html#h.ghqpj1ytqjbm
15+
*/
16+
const IGNORE_LIST_ID = 'x_google_ignoreList';
17+
18+
/**
19+
* Minimal sourcemap object required to create the ignore list.
20+
*/
21+
interface SourceMap {
22+
sources: string[];
23+
[IGNORE_LIST_ID]?: number[];
24+
}
25+
26+
/**
27+
* Creates an esbuild plugin that updates generated sourcemaps to include the Chrome
28+
* DevTools ignore list extension. All source files that originate from a node modules
29+
* directory are added to the ignore list by this plugin.
30+
*
31+
* For more information, see https://developer.chrome.com/articles/x-google-ignore-list/
32+
* @returns An esbuild plugin.
33+
*/
34+
export function createSourcemapIngorelistPlugin(): Plugin {
35+
return {
36+
name: 'angular-sourcemap-ignorelist',
37+
setup(build): void {
38+
if (!build.initialOptions.sourcemap) {
39+
return;
40+
}
41+
42+
build.onEnd((result) => {
43+
if (!result.outputFiles) {
44+
return;
45+
}
46+
47+
for (const file of result.outputFiles) {
48+
// Only process sourcemap files
49+
if (!file.path.endsWith('.map')) {
50+
continue;
51+
}
52+
53+
const contents = Buffer.from(file.contents);
54+
55+
// Avoid parsing sourcemaps that have no node modules references
56+
if (!contents.includes('node_modules/')) {
57+
continue;
58+
}
59+
60+
const map = JSON.parse(contents.toString('utf-8')) as SourceMap;
61+
const ignoreList = [];
62+
63+
// Check and store the index of each source originating from a node modules directory
64+
for (let index = 0; index < map.sources.length; ++index) {
65+
if (
66+
map.sources[index].startsWith('node_modules/') ||
67+
map.sources[index].includes('/node_modules/')
68+
) {
69+
ignoreList.push(index);
70+
}
71+
}
72+
73+
// Avoid regenerating the source map if nothing changed
74+
if (ignoreList.length === 0) {
75+
continue;
76+
}
77+
78+
// Update the sourcemap in the output file
79+
map[IGNORE_LIST_ID] = ignoreList;
80+
file.contents = Buffer.from(JSON.stringify(map), 'utf-8');
81+
}
82+
});
83+
},
84+
};
85+
}

packages/angular_devkit/build_angular/src/builders/browser-esbuild/tests/options/sourcemap_spec.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,5 +123,18 @@ describeBuilder(buildEsbuildBrowser, BROWSER_BUILDER_INFO, (harness) => {
123123
harness.expectFile('dist/main.js.map').content.toContain('/core/index.ts');
124124
harness.expectFile('dist/main.js.map').content.toContain('/common/index.ts');
125125
});
126+
127+
it('should add "x_google_ignoreList" extension to script sourcemap files when true', async () => {
128+
harness.useTarget('build', {
129+
...BASE_OPTIONS,
130+
sourceMap: true,
131+
});
132+
133+
const { result } = await harness.executeOnce();
134+
135+
expect(result?.success).toBe(true);
136+
137+
harness.expectFile('dist/main.js.map').content.toContain('"x_google_ignoreList"');
138+
});
126139
});
127140
});

0 commit comments

Comments
 (0)