Skip to content

Commit c51efda

Browse files
committed
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 cb4dd71 commit c51efda

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

@@ -316,33 +321,45 @@ export async function inlineCode(request: InlineCodeRequest): Promise<InlineCode
316321

317322
return {
318323
output: result.code ?? request.code,
319-
messages: result.diagnostics.messages,
324+
messages: result.diagnostics,
320325
};
321326
}
322327

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

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

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

345-
return localizeToolsModule;
360+
return [messageParts, substitutions];
361+
}
362+
}
346363
}
347364

348365
/**
@@ -464,21 +481,20 @@ async function inlineLocalize(
464481
missingTranslation: 'error' | 'warning' | 'ignore' = 'warning',
465482
) {
466483
const magicString = new MagicString(code);
467-
const { Diagnostics, translate } = await loadLocalizeTools();
468-
const diagnostics = new Diagnostics();
484+
const diagnostics: DiagnosticMessage[] = [];
469485

470486
if (metadata.diagnostics) {
471487
for (const message of metadata.diagnostics) {
472-
diagnostics.error(message);
488+
diagnostics.push({ type: 'error', message });
473489
}
474490
}
475491

476492
if (metadata.localeInsertSites.length > 0) {
477493
const localeData = await loadLocaleData(locale);
478494
if (localeData.error) {
479-
diagnostics.error(localeData.error);
495+
diagnostics.push({ type: 'error', message: localeData.error });
480496
} else if (localeData.warning) {
481-
diagnostics.warn(localeData.warning);
497+
diagnostics.push({ type: 'warning', message: localeData.warning });
482498
}
483499
let injected = false;
484500
for (const site of metadata.localeInsertSites) {
@@ -492,7 +508,7 @@ async function inlineLocalize(
492508
}
493509

494510
for (const callSite of metadata.callSites) {
495-
const [translatedParts, translatedSubstitutions] = translate(
511+
const [translatedParts, translatedSubstitutions] = translateMessage(
496512
diagnostics,
497513
translation || {},
498514
callSite.messageParts,

0 commit comments

Comments
 (0)