Skip to content

feat(@angular/build): add library builder - #34106

Open
alan-agius4 wants to merge 3 commits into
angular:mainfrom
alan-agius4:feat/library-builder
Open

alan-agius4 wants to merge 3 commits into
angular:mainfrom
alan-agius4:feat/library-builder

Conversation

@alan-agius4

@alan-agius4 alan-agius4 commented Sep 17, 2026

Copy link
Copy Markdown
Collaborator

PR Checklist

Please check to confirm your PR fulfills the following requirements:

PR Type

  • Feature

What is the current behavior?

Currently, there is no native @angular/build:library builder in @angular/build.

Issue Number: N/A

What is the new behavior?

Add a new native @angular/build:library builder providing a modern, high-performance compilation and packaging pipeline.

Does this PR introduce a breaking change?

  • No

Other information

@angular-robot angular-robot Bot added detected: feature PR contains a feature commit area: @angular/build labels Sep 17, 2026
@alan-agius4
alan-agius4 force-pushed the feat/library-builder branch 3 times, most recently from e9c43b0 to 67110b0 Compare September 17, 2026 09:36
@alan-agius4
alan-agius4 requested a review from clydin September 17, 2026 09:38
gemini-code-assist[bot]

This comment was marked as outdated.

@angular angular deleted a comment from gemini-code-assist Bot Sep 17, 2026
@angular angular deleted a comment from gemini-code-assist Bot Sep 17, 2026
@alan-agius4

Copy link
Copy Markdown
Collaborator Author

/gemini review

@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 a new library builder (@angular/build:library) designed to compile, bundle, and package Angular libraries in compliance with the Angular Package Format (APF). It includes features such as incremental compilation, watch mode, asset copying, secondary entry point resolution, and package.json generation. The review feedback highlights a few critical areas for improvement: ensuring the package name is updated in options when package.json changes in watch mode, optimizing startup by avoiding redundant reads of package.json, and capturing and propagating stylesheet compilation warnings to the user.

Comment thread packages/angular/build/src/builders/library/builder.ts Outdated
Comment thread packages/angular/build/src/builders/library/builder.ts Outdated
Comment thread packages/angular/build/src/builders/library/pipeline/compilation.ts Outdated
Comment thread packages/angular/build/src/builders/library/pipeline/compilation.ts
@alan-agius4
alan-agius4 marked this pull request as ready for review September 17, 2026 09:56

@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 a new library builder for Angular, enabling the compilation, bundling, and packaging of libraries in accordance with the Angular Package Format (APF). The changes include the core builder logic, a dependency graph scanner for entry points, an incremental compilation pipeline, and support for secondary entry points. My review highlights performance optimizations for the dependency predicate and file change detection logic, which are critical for maintaining build speed in larger projects.

Comment thread packages/angular/build/src/builders/library/pipeline/bundler.ts
Comment thread packages/angular/build/src/builders/library/builder.ts Outdated
@alan-agius4
alan-agius4 force-pushed the feat/library-builder branch 11 times, most recently from 7239409 to 4ee7685 Compare September 17, 2026 12:39
@alan-agius4 alan-agius4 added target: minor This PR is targeted for the next minor release action: review The PR is still awaiting reviews from at least one requested reviewer labels Sep 17, 2026
@alan-agius4
alan-agius4 force-pushed the feat/library-builder branch 2 times, most recently from 5ef0ac4 to b19ab2c Compare September 17, 2026 13:28
@dherges

dherges commented Sep 19, 2026

Copy link
Copy Markdown

Hi @alan-agius4, this is great!

Now that TypeScript projects are growing in size and TypeScript itself is moving to the native Golang rewrite, driven by the need to optimize build times, I think that the approach to the new library builder is the way to go.

I actually just tested this against ngwr (228 entry points) and ran some benchmarks comparing @angular/build:library against ng-packagr. The numbers look very promising especially for incremental rebuilds in watch mode:

