Skip to content

fix(defer): lazy-load an imports entry written as forwardRef(() => X) - #640

Merged
Brooooooklyn merged 3 commits into
voidzero-dev:mainfrom
edbzn:fix/defer-lazy-forwardref
Oct 27, 2026
Merged

Brooooooklyn merged 3 commits into
voidzero-dev:mainfrom
edbzn:fix/defer-lazy-forwardref

Conversation

@edbzn

@edbzn edbzn commented Oct 25, 2026

Copy link
Copy Markdown
Contributor

Fixes #632.

Problem

A standalone component listed as imports: [forwardRef(() => CmpA)] and used only inside @defer stayed eager: the implicit-deferral gate from #313 required every imports entry to be a bare identifier, and the identifier list it matched against skipped forwardRef(...) entries. The static import { CmpA } stayed and ɵɵdefer(1, 0) got no dependency function. ngtsc resolves the entry through forwardRefResolver (tryUnwrapForwardRef in packages/compiler-cli/src/ngtsc/annotations/common/src/util.ts) before resolveAllDeferredDependencies matches selectors.

Fix

  • decorator.rs: record the classes written as forwardRef(() => X) in the imports array (ComponentMetadata::forward_ref_imports), using the existing try_unwrap_forward_ref, so only @angular/core's forwardRef counts. imports itself is unchanged (partial/Direct emit still reads it).
  • transform.rs: import_entry_class maps each raw imports entry to its class (X or a resolved forwardRef(() => X), arrow or return body). The gate, the candidate list and the removal from the eager ɵɵgetComponentDepsFactory array all use it.

Output now matches ngc: ɵɵdefer(1, 0, () => [import("./lazy").then(m => m.LazyCmp)]), ɵɵgetComponentDepsFactory(Parent, []), and no static import.

Selectorless deps (selectorless_spec.ts: "should defer selectorless symbols") are out of scope: OXC rejects selectorless components outright (pipeline/ingest.rs), so there is nothing to defer yet.

Checks

  • New in tests/defer_implicit_imports_test.rs: forward_ref_import_used_only_inside_defer_is_lazy_loaded (port of defer_spec.ts "should lazy-load dependency referenced with a fowrardRef", arrow and block-body forms) and forward_ref_not_from_angular_core_stays_eager.
  • cargo test -p oxc_angular_compiler --all-features: 3518 passed, 0 failed.
  • cargo fmt; cargo clippy -p oxc_angular_compiler --all-features adds no warnings on the changed lines.

🤖 Generated with Claude Code

Resolve `@angular/core`'s `forwardRef` in `imports` before the implicit
`@defer` deferral, like ngtsc's `forwardRefResolver`, so a component used
only inside `@defer` is loaded with a dynamic `import()`.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Brooooooklyn and others added 2 commits October 27, 2026 15:56
# Conflicts:
#	crates/oxc_angular_compiler/src/component/decorator.rs
#	crates/oxc_angular_compiler/src/directive/mod.rs
The converter turns a `function` expression into raw source, so
`forwardRef(function () { return X; })` never matched and stayed eager,
while ngtsc lazy-loads it. Record the class of each `imports` element
from the decorator AST, where `try_unwrap_forward_ref` reads every form,
and match the converted entries by position. Other entries keep the old
rule: a plain name in the output stays eager (`X as any`, like ngtsc)
without blocking the rest.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Brooooooklyn

Copy link
Copy Markdown
Member

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ⚠️ Failed 2026-10-27T08:00:51.450162Z
We couldn’t complete this review. Comment @codex review to try again.
d5d52ee Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Something went wrong. Try again later by commenting “@codex review”.

An unknown error occurred
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@Brooooooklyn

Copy link
Copy Markdown
Member

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Something went wrong. Try again later by commenting “@codex review”.

An unknown error occurred
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@Brooooooklyn
Brooooooklyn merged commit 873b6f1 into voidzero-dev:main Oct 27, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

@defer doesn't lazy-load an imports entry written as forwardRef(() => X)

2 participants