Skip to content

perf(@angular/build): use native sass-embedded compiler daemon - #33935

Merged
alan-agius4 merged 1 commit into
angular:mainfrom
alan-agius4:perf/sass-embedded
Aug 26, 2026
Merged

perf(@angular/build): use native sass-embedded compiler daemon#33935
alan-agius4 merged 1 commit into
angular:mainfrom
alan-agius4:perf/sass-embedded

Conversation

@alan-agius4

Copy link
Copy Markdown
Collaborator

Replaces the Piscina-based worker thread pool executing pure-JS Dart Sass
(sass compiled via dart2js) with a native Dart Sass asynchronous compiler
daemon (sass-embedded).

The build system previously relied on multi-threaded worker pools with synchronous
compilation to bypass performance overheads inherent to pure-JS asynchronous Dart
Sass. However, this approach incurred significant V8 heap and isolate duplication
across worker threads, as well as thread contention and blocking due to
Atomics.wait() synchronization loops during custom URL and module rebasing.

With sass-embedded, Sass compilation is delegated to a long-lived native Dart
AOT binary communicating asynchronously via Protocol Buffers over stdio pipes.
This eliminates duplicate V8 isolates, replaces synchronous Atomics.wait()
with event-driven asynchronous message handling, and dramatically reduces memory
pressure on the Node.js process.

The compiler infrastructure is structured around a shared SassServiceImplementation
interface with two isolated implementations dynamically loaded on demand:

  • SassAsyncCompilerImplementation: Manages the native sass-embedded daemon lifecycle
    and asynchronous URL rebasing importers (AsyncModuleUrlRebasingImporter).
  • SassWorkerImplementation: Retained as an isolated worker pool fallback.

Environment Variable & Fallbacks

  • NG_BUILD_SASS_WORKER=1 (or true): Forces the build system to fall back to
    the pure-JS SassWorkerImplementation using worker threads.
  • In restricted environments where native platform binaries cannot be spawned
    (such as WebContainers detected via process.versions.webcontainer), the build
    system automatically falls back to SassWorkerImplementation without requiring
    manual configuration.

Benchmarks: Sass Service in Isolation

10 Small Files (Angular Components)

Metric Worker Pool (main) Native Async (this change) Delta Speedup / Improvement
Cold Start 369.04 ms 229.89 ms -139.15 ms 1.61x faster (-37.7%)
Warm Median 22.46 ms 5.27 ms -17.19 ms 4.26x faster (-76.5%)
Warm Mean 25.82 ms 7.25 ms -18.57 ms 3.56x faster (-71.9%)
Warm P95 37.30 ms 14.55 ms -22.75 ms 2.56x faster (-61.0%)
Warm Throughput 445.2 files/s 1,899.3 files/s +1,454.1 files/s 4.27x throughput (+326.6%)
Incremental Median 6.54 ms 2.08 ms -4.46 ms 3.14x faster (-68.2%)
Incremental Throughput 153.0 files/s 480.5 files/s +327.5 files/s 3.14x throughput (+214.0%)
Peak Memory RSS 281.16 MB 78.73 MB -202.43 MB -72.0% memory saved

10 Large Files (Design System Tokens & Themes)

Metric Worker Pool (main) Native Async (this change) Delta Speedup / Improvement
Cold Start 823.95 ms 386.25 ms -437.70 ms 2.13x faster (-53.1%)
Warm Median 231.85 ms 175.64 ms -56.21 ms 1.32x faster (-24.2%)
Warm Mean 241.50 ms 172.22 ms -69.28 ms 1.40x faster (-28.7%)
Warm P95 293.09 ms 215.48 ms -77.61 ms 1.36x faster (-26.5%)
Warm Throughput 43.1 files/s 56.9 files/s +13.8 files/s 1.32x throughput (+32.0%)
Incremental Median 72.63 ms 35.34 ms -37.29 ms 2.06x faster (-51.3%)
Incremental Throughput 13.8 files/s 28.3 files/s +14.5 files/s 2.05x throughput (+105.4%)
Peak Memory RSS 986.47 MB 112.34 MB -874.13 MB -88.6% memory saved

Benchmarks: Real Angular CLI Application Scenarios (ng build)