Scenario ng-packagr (v22.1.1) @angular/build:library Result
Clean Build 37.10s (38.36s wall) 34.41s (37.47s wall) ~2.7s faster (@angular/build)
Watch: Cold Initial Build 33.51s 32.93s ~0.6s faster (@angular/build)
Watch: Incremental TS Rebuild (button.ts) 282ms – 634ms 149ms (137ms compile) ~2x to 4.2x faster (@angular/build)
Watch: Incremental Sass Rebuild (breadcrumbs.scss) **582ms** **215ms** (119ms compile) **~2.7x faster** (@angular/build`)

You have a very good point on incremental rebuilds in watch mode. The disk-heavy I/O in ng-packagr (from the early days) is a performance killer in larger projects.

That said, optimus-ui should also be a valid reference to run benchmarks.

To address your questions:

  1. Migration Schematic:
    Yes, an automated migration schematic (ng update) will definitely be provided. It will scan existing ng-package.json files and automatically populate the entryPoints map in angular.json, so you won't need to manually configure them when upgrading.

  2. Explicit Entry Points vs. Discovery / Globs:
    The decision to use explicit entryPoints was driven by:

    • Performance and determinism: Avoiding recursive filesystem traversal/scanning on every build and watcher startup.

Fully agree. The disk-heavy I/O in ng-packagr (inherited from the early days) is a performance killer in larger projects.

I'll also agree that focusing on build times and therefore dropping auto-discovery of source files is a valid point.

  • Single source of truth: Keeping all build configuration centralized in angular.json rather than scattered across dozens of nested configuration files.

Definetely. Centralizing this layout stops configuration drift across deeply nested directories.


I think, this is an incredible and much-needed milestone for the ecosystem!

I’ve been heavily experimenting with a similar in-memory architecture for ng-packagr (aiming at convergence with the CLI and bypassing disk I/O entirely). While I have been looking into esbuild as the bundler for libraries, I still think that hiding the mechanics of the internal build and packaging pipeline is favorable. While I'd loved to see a ng-packagr v2026, I've just started digging deeper into this new library builder and would love to contribute towards the adoption of the new library build system.

Seeing your benchmarks, especially the incremental TS/SCSS rebuild times hitting <250ms compared to legacy chains, completely validates that memory-bound compilation is the way forward for large-scale source repositories.

@alan-agius4

alan-agius4 commented Sep 19, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks for the feedback, @dherges! Also, thanks for pointing out optimus-ui, I'll definitely look into running benchmarks against it as well.

Just one clarification regarding the in memory aspect: ng-packagr already does intermediate compilation and bundling in memory, so disk I/O isn't actually the bottleneck there.

The performance gains we're seeing here, especially for incremental TS and SCSS rebuilds, mainly come from tighter integration with Angular's compiler host and caching infrastructure, along with a direct async rebuild pipeline that eliminates the orchestration overhead of the reactive RxJS transform architecture and per entry point sub pipelines.

@thekhegay

Copy link
Copy Markdown
Contributor

@alan-agius4 about entryPoints: I put up the glob version as a draft #34127.

An entry point that silently doesn't get published was the part that worried me most, so I wanted something concrete to discuss rather than just an issue.

On ngwr these two lines replace the 230-line map, the output is identical to the explicit map, and the scan takes ~10ms.

It's a draft since there's nothing to merge it into yet. Happy to change approach or close it if you'd rather do it differently.

@dherges

dherges commented Sep 19, 2026

Copy link
Copy Markdown

Just one clarification regarding the in memory aspect: ng-packagr already does intermediate compilation and bundling in memory, so disk I/O isn't actually the bottleneck there.

That makes total sense. I was exploring the disk I/O of the source discovery and the .d.ts bundling as potential bottlenecks.

The performance gains we're seeing here, especially for incremental TS and SCSS rebuilds, mainly come from tighter integration with Angular's compiler host and caching infrastructure, along with a direct async rebuild pipeline that eliminates the orchestration overhead of the reactive RxJS transform architecture and per entry point sub pipelines.

So you're saying: sharing the compiler host and the stylesheet bundling across multiple entry points is the real difference. Right?

To better understand the approach: is the speedup here just removing RxJS transforms, or removing RxJS transforms plus additional benefit that come along with it?

@alan-agius4

Copy link
Copy Markdown
Collaborator Author

Thanks for putting together #34127 so quickly, @thekhegay! Having a concrete implementation to look at makes evaluating the ergonomics much easier, and the data on ngwr is definitely interesting. That said, we are not sure yet if this is the direction we want to take, as there are trade offs to consider around determinism and configuration clarity. Let us keep this PR focused on landing the initial builder implementation first, and we can continue evaluating whether pattern support is something we want to pursue.

@dherges That is definitely a large part of it, but the speedup comes from several specific architectural changes beyond just removing the RxJS transform layer:

  1. Shared Stylesheet Bundling:
    • In ng-packagr, each entry point instantiates its own StylesheetProcessor.
    • Here, a single ComponentStylesheetBundler is shared across all entry points, allowing esbuild state and Sass worker pool caches to be reused across the entire project and invalidated centrally.

  2. Public API Invalidation via Declaration Hashing:
    • We hash the emitted .d.ts bundle (lastDtsHash). Downstream entry points are only marked dirty if the public type declarations actually changed. Internal implementation changes in an upstream entry point will not trigger recompilation of dependents.

  3. Dedicated Worker Pool for Production Builds:
    • For non watch builds, entry points within each topological batch are compiled across worker threads via compilerWorkerPool.

  4. Single Pass Output Emission:
    • Rather than writing files throughout individual transform steps, all output files across all entry points (bundles, declarations, sourcemaps, manifests, assets) are written in a single batched pass at the end.

  5. Direct Async Pipeline:
    • Removing injection-js and the RxJS transform chain eliminates the scheduling and subscription overhead across dozens or hundreds of entry points.

@dherges dherges 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.

Regarding ng update

Comment on lines +7 to +18
"entryPoints": {
"type": "object",
"description": "Map of package entry points. The '.' key represents the primary entry point; other keys define secondary subpath entry points.",
"required": ["."],
"additionalProperties": {
"oneOf": [
{
"type": "string",
"description": "Path to the entry file (e.g. 'projects/my-lib/src/public-api.ts')."
},
{
"$ref": "#/definitions/entryPoint"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Example for entry points spec:

".": "public_api.ts",
"./testing": "./testing/public_api.ts"

And:

".": { /* entry file + tsconfig */ }

In ng-packagr the entry points are specified in package.json and ng-package.json.

ng update schematic should replicate ng-packagr's discoverPackages behavior, remove the custom ng-package.json, translating entry points set to the library builder options.

Documentation says that 2nd art entry points must be specified by ng-package.json so this file can safely be removed.

Primary entry point can have both package.json and ng-package.json: the latter can safely be removed. The first should be kept intact and modified , only removing ng-package's custom properties from the package.json.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

‘ng update` is out of scope for this particular PR, but it is on our radar for the near future (most of the work for it is actually already done).

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Good to know someone from the team is already on it!

@thekhegay

Copy link
Copy Markdown
Contributor

@alan-agius4 Understood on patterns, happy to leave #34127 parked until the builder itself lands.

Meanwhile I ran the builder against ng-zorro-antd (105 entry points, same layout as ngwr: one folder per entry point with public-api.ts, a style and a less condition in exports). Median of 3 alternating runs, 14 cores / 24 GB, both builders from the same @angular/build 22.1.8 built from this PR:

Scenario ng-packagr @angular/build:library
Clean build 13.27s / 21.2s CPU / 1.68 GB 10.67s / 86.5s CPU / 7.78 GB
Warm build (no change) 10.25s / 16.7s CPU / 1.57 GB 9.15s / 74.6s CPU / 8.85 GB
Watch: first build 12.29s / 2.6 GB 12.76s / 2.5 GB
Watch: template edit 88ms 80ms
Watch: edit that changes the .d.ts 1.80s 1.38s

No OOM on this one, on the default heap or the 14 GB one the repo sets. Watch memory matches ng-packagr because watch doesn't create the worker pool; the non-watch gap is 13 workers by default on a 14-core machine.

Output matches closely: same 421 files, same 185 exports keys with every style / less condition, and identical export names in 206 of the 210 emitted files. The four that differ are the two bugs I filed with reductions, #34130 (a name reached through two export * paths is dropped, which reproduces in rolldown alone) and #34131 (tslib missing from the output package.json).

Add a new native `@angular/build:library` builder providing a modern, high-performance compilation and packaging pipeline.
thePunderWoman pushed a commit to angular/angular that referenced this pull request Sep 21, 2026
Aligning documentation of Angular Package Format (APF) with angular/angular-cli#34106 and ng-packagr/ng-packagr#3360
thePunderWoman pushed a commit to angular/angular that referenced this pull request Sep 21, 2026
Aligning documentation of Angular Package Format (APF) with angular/angular-cli#34106 and ng-packagr/ng-packagr#3360

(cherry picked from commit 0006391)
thePunderWoman pushed a commit to angular/angular that referenced this pull request Sep 21, 2026
Aligning documentation of Angular Package Format (APF) with angular/angular-cli#34106 and ng-packagr/ng-packagr#3360

(cherry picked from commit 0006391)
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 detected: feature PR contains a feature commit target: minor This PR is targeted for the next minor release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants