Skip to content

fix(defer): emit ɵɵenableIncrementalHydrationRuntime before hydrating defer blocks - #594

Merged
Brooooooklyn merged 2 commits into
voidzero-dev:mainfrom
GabeSilvaDev:fix/incremental-hydration-runtime
Oct 21, 2026
Merged

Brooooooklyn merged 2 commits into
voidzero-dev:mainfrom
GabeSilvaDev:fix/incremental-hydration-runtime

Conversation

@GabeSilvaDev

@GabeSilvaDev GabeSilvaDev commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Summary

@defer blocks with hydrate triggers are never hydrated on the client when compiled with OXC.

Since Angular 22.0.0-next.11, the incremental hydration runtime stays inert until a compiled template calls ɵɵenableIncrementalHydrationRuntime() (packages/core/src/hydration/incremental_runtime.ts). Until then createDehydratedBlockRegistry() returns null and runIncrementalHydrationBootstrap() is a no-op, so withIncrementalHydration() alone does nothing. ngc emits the call through the insertIncrementalHydrationRuntime phase. OXC doesn't have that phase, and the e2e compare fixtures recorded the missing call as a known difference.

@defer (on idle) {a} @defer (hydrate on hover) {b}

ngc and OXC with this change:

i0.ɵɵdomTemplate(0, App_Defer_0_Template, 1, 0);
i0.ɵɵdefer(1, 0);
i0.ɵɵdeferOnIdle();
i0.ɵɵdomTemplate(3, App_Defer_3_Template, 1, 0);
i0.ɵɵenableIncrementalHydrationRuntime();
i0.ɵɵdefer(4, 3, null, null, null, null, null, null, null, 1);
i0.ɵɵdeferHydrateOnHover();
i0.ɵɵdeferOnIdle();

OXC on main has no ɵɵenableIncrementalHydrationRuntime() line.

Piece Change
phases/insert_incremental_hydration_runtime.rs Port of the phase: in each view, insert the op before the first Defer op whose flags have HasHydrateTriggers. Registered right after configureDeferInstructions, as in ngc's phase list.
ir/ops.rs, ir/enums.rs, ir/expression.rs EnableIncrementalHydrationRuntime create op.
reify, r3/identifiers.rs Emits ɵɵenableIncrementalHydrationRuntime().
AngularVersion::supports_incremental_hydration_runtime The instruction first shipped in 22.0.0-next.11 and was never backported to 21.x. I checked the published @angular/compiler builds. Older targets don't export it, so the phase is skipped for them.

Tests

  • Integration tests:
    • one call per view, and the statement right after it is the first hydrating ɵɵdefer;
    • hydrate never, hydrate when, hydrate on timer and a block inside i18n each get the call;
    • an embedded view gets its own call, in its own template function;
    • no call without hydrate triggers;
    • the version gate (21.2.0 and 22.0.0-next.10 skip the call; 22.0.0-next.11 and 22.0.0 emit it).
  • Unit test for supports_incremental_hydration_runtime: next.11, rc, 22.1.0-next.0 and 23.x emit; 21.2.25, next.0, next.10 and beta (parses to Other) don't.
  • I removed the four defer/defer-hydrate-* entries and their reason from known-differences.ts. They match ngc 22.2.2 now: 1091 fixtures, 0 mismatches.
  • cargo fmt --all -- --check, cargo test --all-features (3332 passed), and cargo run -p oxc_angular_conformance (1264/1264, no snapshot diff) all pass.

Review follow-up

Checked against real ngc 22.2.3 on 42 templates (every trigger kind, several blocks per view, nesting in @if/@for/@switch/ng-template/*ngIf/other @defer sub-blocks, i18n, templateUrl), plus partial mode, the linker, JIT and HMR: same output. The 22.0.0-next.11 cutoff matches the npm tarballs: next.10 has no ɵɵenableIncrementalHydrationRuntime, and no 20.x or 21.x release got it.

Polish in the second commit:

  • The op, its OpKind and the identifier sit with the other defer items.
  • The op takes the defer block's source span, as ngc does.
  • Ingest writes HAS_HYDRATE_TRIGGERS through the same constant the phase reads.
  • The doc comment now says that older runtimes turn the feature on from withIncrementalHydration().

Not in this PR (already on main):

  • With only prefetch triggers, ngc emits the prefetch op before the default ɵɵdeferOnIdle(), and OXC emits it after.
  • With no angularVersion set, OXC treats the version as latest, as for every other version gate. So a v20/v21 app now gets the call and throws at runtime.

… defer blocks

Since Angular 22.0.0-next.11 the incremental hydration runtime stays inert until a template calls ɵɵenableIncrementalHydrationRuntime(). OXC never emitted it, so @defer blocks with hydrate triggers were never hydrated on the client.

Port Angular's insertIncrementalHydrationRuntime phase: one call per view, before its first defer block with hydrate triggers. Skipped for Angular versions without the instruction.
Copilot AI balanced review requested due to automatic review settings October 21, 2026 06:38

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing. Review the Copilot code review billing settings.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 21, 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 ✅ Completed 2026-10-21T07:18:09.084453Z 2874926 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.

…ts placement in tests

- Place the op, OpKind and identifier next to the other defer items.
- Give the op the defer block's source span, as Angular does.
- Write HAS_HYDRATE_TRIGGERS in ingest through the same constant the phase reads.
- Fix the doc: older runtimes turn the feature on from withIncrementalHydration().
- Tests: unit test for the 22.0.0-next.11 cutoff (rc, beta, later minors);
  check the exact statement after each call and which function it is in;
  cover hydrate never / when / timer and i18n.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@Brooooooklyn

Copy link
Copy Markdown
Member

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. You're on a roll.

Reviewed commit: 28749264a7

ℹ️ 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 68b3ee5 into voidzero-dev:main Oct 21, 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.

3 participants