Small App (5 small files)

Phase / Metric Main (NG_BUILD_SASS_WORKER=1) This Change (Native Async) Delta Improvement
Cold Wall Time 3,201.3 ms 2,950.2 ms -251.1 ms -7.8%
Cold CLI Time 2,456.0 ms 2,302.0 ms -154.0 ms -6.3%
Cold Peak RSS 1,227.3 MB 1,058.9 MB -168.4 MB -13.7%
Warm (Avg) Wall Time 2,762.0 ms 2,475.3 ms -286.7 ms -10.4%
Warm (Avg) CLI Time 2,071.3 ms 1,820.3 ms -251.0 ms -12.1%
Warm (Avg) Peak RSS 1,102.3 MB 894.8 MB -207.5 MB -18.8%

Small App (5 large files)

Phase / Metric Main (NG_BUILD_SASS_WORKER=1) This Change (Native Async) Delta Improvement
Cold Wall Time 3,605.9 ms 3,175.8 ms -430.1 ms -11.9%
Cold CLI Time 2,874.0 ms 2,482.0 ms -392.0 ms -13.6%
Cold Peak RSS 1,420.2 MB 1,072.3 MB -347.9 MB -24.5%
Warm (Avg) Wall Time 3,123.7 ms 2,682.5 ms -441.2 ms -14.1%
Warm (Avg) CLI Time 2,435.7 ms 2,028.7 ms -407.0 ms -16.7%
Warm (Avg) Peak RSS 1,278.2 MB 911.3 MB -366.9 MB -28.7%

Large App (500 files: 250 large + 250 small)

Phase / Metric Main (NG_BUILD_SASS_WORKER=1) This Change (Native Async) Delta Improvement
Cold Wall Time 15,259.7 ms 15,348.6 ms +88.9 ms +0.6% (Parity)
Cold CLI Time 14,425.0 ms 14,373.0 ms -52.0 ms -0.4% (Parity)
Cold Peak RSS 2,305.1 MB 1,582.7 MB -722.4 MB -31.3% memory saved
Warm Run 1 Wall Time 14,673.1 ms 14,277.1 ms -396.0 ms -2.7%
Warm Run 1 CLI Time 13,812.0 ms 13,375.0 ms -437.0 ms -3.2%
Warm Run 1 Peak RSS 2,180.5 MB 1,485.6 MB -694.9 MB -31.9% memory saved
Warm Run 2 Wall Time 14,771.2 ms 14,502.7 ms -268.5 ms -1.8%
Warm Run 2 CLI Time 13,943.0 ms 13,527.0 ms -416.0 ms -3.0%
Warm Run 2 Peak RSS 2,220.8 MB 1,493.3 MB -727.5 MB -32.8% memory saved
Warm (Avg) Peak RSS 2,200.7 MB 1,489.5 MB -711.2 MB -32.3% memory saved

Closes #29319

@alan-agius4 alan-agius4 added the target: minor This PR is targeted for the next minor release label Aug 26, 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 introduces support for the persistent Dart Sass embedded compiler (sass-embedded) via a new SassAsyncCompilerImplementation. It refactors the existing worker-based Sass compilation into SassWorkerImplementation and introduces a conditional check (useSassWorker) to choose between the native asynchronous compiler and the worker-based fallback. Feedback on the changes highlights potential issues with error handling during asynchronous initialization and shutdown, specifically recommending try-finally blocks to ensure failed initialization promises are cleared so subsequent attempts can retry, and a try-catch block in close() to prevent initialization failures from disrupting the shutdown sequence. Additionally, it is suggested to validate importers upfront in SassAsyncCompilerImplementation to match the fail-fast behavior of the worker implementation.

Comment thread packages/angular/build/src/tools/esbuild/stylesheets/sass-language.ts Outdated
Comment thread packages/angular/build/src/tools/sass/sass-async-compiler-implementation.ts Outdated
Replaces the Piscina-based worker thread pool executing pure-JS Dart Sass
(`sass` compiled via `dart2js`) with a native Dart Sass asynchronous compiler
daemon (`sass-embedded`).

The build system previously relied on multi-threaded worker pools with synchronous
compilation to bypass performance overheads inherent to pure-JS asynchronous Dart
Sass. However, this approach incurred significant V8 heap and isolate duplication
across worker threads, as well as thread contention and blocking due to
`Atomics.wait()` synchronization loops during custom URL and module rebasing.

With `sass-embedded`, Sass compilation is delegated to a long-lived native Dart
AOT binary communicating asynchronously via Protocol Buffers over stdio pipes.
This eliminates duplicate V8 isolates, replaces synchronous `Atomics.wait()`
with event-driven asynchronous message handling, and dramatically reduces memory
pressure on the Node.js process.

The compiler infrastructure is structured around a shared `SassServiceImplementation`
interface with two isolated implementations dynamically loaded on demand:
- `SassAsyncCompilerImplementation`: Manages the native `sass-embedded` daemon lifecycle
  and asynchronous URL rebasing importers (`AsyncModuleUrlRebasingImporter`).
- `SassWorkerImplementation`: Retained as an isolated worker pool fallback.

Environment Variable & Fallbacks:
- `NG_BUILD_SASS_WORKER=1` (or `true`): Forces the build system to fall back to
  the pure-JS `SassWorkerImplementation` using worker threads.
- In restricted environments where native platform binaries cannot be spawned
  (such as WebContainers detected via `process.versions.webcontainer`), the build
  system automatically falls back to `SassWorkerImplementation` without requiring
  manual configuration.

### Benchmarks: Sass Service in Isolation

#### 10 Small Files (Angular Components)

| Metric | Worker Pool (`main`) | Native Async (`this change`) | Delta | Speedup / Improvement |
| :--- | :---: | :---: | :---: | :---: |
| Cold Start | 369.04 ms | 229.89 ms | -139.15 ms | 1.61x faster (-37.7%) |
| Warm Median | 22.46 ms | 5.27 ms | -17.19 ms | 4.26x faster (-76.5%) |
| Warm Mean | 25.82 ms | 7.25 ms | -18.57 ms | 3.56x faster (-71.9%) |
| Warm P95 | 37.30 ms | 14.55 ms | -22.75 ms | 2.56x faster (-61.0%) |
| Warm Throughput | 445.2 files/s | 1,899.3 files/s | +1,454.1 files/s | 4.27x throughput (+326.6%) |
| Incremental Median | 6.54 ms | 2.08 ms | -4.46 ms | 3.14x faster (-68.2%) |
| Incremental Throughput | 153.0 files/s | 480.5 files/s | +327.5 files/s | 3.14x throughput (+214.0%) |
| Peak Memory RSS | 281.16 MB | 78.73 MB | -202.43 MB | -72.0% memory saved |

#### 10 Large Files (Design System Tokens & Themes)

| Metric | Worker Pool (`main`) | Native Async (`this change`) | Delta | Speedup / Improvement |
| :--- | :---: | :---: | :---: | :---: |
| Cold Start | 823.95 ms | 386.25 ms | -437.70 ms | 2.13x faster (-53.1%) |
| Warm Median | 231.85 ms | 175.64 ms | -56.21 ms | 1.32x faster (-24.2%) |
| Warm Mean | 241.50 ms | 172.22 ms | -69.28 ms | 1.40x faster (-28.7%) |
| Warm P95 | 293.09 ms | 215.48 ms | -77.61 ms | 1.36x faster (-26.5%) |
| Warm Throughput | 43.1 files/s | 56.9 files/s | +13.8 files/s | 1.32x throughput (+32.0%) |
| Incremental Median | 72.63 ms | 35.34 ms | -37.29 ms | 2.06x faster (-51.3%) |
| Incremental Throughput | 13.8 files/s | 28.3 files/s | +14.5 files/s | 2.05x throughput (+105.4%) |
| Peak Memory RSS | 986.47 MB | 112.34 MB | -874.13 MB | -88.6% memory saved |

### Benchmarks: Real Angular CLI Application Scenarios (ng build)

#### Small App (5 small files)

