Skip to content

perf(@angular/build): reduce memory usage + improve times when i18n inlining - #33768

Open
mattlewis92 wants to merge 4 commits into
angular:mainfrom
mattlewis92:perf/i18n-inliner-translation-sharing
Open

perf(@angular/build): reduce memory usage + improve times when i18n inlining#33768
mattlewis92 wants to merge 4 commits into
angular:mainfrom
mattlewis92:perf/i18n-inliner-translation-sharing

Conversation

@mattlewis92

Copy link
Copy Markdown
Contributor

PR Checklist

Please check to confirm your PR fulfills the following requirements:

PR Type

What kind of change does this PR introduce?

  • Bugfix
  • Feature
  • Code style update (formatting, local variables)
  • Refactoring (no functional changes, no api changes)
  • Build related changes
  • CI related changes
  • Documentation content changes
  • Other... Please describe: Performance improvement

What is the current behavior?

For context, our application has 33,000 translated strings and each translation file is 2-3MB in size. Our CI builds recently started running out of memory when building localised builds due to the overhead of repeatedly transferring these large translation files when running i18n inlining during builds (we had gone up to a 96GB runner which was still running out of memory 😅)

Issue Number: N/A

What is the new behavior?

Peak memory usage has dropped by 32GB+ of memory when building the localised app in CI (I don't have the exact drop, but from a test on my local machine, inlining 400 chunks across 4 locales went from ~14GB to ~1GB), and this also made the app build significantly faster: ~10m to ~7m (~1m of this was removing a local workaround that capped the inliner pool at 4 workers, which the memory reduction made unnecessary)

Does this PR introduce a breaking change?

  • Yes
  • No

Other information

Broken out into 4 commits to make it easier to review:

  1. test(@angular/build): add unit tests for the i18n inliner - adds some more granular tests to the i18n inliner
  2. perf(@angular/build): share i18n translations with the inliner workers by reference - this contains the bulk of the perf improvement that prevents transferring the entire translation map for each file, which saves an enormous amount of memory
  3. perf(@angular/build): hash the i18n inline cache key options once per locale - when disk caching is enabled, avoid the overhead of repeatedly hashing a multi megabyte translation object each time
  4. perf(@angular/build): avoid encoding the inline source map before remapping - a low risk change that is not strictly needed and was something pointed out by AI that should also help reduce memory usage

The inliner is covered end to end by the localized builder specs and the `i18n` e2e suite, but
`I18nInliner` itself has no unit tests, so behaviour that only shows up across several inline
requests is untested. These add that layer: each locale gets its own translations when several
are inlined in sequence through one pool, a locale without translations keeps the original
messages without reporting them as missing, a locale with translations reports the ones it is
missing, a modified file's source map is remapped back to the original sources, and template
updates are inlined for both a translated and an untranslated locale.

A single thread is used so that every request of every locale is served by the same Worker, which
is what makes translation state retained between requests observable.
…s by reference

`inlineForLocale` passes the locale's translations to `workerPool.run()` once per file, so every
request structure-clones the whole set of messages into a Worker. For an application with a few
thousand localized chunks and a catalog of tens of thousands of messages, that is tens of
gigabytes of short-lived allocation and minutes of serialization on the builder's main thread.
Because each clone is several megabytes it lands in V8's large object space, where only a major
collection reclaims it, so a build with a heap ceiling sized for the machine can exhaust the
machine before the collector intervenes.

The messages are now serialized once per locale and passed as a Blob. Cloning a Blob shares its
data by reference, which is the same reason the application files are already passed that way.
Each Worker deserializes the messages once per locale and retains only the active locale, so at
most one set of messages is held per Worker.

`node:v8` serialization is used rather than JSON so that the messages arrive in the Worker
exactly as the structured clone delivered them today. `translate` only reads from the messages,
so sharing one deserialized set across the files of a locale is safe.

Measured on an application with 1,721 localized chunks across 9 locales, where the largest
catalog serializes to 6.0MB: inlining 400 chunks across 4 locales went from 23,679ms and 14,504MB
peak RSS to 915ms and 1,118MB, with byte-identical output.
… locale

Each file's persistent cache key is built from `file.hash`, the filename, and the inline options.
The options include the locale's translations, so the multi-megabyte set of messages was fed into
a fresh SHA-256 for every localized file: for an application with a few thousand localized chunks
that is tens of gigabytes of hash input per build, all of it recomputing the same value.

The options are digested once per locale instead, so each file's key is derived from a fixed
32 bytes. This changes the keys, so the first build after this change repopulates the i18n cache.
…apping

`generateMap` is `generateDecodedMap` followed by encoding the mappings to VLQ, and remapping
decodes whatever it is handed. The encoded string was therefore built only to be parsed straight
back, holding two representations of the largest structure involved in inlining a file at the
point where a build is already at its peak.

`generateDecodedMap` is used instead. The mapping resolution is unchanged: `hires`, `source` and
`includeContent` are untouched, and VLQ round-trips integers exactly, so the remapped output is
byte-identical. Verified over the output of a 10MB chunk with 488 messages, whose map carries
78,036 mapping segments across 303 sources.
@angular-robot angular-robot Bot added area: performance Issues related to performance area: @angular/build labels Aug 5, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request optimizes the i18n inlining process by serializing translation messages using V8 serialization and passing them to workers as a Blob to share them by reference instead of copying. It also switches to generating decoded source maps to reduce peak memory usage, and adds a comprehensive test suite for I18nInliner. The reviewer feedback focuses on a performance optimization to avoid stringifying the potentially large translation object when generating the cache key. Instead, the reviewer suggests computing the SHA-256 hash of the serialized V8 buffer once during serialization and reusing it.

Comment thread packages/angular/build/src/tools/esbuild/i18n-inliner.ts
Comment thread packages/angular/build/src/tools/esbuild/i18n-inliner.ts
Comment thread packages/angular/build/src/tools/esbuild/i18n-inliner.ts
Comment thread packages/angular/build/src/tools/esbuild/i18n-inliner.ts
@alan-agius4 alan-agius4 added the action: review The PR is still awaiting reviews from at least one requested reviewer label Aug 5, 2026
@alan-agius4
alan-agius4 requested a review from clydin August 5, 2026 13:21
@clydin

clydin commented Aug 5, 2026

Copy link
Copy Markdown
Member

This has been on the backlog so thank you for taking a look.
LGTM.
Thank you for the contribution.

@clydin

clydin commented Aug 5, 2026

Copy link
Copy Markdown
Member

Looks like there is one lint failure. Once that is addressed, this should be good to merge.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

action: review The PR is still awaiting reviews from at least one requested reviewer area: @angular/build area: performance Issues related to performance

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants