From 175c787bbb4416d0f97be2f97ea96cc48084aebe Mon Sep 17 00:00:00 2001 From: Charles Lyding <19598772+clydin@users.noreply.github.com> Date: Fri, 7 Aug 2026 17:23:28 -0400 Subject: [PATCH] perf(@angular/build): optimize sourcemap stripping and loading with buffer fast path Optimize sourcemap detection, loading, and stripping during JavaScript transformations by inspecting incoming raw Uint8Array/Buffer data directly before decoding into strings. Files without sourcemap comments are identified via fast Buffer.indexOf() and skip comment removal entirely. For single trailing comments, the sourcemap is parsed directly from the trailing URL slice and the raw code buffer is sliced using a zero-copy subarray view to avoid large string allocations and trailing state-machine scans. Line-start boundaries and end-of-file trailing whitespace are validated to prevent false positives with template strings, safely falling back to full string state-machine parsing when necessary. --- .../esbuild/javascript-transformer-worker.ts | 88 ++++++++++- .../angular/build/src/utils/source-map.ts | 146 ++++++++++++------ .../build/src/utils/source-map_spec.ts | 91 ++++++++++- 3 files changed, 272 insertions(+), 53 deletions(-) diff --git a/packages/angular/build/src/tools/esbuild/javascript-transformer-worker.ts b/packages/angular/build/src/tools/esbuild/javascript-transformer-worker.ts index 49ba241d99b3..caa88c9e27ce 100644 --- a/packages/angular/build/src/tools/esbuild/javascript-transformer-worker.ts +++ b/packages/angular/build/src/tools/esbuild/javascript-transformer-worker.ts @@ -11,7 +11,12 @@ import { type PluginItem, transformAsync } from '@babel/core'; import { createRequire } from 'node:module'; import Piscina from 'piscina'; import { useBabelLinker } from '../../utils/environment-options.js'; -import { loadInputSourceMap, removeSourceMappingURL } from '../../utils/source-map'; +import { + isTrailingSourceMapComment, + loadInputSourceMap, + loadInputSourceMapFromUrl, + removeSourceMappingURL, +} from '../../utils/source-map'; interface JavaScriptTransformRequest { filename: string; @@ -25,8 +30,14 @@ interface JavaScriptTransformRequest { instrumentForCoverage?: boolean; } +interface TransformOptions extends Omit { + inputSourceMap?: EncodedSourceMap; + isAlreadyStripped?: boolean; +} + const textDecoder = new TextDecoder(); const textEncoder = new TextEncoder(); +const SOURCEMAP_COMMENT_BYTES = Buffer.from('//# sourceMappingURL='); /** * The function name prefix for all Angular partial compilation functions. @@ -84,9 +95,70 @@ export default async function transformJavaScript( request: JavaScriptTransformRequest, ): Promise { const { filename, data, ...options } = request; - const textData = typeof data === 'string' ? data : textDecoder.decode(data); - const transformedData = await transformJavaScriptImpl(filename, textData, options); + const useInputSourcemap = + options.sourcemap && + (!!options.thirdPartySourcemaps || !/[\\/]node_modules[\\/]/.test(filename)); + + let textData: string; + let inputSourceMap: EncodedSourceMap | undefined; + let isAlreadyStripped = false; + + if (typeof data !== 'string') { + const dataBuffer = Buffer.isBuffer(data) + ? data + : Buffer.from(data.buffer, data.byteOffset, data.byteLength); + + const firstIndex = dataBuffer.indexOf(SOURCEMAP_COMMENT_BYTES); + if (firstIndex === -1) { + // 0 comments: fast path, no sourcemap to load or strip + textData = textDecoder.decode(data); + isAlreadyStripped = true; + } else { + const lastIndex = dataBuffer.lastIndexOf(SOURCEMAP_COMMENT_BYTES); + let prevIdx = lastIndex - 1; + while (prevIdx >= 0 && (dataBuffer[prevIdx] === 32 || dataBuffer[prevIdx] === 9)) { + prevIdx--; + } + const isLineStart = prevIdx < 0 || dataBuffer[prevIdx] === 10 || dataBuffer[prevIdx] === 13; + + if (firstIndex === lastIndex && isLineStart) { + const urlLine = dataBuffer + .subarray(lastIndex + SOURCEMAP_COMMENT_BYTES.length) + .toString('utf-8'); + + if (useInputSourcemap) { + inputSourceMap = loadInputSourceMapFromUrl(filename, urlLine); + if (inputSourceMap !== undefined) { + // Valid trailing sourcemap comment confirmed: safe to slice code buffer + textData = textDecoder.decode(dataBuffer.subarray(0, prevIdx < 0 ? 0 : prevIdx + 1)); + isAlreadyStripped = true; + } else { + // Not a valid trailing sourcemap (e.g. inside template literal): fallback to full decode + textData = textDecoder.decode(data); + } + } else if (isTrailingSourceMapComment(urlLine)) { + // Valid trailing sourcemap comment confirmed: safe to slice code buffer + textData = textDecoder.decode(dataBuffer.subarray(0, prevIdx < 0 ? 0 : prevIdx + 1)); + isAlreadyStripped = true; + } else { + // Fallback to full decode and state-machine stripping + textData = textDecoder.decode(data); + } + } else { + // Multiple comments or comment not at line start: fall back to full decode and string parser + textData = textDecoder.decode(data); + } + } + } else { + textData = data; + } + + const transformedData = await transformJavaScriptImpl(filename, textData, { + ...options, + inputSourceMap, + isAlreadyStripped, + }); // Transfer the data via `move` instead of cloning if (transformedData === textData && typeof data !== 'string') { @@ -109,7 +181,7 @@ let oxcTransformModule: typeof import('../oxc/oxc-transform.js') | undefined; async function transformJavaScriptImpl( filename: string, data: string, - options: Omit, + options: TransformOptions, ): Promise { const shouldLink = !options.skipLinker && requiresLinking(filename, data); const useInputSourcemap = @@ -194,9 +266,11 @@ async function transformJavaScriptImpl( } if (useInputSourcemap) { - const baseMap = coverageMap ?? loadInputSourceMap(filename, data); + const baseMap = coverageMap ?? options.inputSourceMap ?? loadInputSourceMap(filename, data); if (maps.length > 0 || coverageMap) { - code = removeSourceMappingURL(code); + if (!options.isAlreadyStripped) { + code = removeSourceMappingURL(code); + } const remappingChain: (DecodedSourceMap | EncodedSourceMap)[] = maps.reverse(); if (baseMap) { remappingChain.push(baseMap); @@ -213,7 +287,7 @@ async function transformJavaScriptImpl( } // Strip sourcemaps if they should not be used - return removeSourceMappingURL(code); + return options.isAlreadyStripped ? code : removeSourceMappingURL(code); } function requiresLinking(path: string, source: string): boolean { diff --git a/packages/angular/build/src/utils/source-map.ts b/packages/angular/build/src/utils/source-map.ts index 9ce5e838987b..a1212bc56e02 100644 --- a/packages/angular/build/src/utils/source-map.ts +++ b/packages/angular/build/src/utils/source-map.ts @@ -184,71 +184,101 @@ export function removeSourceMappingURL(code: string): string { } /** - * Finds, resolves, and loads the input sourcemap referenced in the code's trailing - * sourceMappingURL comment, if present. Supports inline base64 data URIs, local absolute - * file URLs, and relative/absolute filesystem paths. + * Extracts the base64 payload from an inline sourcemap data URI line and verifies + * that only trailing whitespace follows the payload. + * + * @returns The base64 payload string if valid and trailing, or `undefined` otherwise. */ -export function loadInputSourceMap(filename: string, code: string): EncodedSourceMap | undefined { - // Locate the last sourceMappingURL comment using lastIndexOf to avoid scanning - // the entire file with a regular expression (significant for large files). - const lastSourceMapIndex = code.lastIndexOf('//# sourceMappingURL='); - if (lastSourceMapIndex === -1) { +function extractTrailingBase64Payload(urlLine: string): string | undefined { + if (!urlLine.startsWith('data:application/json;')) { return undefined; } - const urlLine = code.slice(lastSourceMapIndex + 21); - - // Inline base64-encoded sourcemaps can be extremely large (up to megabytes). - // Parse them without regular expressions to avoid heavy backtracking and allocations. - if (urlLine.startsWith('data:application/json;')) { - const base64StartIndex = urlLine.indexOf('base64,'); - if (base64StartIndex === -1) { - return undefined; - } + const base64StartIndex = urlLine.indexOf('base64,'); + if (base64StartIndex === -1) { + return undefined; + } - const payloadStart = base64StartIndex + 7; - let payloadEnd = urlLine.length; - // Find the first trailing whitespace character that marks the end of the base64 payload. - for (let i = payloadStart; i < urlLine.length; i++) { - const char = urlLine[i]; - if (char === ' ' || char === '\r' || char === '\n' || char === '\t') { - payloadEnd = i; - break; - } + const payloadStart = base64StartIndex + 7; + let payloadEnd = urlLine.length; + // Find the first trailing whitespace character that marks the end of the base64 payload. + for (let i = payloadStart; i < urlLine.length; i++) { + const char = urlLine[i]; + if (char === ' ' || char === '\r' || char === '\n' || char === '\t') { + payloadEnd = i; + break; } + } - // Verify that everything after the base64 payload is trailing whitespace - // to ensure this is a valid trailing sourceMappingURL comment at the end of the file. - for (let i = payloadEnd; i < urlLine.length; i++) { - const char = urlLine[i]; - if (char !== ' ' && char !== '\r' && char !== '\n' && char !== '\t') { - return undefined; - } + // Verify that everything after the base64 payload is trailing whitespace + // to ensure this is a valid trailing sourceMappingURL comment at the end of the file. + for (let i = payloadEnd; i < urlLine.length; i++) { + const char = urlLine[i]; + if (char !== ' ' && char !== '\r' && char !== '\n' && char !== '\t') { + return undefined; } + } - try { - // Extract the base64 payload and decode it directly into binary memory. - const base64Content = urlLine.slice(payloadStart, payloadEnd); + return urlLine.slice(payloadStart, payloadEnd); +} - return JSON.parse(Buffer.from(base64Content, 'base64').toString('utf-8')) as EncodedSourceMap; - } catch { - return undefined; - } +/** + * Extracts the URL from an external sourcemap comment line and verifies + * that only trailing whitespace follows the URL. + * + * @returns The URL string if valid and trailing, or `undefined` otherwise. + */ +function extractTrailingUrl(urlLine: string): string | undefined { + if (urlLine.startsWith('data:')) { + return undefined; } - // Non-inline sourcemap comments (always small, typically < 200 characters). - const urlMatch = /^([^\r\n\s]+)/.exec(urlLine); + const urlMatch = /^([^\r\n\s'"`]+)/.exec(urlLine); if (!urlMatch) { return undefined; } - const url = urlMatch[1]; - const remaining = urlLine.slice(url.length); - // Verify there is only whitespace after the URL to the end of the file. + const remaining = urlLine.slice(urlMatch[1].length); if (!/^\s*$/.test(remaining)) { return undefined; } + return urlMatch[1]; +} + +/** + * Checks whether a `//# sourceMappingURL=` URL line snippet represents a valid trailing comment at the end of the file. + */ +export function isTrailingSourceMapComment(urlLine: string): boolean { + return ( + extractTrailingBase64Payload(urlLine) !== undefined || extractTrailingUrl(urlLine) !== undefined + ); +} + +/** + * Resolves and loads the input sourcemap referenced in a `//# sourceMappingURL=` URL line snippet. + * Supports inline base64 data URIs, local absolute file URLs, and relative/absolute filesystem paths. + */ +export function loadInputSourceMapFromUrl( + filename: string, + urlLine: string, +): EncodedSourceMap | undefined { + // Inline base64-encoded sourcemaps can be extremely large (up to megabytes). + // Parse them without regular expressions to avoid heavy backtracking and allocations. + const base64Payload = extractTrailingBase64Payload(urlLine); + if (base64Payload !== undefined) { + try { + return JSON.parse(Buffer.from(base64Payload, 'base64').toString('utf-8')) as EncodedSourceMap; + } catch { + return undefined; + } + } + + const url = extractTrailingUrl(urlLine); + if (!url) { + return undefined; + } + if (url.startsWith('file://')) { // Local absolute file URL scheme. try { @@ -269,3 +299,29 @@ export function loadInputSourceMap(filename: string, code: string): EncodedSourc return undefined; } + +/** + * Finds, resolves, and loads the input sourcemap referenced in the code's trailing + * sourceMappingURL comment, if present. Supports inline base64 data URIs, local absolute + * file URLs, and relative/absolute filesystem paths. + */ +export function loadInputSourceMap(filename: string, code: string): EncodedSourceMap | undefined { + // Locate the last sourceMappingURL comment using lastIndexOf to avoid scanning + // the entire file with a regular expression (significant for large files). + const lastSourceMapIndex = code.lastIndexOf('//# sourceMappingURL='); + if (lastSourceMapIndex === -1) { + return undefined; + } + + if (lastSourceMapIndex > 0) { + let prevIdx = lastSourceMapIndex - 1; + while (prevIdx >= 0 && (code[prevIdx] === ' ' || code[prevIdx] === '\t')) { + prevIdx--; + } + if (prevIdx >= 0 && code[prevIdx] !== '\n' && code[prevIdx] !== '\r') { + return undefined; + } + } + + return loadInputSourceMapFromUrl(filename, code.slice(lastSourceMapIndex + 21)); +} diff --git a/packages/angular/build/src/utils/source-map_spec.ts b/packages/angular/build/src/utils/source-map_spec.ts index b34d5bc57a98..8840315c019d 100644 --- a/packages/angular/build/src/utils/source-map_spec.ts +++ b/packages/angular/build/src/utils/source-map_spec.ts @@ -6,7 +6,12 @@ * found in the LICENSE file at https://angular.dev/license */ -import { removeSourceMappingURL } from './source-map'; +import { + isTrailingSourceMapComment, + loadInputSourceMap, + loadInputSourceMapFromUrl, + removeSourceMappingURL, +} from './source-map'; describe('removeSourceMappingURL', () => { it('should remove top-level sourcemap comments', () => { @@ -98,3 +103,87 @@ describe('removeSourceMappingURL', () => { expect(removeSourceMappingURL(code)).toBe('console.log("hello");\r\n\r\nconst next = 2;'); }); }); + +describe('loadInputSourceMapFromUrl', () => { + it('should decode inline base64 sourcemaps', () => { + const map = { version: 3, sources: ['foo.ts'], mappings: 'AAAA;' }; + const base64 = Buffer.from(JSON.stringify(map)).toString('base64'); + const urlLine = `data:application/json;charset=utf-8;base64,${base64}\n`; + + expect(loadInputSourceMapFromUrl('/src/foo.js', urlLine)).toEqual(map as never); + }); + + it('should return undefined for invalid base64 payloads', () => { + expect( + loadInputSourceMapFromUrl('/src/foo.js', 'data:application/json;base64,invalid!!!'), + ).toBeUndefined(); + }); + + it('should return undefined when no base64 marker is found in data URI', () => { + expect( + loadInputSourceMapFromUrl('/src/foo.js', 'data:application/json;utf8,{}'), + ).toBeUndefined(); + }); +}); + +describe('loadInputSourceMap', () => { + it('should extract and decode sourcemap from source string', () => { + const map = { version: 3, sources: ['foo.ts'], mappings: 'AAAA;' }; + const base64 = Buffer.from(JSON.stringify(map)).toString('base64'); + const code = `console.log("hello");\n//# sourceMappingURL=data:application/json;base64,${base64}\n`; + + expect(loadInputSourceMap('/src/foo.js', code)).toEqual(map as never); + }); + + it('should return undefined when no sourceMappingURL comment is present', () => { + expect(loadInputSourceMap('/src/foo.js', 'console.log("hello");')).toBeUndefined(); + }); + + it('should return undefined for comments inside template strings with code after them', () => { + const map = { version: 3, sources: ['foo.ts'], mappings: 'AAAA;' }; + const base64 = Buffer.from(JSON.stringify(map)).toString('base64'); + const code = `const str = \`\n//# sourceMappingURL=data:application/json;base64,${base64}\n\`;\nconsole.log(str);`; + + expect(loadInputSourceMap('/src/foo.js', code)).toBeUndefined(); + }); + + it('should return undefined for comments inside template strings ending with backticks', () => { + const map = { version: 3, sources: ['foo.ts'], mappings: 'AAAA;' }; + const base64 = Buffer.from(JSON.stringify(map)).toString('base64'); + const code = `const str = \`\n//# sourceMappingURL=data:application/json;base64,${base64}\n\`;`; + + expect(loadInputSourceMap('/src/foo.js', code)).toBeUndefined(); + }); + + it('should return undefined for single-line template literals', () => { + const map = { version: 3, sources: ['foo.ts'], mappings: 'AAAA;' }; + const base64 = Buffer.from(JSON.stringify(map)).toString('base64'); + const code = `const str = \`//# sourceMappingURL=data:application/json;base64,${base64}\`;`; + + expect(loadInputSourceMap('/src/foo.js', code)).toBeUndefined(); + }); +}); + +describe('isTrailingSourceMapComment', () => { + it('should return true for valid inline data URIs at end of file', () => { + const map = { version: 3, sources: ['foo.ts'], mappings: 'AAAA;' }; + const base64 = Buffer.from(JSON.stringify(map)).toString('base64'); + const urlLine = `data:application/json;charset=utf-8;base64,${base64}\n`; + + expect(isTrailingSourceMapComment(urlLine)).toBe(true); + }); + + it('should return true for external sourcemap URLs at end of file', () => { + expect(isTrailingSourceMapComment('main.js.map\n')).toBe(true); + expect(isTrailingSourceMapComment('main.js.map')).toBe(true); + }); + + it('should return false when followed by non-whitespace characters', () => { + expect(isTrailingSourceMapComment('main.js.map\n`;\nconsole.log("hi");')).toBe(false); + expect(isTrailingSourceMapComment('main.js.map`;')).toBe(false); + }); + + it('should return false for invalid data URI format', () => { + expect(isTrailingSourceMapComment('data:application/json;utf8,{}')).toBe(false); + }); +});