| Phase / Metric | Main (`NG_BUILD_SASS_WORKER=1`) | This Change (Native Async) | Delta | Improvement |
| :--- | :---: | :---: | :---: | :---: |
| Cold Wall Time | 3,201.3 ms | 2,950.2 ms | -251.1 ms | -7.8% |
| Cold CLI Time | 2,456.0 ms | 2,302.0 ms | -154.0 ms | -6.3% |
| Cold Peak RSS | 1,227.3 MB | 1,058.9 MB | -168.4 MB | -13.7% |
| Warm (Avg) Wall Time | 2,762.0 ms | 2,475.3 ms | -286.7 ms | -10.4% |
| Warm (Avg) CLI Time | 2,071.3 ms | 1,820.3 ms | -251.0 ms | -12.1% |
| Warm (Avg) Peak RSS | 1,102.3 MB | 894.8 MB | -207.5 MB | -18.8% |

#### Small App (5 large files)

| Phase / Metric | Main (`NG_BUILD_SASS_WORKER=1`) | This Change (Native Async) | Delta | Improvement |
| :--- | :---: | :---: | :---: | :---: |
| Cold Wall Time | 3,605.9 ms | 3,175.8 ms | -430.1 ms | -11.9% |
| Cold CLI Time | 2,874.0 ms | 2,482.0 ms | -392.0 ms | -13.6% |
| Cold Peak RSS | 1,420.2 MB | 1,072.3 MB | -347.9 MB | -24.5% |
| Warm (Avg) Wall Time | 3,123.7 ms | 2,682.5 ms | -441.2 ms | -14.1% |
| Warm (Avg) CLI Time | 2,435.7 ms | 2,028.7 ms | -407.0 ms | -16.7% |
| Warm (Avg) Peak RSS | 1,278.2 MB | 911.3 MB | -366.9 MB | -28.7% |

#### Large App (500 files: 250 large + 250 small)

| Phase / Metric | Main (`NG_BUILD_SASS_WORKER=1`) | This Change (Native Async) | Delta | Improvement |
| :--- | :---: | :---: | :---: | :---: |
| Cold Wall Time | 15,259.7 ms | 15,348.6 ms | +88.9 ms | +0.6% (Parity) |
| Cold CLI Time | 14,425.0 ms | 14,373.0 ms | -52.0 ms | -0.4% (Parity) |
| Cold Peak RSS | 2,305.1 MB | 1,582.7 MB | -722.4 MB | -31.3% memory saved |
| Warm Run 1 Wall Time | 14,673.1 ms | 14,277.1 ms | -396.0 ms | -2.7% |
| Warm Run 1 CLI Time | 13,812.0 ms | 13,375.0 ms | -437.0 ms | -3.2% |
| Warm Run 1 Peak RSS | 2,180.5 MB | 1,485.6 MB | -694.9 MB | -31.9% memory saved |
| Warm Run 2 Wall Time | 14,771.2 ms | 14,502.7 ms | -268.5 ms | -1.8% |
| Warm Run 2 CLI Time | 13,943.0 ms | 13,527.0 ms | -416.0 ms | -3.0% |
| Warm Run 2 Peak RSS | 2,220.8 MB | 1,493.3 MB | -727.5 MB | -32.8% memory saved |
| Warm (Avg) Peak RSS | 2,200.7 MB | 1,489.5 MB | -711.2 MB | -32.3% memory saved |

Closes angular#29319
@alan-agius4 alan-agius4 added the action: review The PR is still awaiting reviews from at least one requested reviewer label Aug 26, 2026
@alan-agius4
alan-agius4 requested a review from clydin August 26, 2026 16:18
@angular-robot angular-robot Bot added area: performance Issues related to performance area: @angular/build labels Aug 26, 2026
@alan-agius4 alan-agius4 added action: merge The PR is ready for merge by the caretaker and removed action: review The PR is still awaiting reviews from at least one requested reviewer labels Aug 26, 2026
@alan-agius4
alan-agius4 merged commit ecbcd87 into angular:main Aug 26, 2026
70 of 71 checks passed
@alan-agius4

Copy link
Copy Markdown
Collaborator Author

This PR was merged into the repository. The changes were merged into the following branches:

@alan-agius4
alan-agius4 deleted the perf/sass-embedded branch August 26, 2026 17:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

action: merge The PR is ready for merge by the caretaker area: @angular/build area: performance Issues related to performance target: minor This PR is targeted for the next minor release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support building scss with sass-embedded

2 participants