From 8f703a56fbd6fb300238a8a3a209fd8886a7737e Mon Sep 17 00:00:00 2001 From: Charles <19598772+clydin@users.noreply.github.com> Date: Tue, 29 Sep 2026 09:54:13 -0400 Subject: [PATCH 1/2] refactor(@angular/build): avoid loading localize tools and babel in i18n inliner worker Previously, `i18n-inliner-worker.ts` dynamically loaded `@angular/localize/tools` to invoke its `translate()` function and `Diagnostics` class. Importing `@angular/localize/tools` eagerly pulls in `@babel/core`, `@angular/compiler-cli`, and `@angular/compiler`, consuming ~180 ms of CPU latency and ~32 MB of heap per worker thread upon first translation. --- .../src/tools/i18n/i18n-inliner-worker.ts | 70 ++++++++++++------- 1 file changed, 43 insertions(+), 27 deletions(-) diff --git a/packages/angular/build/src/tools/i18n/i18n-inliner-worker.ts b/packages/angular/build/src/tools/i18n/i18n-inliner-worker.ts index e2bb28fe1eaf..924c7ed4dd91 100644 --- a/packages/angular/build/src/tools/i18n/i18n-inliner-worker.ts +++ b/packages/angular/build/src/tools/i18n/i18n-inliner-worker.ts @@ -7,7 +7,12 @@ */ import remapping, { type DecodedSourceMap, type SourceMapInput } from '@ampproject/remapping'; -import type { ɵParsedTranslation } from '@angular/localize'; +import { + type ɵParsedTranslation, + ɵisMissingTranslationError, + ɵmakeTemplateObject, + ɵtranslate, +} from '@angular/localize'; import { MagicString } from 'magic-string'; import { deserialize } from 'node:v8'; import { parseSync } from 'oxc-parser'; @@ -285,7 +290,7 @@ export async function inlineFileBatch( locale, code: result.code, map: result.map, - messages: result.diagnostics.messages, + messages: result.diagnostics, }); } @@ -316,33 +321,45 @@ export async function inlineCode(request: InlineCodeRequest): Promise, + messageParts: TemplateStringsArray, + substitutions: readonly number[], + missingTranslation: 'error' | 'warning' | 'ignore', +): [TemplateStringsArray, readonly number[]] { + try { + return ɵtranslate(translations, messageParts, substitutions) as [ + TemplateStringsArray, + readonly number[], + ]; + } catch (error) { + if (ɵisMissingTranslationError(error)) { + if (missingTranslation !== 'ignore') { + diagnostics.push({ type: missingTranslation, message: error.message }); + } -/** - * Attempts to load the `@angular/localize/tools` module containing the functionality to - * perform the file translations. - * This module must be dynamically loaded as it is an ESM module and this file is CommonJS. - */ -async function loadLocalizeTools(): Promise { - // Load ESM `@angular/localize/tools` using the TypeScript dynamic import workaround. - // Once TypeScript provides support for keeping the dynamic import this workaround can be - // changed to a direct dynamic import. - localizeToolsModule ??= await import('@angular/localize/tools'); + return [ + ɵmakeTemplateObject(error.parsedMessage.messageParts, error.parsedMessage.messageParts), + substitutions, + ]; + } else { + const message = error instanceof Error ? error.message : String(error); + diagnostics.push({ type: 'error', message }); - return localizeToolsModule; + return [messageParts, substitutions]; + } + } } /** @@ -464,21 +481,20 @@ async function inlineLocalize( missingTranslation: 'error' | 'warning' | 'ignore' = 'warning', ) { const magicString = new MagicString(code); - const { Diagnostics, translate } = await loadLocalizeTools(); - const diagnostics = new Diagnostics(); + const diagnostics: DiagnosticMessage[] = []; if (metadata.diagnostics) { for (const message of metadata.diagnostics) { - diagnostics.error(message); + diagnostics.push({ type: 'error', message }); } } if (metadata.localeInsertSites.length > 0) { const localeData = await loadLocaleData(locale); if (localeData.error) { - diagnostics.error(localeData.error); + diagnostics.push({ type: 'error', message: localeData.error }); } else if (localeData.warning) { - diagnostics.warn(localeData.warning); + diagnostics.push({ type: 'warning', message: localeData.warning }); } let injected = false; for (const site of metadata.localeInsertSites) { @@ -492,7 +508,7 @@ async function inlineLocalize( } for (const callSite of metadata.callSites) { - const [translatedParts, translatedSubstitutions] = translate( + const [translatedParts, translatedSubstitutions] = translateMessage( diagnostics, translation || {}, callSite.messageParts, From ab5aa6aaeced7cfd70e8fb705c52ed76a90c4636 Mon Sep 17 00:00:00 2001 From: Charles Lyding <19598772+clydin@users.noreply.github.com> Date: Mon, 28 Sep 2026 14:54:52 -0400 Subject: [PATCH 2/2] refactor(@angular/build): lazy load sourcemap remapping in i18n inliner worker Previously, `@ampproject/remapping` was statically imported at the top level of `i18n-inliner-worker.ts`, eagerly pulling in 5 packages (`@ampproject/remapping` and 4 sub-dependencies) upon worker initialization. In typical workflows or when sourcemaps are not requested for inlining, remapping is never executed. This changes the import of `@ampproject/remapping` to be dynamic so that it is only loaded when a file actually requires sourcemap remapping. --- packages/angular/build/src/tools/i18n/i18n-inliner-worker.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/angular/build/src/tools/i18n/i18n-inliner-worker.ts b/packages/angular/build/src/tools/i18n/i18n-inliner-worker.ts index 924c7ed4dd91..4286f33a1b41 100644 --- a/packages/angular/build/src/tools/i18n/i18n-inliner-worker.ts +++ b/packages/angular/build/src/tools/i18n/i18n-inliner-worker.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import remapping, { type DecodedSourceMap, type SourceMapInput } from '@ampproject/remapping'; +import { type DecodedSourceMap, type SourceMapInput } from '@ampproject/remapping'; import { type ɵParsedTranslation, ɵisMissingTranslationError, @@ -156,6 +156,8 @@ const deserializedTranslations = new Map null); }