Skip to content

Commit 4960e70

Browse files
authored
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.
1 parent 9b7a14a commit 4960e70

1 file changed

Lines changed: 43 additions & 27 deletions

File tree

‎packages/angular/build/src/tools/i18n/i18n-inliner-worker.ts‎

Lines changed: 43 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,12 @@
77
*/
88

99
import remapping, { type DecodedSourceMap, type SourceMapInput } from '@ampproject/remapping';
10-
import type { ɵParsedTranslation } from '@angular/localize';
10+
import {
11+
type ɵParsedTranslation,
12+
ɵisMissingTranslationError,
13+
ɵmakeTemplateObject,
14+
ɵtranslate,
15+
} from '@angular/localize';
1116
import { MagicString } from 'magic-string';
1217
import { deserialize } from 'node:v8';
1318
import { parseSync } from 'oxc-parser';
@@ -285,7 +290,7 @@ export async function inlineFileBatch(
285290
locale,
286291
code: result.code,
287292
map: result.map,
288-
messages: result.diagnostics.messages,
293+
messages: result.diagnostics,
289294
};
290295
}),
291296
);
@@ -317,33 +322,45 @@ export async function inlineCode(request: InlineCodeRequest): Promise<InlineCode
317322

318323
return {
319324
output: result.code ?? request.code,
320-
messages: result.diagnostics.messages,
325+
messages: result.diagnostics,
321326
};
322327
}
323328

324-
/**
325-
* A Type representing the localize tools module.
326-
*/
327-
type LocalizeUtilityModule = typeof import('@angular/localize/tools');
329+
type DiagnosticMessage = { type: 'error' | 'warning'; message: string };
328330

329331
/**
330-
* Cached instance of the `@angular/localize/tools` module.
331-
* This is used to remove the need to repeatedly import the module per file translation.
332+
* Translates a $localize message using @angular/localize low-level runtime functions.
333+
* Handles missing translations and errors without requiring @angular/localize/tools.
332334
*/
333-
let localizeToolsModule: LocalizeUtilityModule | undefined;
335+
function translateMessage(
336+
diagnostics: DiagnosticMessage[],
337+
translations: Record<string, ɵParsedTranslation>,
338+
messageParts: TemplateStringsArray,
339+
substitutions: readonly number[],
340+
missingTranslation: 'error' | 'warning' | 'ignore',
341+
): [TemplateStringsArray, readonly number[]] {
342+
try {
343+
return ɵtranslate(translations, messageParts, substitutions) as [
344+
TemplateStringsArray,
345+
readonly number[],
346+
];
347+
} catch (error) {
348+
if (ɵisMissingTranslationError(error)) {
349+
if (missingTranslation !== 'ignore') {
350+
diagnostics.push({ type: missingTranslation, message: error.message });
351+
}
334352

335-
/**
336-
* Attempts to load the `@angular/localize/tools` module containing the functionality to
337-
* perform the file translations.
338-
* This module must be dynamically loaded as it is an ESM module and this file is CommonJS.
339-
*/
340-
async function loadLocalizeTools(): Promise<LocalizeUtilityModule> {
341-
// Load ESM `@angular/localize/tools` using the TypeScript dynamic import workaround.
342-
// Once TypeScript provides support for keeping the dynamic import this workaround can be
343-
// changed to a direct dynamic import.
344-
localizeToolsModule ??= await import('@angular/localize/tools');
353+
return [
354+
ɵmakeTemplateObject(error.parsedMessage.messageParts, error.parsedMessage.messageParts),
355+
substitutions,
356+
];
357+
} else {
358+
const message = error instanceof Error ? error.message : String(error);
359+
diagnostics.push({ type: 'error', message });
345360

346-
return localizeToolsModule;
361+
return [messageParts, substitutions];
362+
}
363+
}
347364
}
348365

349366
/**
@@ -465,21 +482,20 @@ async function inlineLocalize(
465482
missingTranslation: 'error' | 'warning' | 'ignore' = 'warning',
466483
) {
467484
const magicString = new MagicString(code);
468-
const { Diagnostics, translate } = await loadLocalizeTools();
469-
const diagnostics = new Diagnostics();
485+
const diagnostics: DiagnosticMessage[] = [];
470486

471487
if (metadata.diagnostics) {
472488
for (const message of metadata.diagnostics) {
473-
diagnostics.error(message);
489+
diagnostics.push({ type: 'error', message });
474490
}
475491
}
476492

477493
if (metadata.localeInsertSites.length > 0) {
478494
const localeData = await loadLocaleData(locale);
479495
if (localeData.error) {
480-
diagnostics.error(localeData.error);
496+
diagnostics.push({ type: 'error', message: localeData.error });
481497
} else if (localeData.warning) {
482-
diagnostics.warn(localeData.warning);
498+
diagnostics.push({ type: 'warning', message: localeData.warning });
483499
}
484500
let injected = false;
485501
for (const site of metadata.localeInsertSites) {
@@ -493,7 +509,7 @@ async function inlineLocalize(
493509
}
494510

495511
for (const callSite of metadata.callSites) {
496-
const [translatedParts, translatedSubstitutions] = translate(
512+
const [translatedParts, translatedSubstitutions] = translateMessage(
497513
diagnostics,
498514
translation || {},
499515
callSite.messageParts,

0 commit comments

Comments
 (0)