voidzero-dev / voidzero-dev/oxc-angular-compiler
fix(defer): emit lazy dependencies for components used only inside @defer blocks
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 228
- Forks
- 20
- Avg merge
- 1d 15h
- Merged PRs (30d)
- 36
Description
Summary
Angular's @defer block is supposed to lazy-load any standalone component that appears only inside it — the user gets import().then(m => m.X) instead of an eager import at the top of the file. OXC currently emits these references through the regular dependencies factory, so @defer parses correctly but does not actually defer: the deferred component lands in the initial bundle and the lazy boundary is lost.
This is the default ngc behavior and is not opt-in via @Component.deferredImports — the recent bb2735b commit added the explicit-deferredImports path but the implicit detection is still missing.
Reference behavior
ngc at packages/compiler-cli/src/ngtsc/annotations/component/src/handler.ts:2344-2475 (collectDeferredSymbols / surrounding logic):
- Walks the component's template AST and collects every standalone import (
scope.directives,scope.pipes). - For each import, checks whether all references appear inside
DeferredBlocknodes — if so, the import is added toallDeferredDecls. - Routes those imports through
DeferBlockDepsEmitMode.PerComponent(orPerBlockfor fine-grained mode), producing lazyimport().then(...)resolvers instead of regular dependency entries. - The normal
dependenciesfactory is built from the non-deferred imports only.
Test that demonstrates the gap (in napi/angular-compiler/test/analog-compat/component.spec.ts:3262):
import { Component } from '@angular/core';
import { LazyCmp } from './lazy';
@Component({
selector: 'app-parent',
imports: [LazyCmp],
template: '@defer { <app-lazy/> }',
})
export class Parent {}
Expected output (matching ngc):
ɵcmp = ɵɵdefineComponent({
...,
dependencies: ɵɵgetComponentDepsFactory(Parent, []),
deferBlockDependencies: [() => import('./lazy').then(m => m.LazyCmp)],
});
Current OXC output:
ɵcmp = ɵɵdefineComponent({
...,
dependencies: ɵɵgetComponentDepsFactory(Parent, [LazyCmp]), // eager
});
Required work
- Defer-only detection — extend the existing template walk in
crates/oxc_angular_compiler/src/component/defer_resolver.rsto flag eachimports: [...]entry that is referenced only insideDeferredBlockAST nodes. - Route to lazy emit — currently
defer_resolver.rsonly handles@Component.deferredImports. Generalize it to also accept implicitly-deferred imports from step 1. - Emit
deferBlockDependencies— wire the lazyimport().then(m => m.X)resolver into thedefineComponentemit. Use original export name (not local alias) — seenapi/angular-compiler/test/analog-compat/component.spec.ts:3284for the aliased-import case. - Default-import shape —
m.defaultrather thanm.Xfor default-imported components — covered bynapi/angular-compiler/test/analog-compat/component.spec.ts:3312.
Tests
The four currently-failing tests under @defer dependency import shape in component.spec.ts (3262, 3284, 3312, plus the @defer inside @switch/@case cases at 1764-area) cover the matrix.
Reference
- ngc impl:
packages/compiler-cli/src/ngtsc/annotations/component/src/handler.ts:1312(DeferBlockDepsEmitMode.PerComponent),:2344-2475(allDeferredDeclscollection) - DefersBlock AST:
packages/compiler/src/render3/r3_ast.ts - OXC defer entry:
crates/oxc_angular_compiler/src/component/defer_resolver.rs(added in #307) - Related upstream commit:
bb2735b feat(defer): emit lazy resolver and async metadata for @Component.deferredImports (#307)— explicit-deferredImports path; this issue covers the implicit case
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in crates/oxc_angular_compiler/src/component/defer_resolver.rs and compare its existing explicit-deferredImports handling with ngc's collectDeferredSymbols logic in handler.ts:2344-2475. Run the @defer dependency import shape cases in napi/angular-compiler/test/analog-compat/component.spec.ts, including the aliased, default-import, and nested @switch cases. Done means deferred-only imports produce deferBlockDependencies with the correct lazy export shape while regular dependencies exclude them.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- angular, rust, typescript
- Domain
- compilers, frontend, testing-qa
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 56/100