v0.11.0: builder parity — close all parity:important issues - #189
Merged
Conversation
…#166) `#165` stopped the build from breaking on the `; as alias` syntax by stripping the alias clause at codegen time. The alias itself was still unbound: references like `{{ alias.name }}` compiled to `ctx.alias` on the parent component, which is `undefined` for any component that does not happen to carry a field of that name. This change makes the alias actually flow through Angular's `ɵɵconditional` contract: - The truthy expression value is passed as `ɵɵconditional`'s second argument, so the matching template's embedded view receives it as its `_ctx` parameter. - The matching branch's child template function uses the alias name as its `_ctx` parameter, so body references like `{{ it.name }}` resolve to the local directly. - `@else if (expr; as <alias>)` works the same way, branch-for-branch — the alias-value chain mirrors the slot-selection ternary so each branch's truthy value lights up only when that branch matches. - Nested templates inside an aliased `@if` (e.g. a `@switch` case that reads `s.error`) bind the alias via a single `ɵɵnextContext()` walk back to the aliased ancestor's embedded view. Both the update prelude and listener closures emit this binding. - Pipes inside an `@if` condition (`state$ | async; as s`) now route through `compile_binding_expr` so `ɵɵpipe(...)` registers at the correct slot scope and the resulting `ɵɵpipeBind1(...)` form is reused as both `ɵɵconditional` arguments. Previously a piped condition compiled to a bitwise-OR expression. Codegen-shape regression tests pin the emission for the four shapes the fixture exercises (basic alias, no-alias parity, `@else if` per-branch alias, nested-scope alias). A new integration test under `crates/template-compiler/tests/` walks `compile_component` end-to-end for the same shapes.
feat(template-compiler): bind `@if (expr; as alias)` value at runtime
Adds support for `externalDependencies` in `angular.json` so projects loading specific npm packages from a CDN (or expecting them as `import` map entries) can exclude those packages from the bundle — matching `@angular/build:application`. - project-resolver: parse `externalDependencies` (base + per-config override) into `ResolvedAngularProject.external_dependencies`. - npm-resolver: new `resolve_npm_dependencies_with_externals` skips externalised specifiers in phase 1 and the transitive BFS, so a package and all its modules stay out of `npm_resolution`. - bundler: `BundleInput.external_specifiers` vetoes `is_local` — an import whose specifier matches an external entry (exact or `<name>/...` subpath) is emitted verbatim and never rewritten to a `__ns_*` namespace reference. Lazy-chunk routing keeps externals as bare specifiers instead of folding them through `./main.js`. - cli: thread externals through every npm-resolver call site; strip externals from `bundled_specifiers` defensively before bundling. - builder: drop the stale "currently ignored" warning. Verified against `test-ng-project`: declaring `externalDependencies: ["jquery"]` and importing `$ from 'jquery'` (and the subpath `jquery/dist/jquery.slim`) emits the imports verbatim in `main.js`, jquery is not installed in the fixture so unresolved externals are silently skipped by the resolver as intended. Bumps workspace to 0.10.10.
feat(bundler): honor angular.json `externalDependencies` (#146)
Switch `--localize` from a bool flag to an optional comma-separated list so CI builds can emit just the locales they need. ngc-rs build --localize # all i18n.locales (unchanged) ngc-rs build --localize=en-US,de # only those two subdirs The architect builder now serializes `localize: ['en', 'de']` as `--localize=en,de` instead of dropping the array and warning. An empty array still falls back to "all locales" to match `@angular/build`. `fan_out_locales` validates each subset entry against the source locale and `i18n.locales` keys; an unknown locale fails the build with a clear error rather than silently producing an empty `dist/`.
feat(i18n): honor `localize: [...]` per-locale subset
…lhost) (#144) Add the `allowedHosts` knob the `@angular/build:dev-server` builder exposes so projects fronted by a tunneling proxy (ngrok, Cloudflare Tunnel, GitHub Codespaces) or running under a non-default local hostname (`*.localhost`, `app.local`) don't get rejected by the dev server's `Host:` header check. * `packages/builder/schemas/dev-server.json`: add `allowedHosts: array`. * `packages/builder/src/serve/options.ts`: forward the list as `--allowed-hosts host1,host2`, normalizing empty/whitespace entries and case-insensitively deduping. * `crates/cli`: new `--allowed-hosts` flag on `ngc-rs serve` (value delimiter `,`) wired through `serve_cmd::run` to the dev server. * `crates/dev-server`: new `AllowedHosts` resolver + filter in `handle_request`. Loopback hosts (`localhost`, `127.0.0.1`, `[::1]`) are always allowed. The literal `"all"` disables the check entirely; `"auto"` (or an empty list, the default) additionally accepts the bound host. Anything else is an exact, case-insensitive hostname match with the `Host:`-header port stripped before comparison. Mismatches respond with a 403 whose body names the offending host and points at both the angular.json option and the CLI flag. Bumps workspace version to 0.10.12.
feat(dev-server): support `allowedHosts` (Codespaces / ngrok / *.localhost) (#144)
Generalize cross-chunk used-names collection so every chunk gets its own externally-used set, not just main. Vendor chunks holding `@angular/core` or `rxjs` previously pinned every export the package declared because `externally_used = None` made the shaker fall back to entry-walk reachability — on `index.mjs`-style packages that reaches almost everything. - `shake::collect_cross_chunk_used_names_per_provider` returns `Vec<HashSet<String>>` indexed by chunk index; for each chunk i it collects the names other chunks import from any module in i. - `bundle()` builds a bare-specifier → canonical-path map from the existing namespace tables so bare imports (`'@angular/core'`) attribute to the owning vendor chunk, then feeds `externally_used_per_chunk[idx]` into `analyze_unused_exports` for every chunk — the `is_main` gate is dropped. - `npm_wrap::wrap_npm_module` now accepts `unused_exports` and drops both the unused `export const X = ...` declarations and the matching `__exports.X = ns.X` re-export bridges, so shake decisions reach the emitted vendor chunk code. Bumps version to 0.10.13.
feat(bundler): per-provider tree-shake for vendor chunks
Previously a localized build with serviceWorker enabled skipped ngsw.json generation entirely, forcing apps to choose between i18n and PWA caching. Move the service-worker step to run after locale fan-out and, when --localize is in use, emit one ngsw.json per <out_dir>/<locale>/ deploy root. Asset hashes are computed from each locale's own tree, so a translated bundle hashes differently per locale — correct cache invalidation. The non-localized path is unchanged. Bump version to 0.10.14.
feat(ngsw): per-locale ngsw.json when --localize is set (#148)
The dev-server `headers` option in angular.json was silently dropped, so apps relying on production-like security headers in dev (CSP, COOP) or testing CORS scenarios had no way to configure them. - `dev-server.json`: add `headers` (object of name to string value). - `serve/options.ts`: serialize the map into a `--headers` JSON arg, trimming names and dropping empty-name / non-string entries. - `serve_cmd.rs` / `main.rs`: parse the `--headers` JSON object and thread the name/value pairs into `DevServerConfig`. - `dev-server`: new `CustomHeaders` type applies the configured headers to every served response — static assets, the SPA-fallback index.html, and the SSE live-reload stream. Headers the server sets itself (`Content-Type`, `Cache-Control`, and the SSE-specific `Connection` / `Access-Control-Allow-Origin`) are never overridden. Headers are applied only in the Rust dev server, so proxy-forwarded responses keep their upstream headers untouched. Bump version to 0.10.15.
feat(dev-server): support `headers` custom HTTP response headers (#143)
The dev-server builder hard-failed whenever ssl/sslKey/sslCert was set, so projects needing HTTPS in dev (OAuth callbacks, secure-cookie testing, mixed-content debugging, service-worker registration) had to stand up a separate TLS-terminating proxy. ngc-rs now serves HTTPS directly. - dev-server: new TlsConfig (from_pem for explicit material, self_signed via rcgen for the auto-generated case, covering the bind host plus the loopback names). DevServerConfig::with_tls wires the PEM into tiny_http's ssl-rustls backend; the SSE live-reload stream rides the same TLS connection. DevServer::scheme() reports https/http. The private key is redacted from Debug output. - cli: --ssl/--ssl-key/--ssl-cert on serve. resolve_tls enforces both-or- neither cert paths and that key/cert require --ssl; the printed URL uses the right scheme. - builder: options.ts forwards the flags, resolves cert paths against the workspace root, emits an https:// URL, and rejects ssl + proxyConfig (the proxy is the browser-facing endpoint; Node-side TLS termination is out of scope). Schema descriptions updated; README serve section corrected. ssl: true without explicit key/cert mints a throwaway self-signed certificate, matching @angular/build:dev-server; browsers show the usual untrusted-cert warning. Bump version to 0.10.16.
feat(dev-server): support `ssl`, `sslKey`, `sslCert` for HTTPS dev (#142)
First slice of Hot Module Replacement: read the `hmr` flag and hot-swap
global stylesheets in place without a full page reload.
- project-resolver: parse `architect.serve.options.hmr` (base + active
serve configuration override) into `ResolvedAngularProject.hmr`;
defaults to false.
- cli: add `--hmr` / `--no-hmr` to `serve` (CLI wins over angular.json,
else inherit, else false). When HMR is on and a rebuild touches only
global stylesheet entries, emit a CSS-update instead of a reload.
- dev-server: new `DevServerEvent::CssUpdate { timestamp }` →
`event: css-update` SSE frame; the injected client swaps the
`styles.css` <link> with a cache-busted href, preserving page state.
Component template/style HMR and the `/@ng/component` update endpoint
land in follow-up slices on the milestone branch.
Runtime scaffolding for component-level HMR, ahead of the compiler
codegen that populates it.
- dev-server: new `DevServerEvent::ComponentUpdate { id, timestamp }` →
`event: angular:component-update` SSE frame; a shared `ComponentUpdates`
registry (id → update-module source) on `DevServerConfig`, served at
`GET /@ng/component?c=<id>` (text/javascript, empty 200 for unknown ids,
400 when `c` is absent) — mirrors @angular/build's component middleware.
- client: the injected script publishes a `window.__ngcHmr` bus and
dispatches `angular:component-update` to registered handlers; new
`HMR_RUNTIME_PRELUDE` binds `import.meta.hot` to that bus.
- serve: share the registry with the dev server and prepend the runtime
prelude to `main.js` on each HMR build so per-component initializers can
resolve `import.meta.hot`.
The registry stays empty and component edits still trigger a full reload
until the template-compiler emits update modules (next slice).
Lights up Angular-parity template/style HMR end to end, building on the
config plumbing, dev-server endpoint, and runtime bus from the previous
slices.
template-compiler:
- New `hmr` module: `encode_uri_component` (JS semantics), per-component
id `encodeURIComponent("relpath@Class")`, the initializer IIFE, and the
update-module codegen. The update module reuses the existing Ivy def
verbatim via the linker's var-alias technique (`var XX = i0.XX`), with
`@angular/core` arriving as the `namespaces` arg and template deps as
positional params; it reassigns only `ɵcmp` (template/style-only path).
- `CompileOptions.hmr` and `CompiledFile.hmr` carry the artifacts;
`rewrite_source` adds `import * as i0` so the appended initializer can
reach `ɵɵreplaceMetadata`.
cli:
- Thread `hmr` through `run_build_with_options`; aggregate per-component
update modules and resource→component maps into `BuildResult`; cache the
artifacts in the incremental `CachedModule`.
- serve: classify each rebuild — global stylesheet → css-update, external
component template/style → component-update(s), `.ts`/inline/unknown →
full reload — and inject the `import.meta.hot` prelude into every chunk
(eager `main.js` and lazy `chunk-*.js`).
Verified against an Angular 21 app: editing a component `.html`/`.scss`
swaps it in place via the `/@ng/component` update module and
`ɵɵreplaceMetadata`; global styles swap the `<link>`; `.ts` reloads.
feat(dev-server): hmr config + global CSS hot-swap (#145)
feat(dev-server): /@ng/component endpoint + HMR runtime bus (#145)
feat(hmr): component template & style hot module replacement (#145)
The Rust side of HMR landed in PRs #185-#187, but the Architect builder still rejected `hmr` at schema validation (additionalProperties: false) and never forwarded a flag, so `ng serve` users could not reach it. - schemas/dev-server.json: declare `hmr` (boolean, no default) - serve/options.ts: tri-state forwarding — true → --hmr, false → --no-hmr, unset → nothing (binary inherits architect.serve.options.hmr from angular.json)
feat(builder): accept and forward the dev-server hmr option (#145)
…d template listeners Tree-shaking (issue #171): barrel-export npm packages are now shaken to the used exports only, matching @angular/build. Two gaps are fixed in the chunk shake: - Bare package specifiers (`import { debounce } from 'lodash-es'`) now resolve through node_modules, so reachability can follow the barrel re-export edge to the one used leaf instead of pinning the whole package. - The owning package's `sideEffects` field is honoured. A file in a `sideEffects: false` package is treated as free of module-level effects and may be dropped when unreached, even with top-level statements. Without this, lodash-es's `lodash.default.js` (hundreds of top-level `_.x = ...` lines) was pinned as side-effectful and dragged the entire package into the chunk. The shake analysis was reworked from "imported-by-anyone" to a reachability fixpoint over (module, export-name) pairs, with whole-module dead-code elimination for unreachable npm modules. Result on the test app's vendor-treeshake route: the lodash chunk drops from 133 KB to 8.3 KB (@angular/build reference: 6.7 KB), carrying only debounce and its transitive deps. Template listeners: the compiler now matches @angular/build for two constructs that previously produced runtime errors: - `$any()` casts in listener expressions are stripped at compile time instead of emitting a `ctx.$any(...)` call that throws `TypeError`. - Template reference variables read inside a root-level listener (`<input #box (input)="f(box.value)">`) resolve via `restoreView` + `reference()` instead of throwing `ReferenceError`. Adds a `read_side_effects` reader in npm-resolver, an end-to-end barrel-treeshake integration test mirroring the lodash-es shape (bare specifier + `sideEffects: false` aggregator), and unit coverage for the new paths. Bumps workspace version to 0.10.19.
Owner
Author
|
Parity batch: tree-shaking + template listeners (
Version → 0.10.19. Tests: bundler 87 + integration, npm-resolver 63, template-compiler 266 — all green. |
Apply rustfmt across the workspace to satisfy `cargo fmt --all -- --check`. Reflows the new tree-shake/side-effects code plus pre-existing formatting drift on the milestone branch (serve_cmd, hmr, if_alias tests) that was failing CI. No behavioural changes.
Replace a nested `match ... { Some(d) => ..., None => return None }` with the
`?` operator. Pre-existing lint that only surfaced now that `cargo fmt --check`
no longer fails the CI lint job first.
Finalize the v0.11.0 builder-parity milestone. This release closes the parity:important set: dev-server option parity (SSL, headers, allowedHosts, HMR), bundler parity (externalDependencies, barrel-package vendor tree-shaking), i18n parity (localize subsets, per-locale ngsw.json), `@if (expr; as alias)` runtime binding, and template-listener fixes (`$any()` strip, template refs).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes out all nine
parity:importantissues: full dev-server option parity (SSL, custom headers,allowedHosts, HMR), bundler parity (externalDependencies, per-provider vendor tree-shake), i18n parity (localizesubsets, per-localengsw.json), and the@if (expr; as alias)runtime alias binding. Version moves to 0.10.18.Issues closed
Closes #142
Closes #143
Closes #144
Closes #145
Closes #146
Closes #147
Closes #148
Closes #166
Closes #171
Included PRs
@if (expr; as alias)value at runtimeexternalDependencieslocalize: [...]per-locale subsetallowedHosts(Codespaces / ngrok /*.localhost)ngsw.jsonmanifests under--localizeheaderscustom HTTP response headersssl/sslKey/sslCertHTTPS dev/@ng/componentendpoint + HMR runtime bushmroptionVerification
Each feature was verified against test-ng-project through the Architect builder (
ng build/ng servewith@ngc-rs/builder), most recently the full HMR path:hmr: truepasses schema validation, CSS edits hot-swap viacss-updateSSE events, template edits re-render viaangular:component-update, andng serve --no-hmroverrides angular.json in both directions.