Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions crates/oxc_angular_compiler/src/component/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,24 @@ impl AngularVersion {
false
}

/// Check if this version has the `ɵɵenableIncrementalHydrationRuntime`
/// instruction (v22.0.0-next.11+).
///
/// From that release `withIncrementalHydration()` alone no longer turns
/// incremental hydration on: a compiled template must also call the
/// instruction, so every view with a `hydrate` trigger emits it. Earlier
/// runtimes don't export it and turn the feature on from the provider
/// alone.
pub fn supports_incremental_hydration_runtime(&self) -> bool {
if self.major == 22 && self.minor == 0 && self.patch == 0 {
return match self.prerelease {
None => true,
Some(prerelease) => prerelease >= Prerelease::Next(11),
};
}
self.is_at_least(22, 0, 0)
}

/// Check if this version is `major.minor.patch` or newer.
///
/// Compares the release triple only: a prerelease of `22.3.0` counts as
Expand Down Expand Up @@ -892,4 +910,30 @@ mod tests {
assert!(!version.strips_namespaced_block_tag(), "{version:?}");
}
}

#[test]
fn incremental_hydration_runtime_starts_at_22_0_0_next_11() {
let parse = |s| AngularVersion::parse(s).unwrap();

for version in [
parse("22.0.0-next.11"),
parse("22.0.0-rc.0"),
parse("22.0.0"),
parse("22.1.0-next.0"),
parse("23.0.0"),
AngularVersion::new(u32::MAX, 0, 0),
] {
assert!(version.supports_incremental_hydration_runtime(), "{version:?}");
}

for version in [
parse("21.2.25"),
parse("22.0.0-next.0"),
parse("22.0.0-next.10"),
// Unknown tags parse to `Other`, below `next`.
parse("22.0.0-beta.1"),
] {
assert!(!version.supports_incremental_hydration_runtime(), "{version:?}");
}
}
}
7 changes: 7 additions & 0 deletions crates/oxc_angular_compiler/src/ir/enums.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ pub enum OpKind {
DeferOn,
/// Control @defer with a custom condition.
DeferWhen,
/// Activate the incremental hydration runtime.
EnableIncrementalHydrationRuntime,

// I18n operations
/// An i18n message for consts array.
Expand Down Expand Up @@ -507,6 +509,11 @@ impl TDeferDetailsFlags {
pub const DEFAULT: Self = Self(0);
/// Has hydrate triggers.
pub const HAS_HYDRATE_TRIGGERS: Self = Self(1 << 0);

/// The raw flag bits, as stored in `DeferOp::flags`.
pub const fn bits(self) -> u32 {
self.0 as u32
}
}

#[cfg(test)]
Expand Down
2 changes: 2 additions & 0 deletions crates/oxc_angular_compiler/src/ir/expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2452,6 +2452,7 @@ pub fn transform_expressions_in_create_op<'a, F>(
| CreateOp::I18nMessage(_)
| CreateOp::Namespace(_)
| CreateOp::ProjectionDef(_)
| CreateOp::EnableIncrementalHydrationRuntime(_)
| CreateOp::Projection(_)
| CreateOp::DeclareLet(_)
| CreateOp::I18nStart(_)
Expand Down Expand Up @@ -2799,6 +2800,7 @@ pub fn visit_expressions_in_create_op<'a, F>(
| CreateOp::I18nMessage(_)
| CreateOp::Namespace(_)
| CreateOp::ProjectionDef(_)
| CreateOp::EnableIncrementalHydrationRuntime(_)
| CreateOp::Projection(_)
| CreateOp::DeclareLet(_)
| CreateOp::I18nStart(_)
Expand Down
19 changes: 19 additions & 0 deletions crates/oxc_angular_compiler/src/ir/ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,8 @@ pub enum CreateOp<'a> {
Defer(DeferOp<'a>),
/// Defer trigger.
DeferOn(DeferOnOp<'a>),
/// Activate the incremental hydration runtime.
EnableIncrementalHydrationRuntime(EnableIncrementalHydrationRuntimeOp<'a>),
/// An i18n message.
I18nMessage(I18nMessageOp<'a>),
/// Change namespace.
Expand Down Expand Up @@ -231,6 +233,9 @@ impl<'a> Op for CreateOp<'a> {
CreateOp::Pipe(_) => OpKind::Pipe,
CreateOp::Defer(_) => OpKind::Defer,
CreateOp::DeferOn(_) => OpKind::DeferOn,
CreateOp::EnableIncrementalHydrationRuntime(_) => {
OpKind::EnableIncrementalHydrationRuntime
}
CreateOp::I18nMessage(_) => OpKind::I18nMessage,
CreateOp::Namespace(_) => OpKind::Namespace,
CreateOp::ProjectionDef(_) => OpKind::ProjectionDef,
Expand Down Expand Up @@ -276,6 +281,7 @@ impl<'a> Op for CreateOp<'a> {
CreateOp::Pipe(op) => op.base.prev,
CreateOp::Defer(op) => op.base.prev,
CreateOp::DeferOn(op) => op.base.prev,
CreateOp::EnableIncrementalHydrationRuntime(op) => op.base.prev,
CreateOp::I18nMessage(op) => op.base.prev,
CreateOp::Namespace(op) => op.base.prev,
CreateOp::ProjectionDef(op) => op.base.prev,
Expand Down Expand Up @@ -321,6 +327,7 @@ impl<'a> Op for CreateOp<'a> {
CreateOp::Pipe(op) => op.base.next,
CreateOp::Defer(op) => op.base.next,
CreateOp::DeferOn(op) => op.base.next,
CreateOp::EnableIncrementalHydrationRuntime(op) => op.base.next,
CreateOp::I18nMessage(op) => op.base.next,
CreateOp::Namespace(op) => op.base.next,
CreateOp::ProjectionDef(op) => op.base.next,
Expand Down Expand Up @@ -366,6 +373,7 @@ impl<'a> Op for CreateOp<'a> {
CreateOp::Pipe(op) => op.base.prev = prev,
CreateOp::Defer(op) => op.base.prev = prev,
CreateOp::DeferOn(op) => op.base.prev = prev,
CreateOp::EnableIncrementalHydrationRuntime(op) => op.base.prev = prev,
CreateOp::I18nMessage(op) => op.base.prev = prev,
CreateOp::Namespace(op) => op.base.prev = prev,
CreateOp::ProjectionDef(op) => op.base.prev = prev,
Expand Down Expand Up @@ -411,6 +419,7 @@ impl<'a> Op for CreateOp<'a> {
CreateOp::Pipe(op) => op.base.next = next,
CreateOp::Defer(op) => op.base.next = next,
CreateOp::DeferOn(op) => op.base.next = next,
CreateOp::EnableIncrementalHydrationRuntime(op) => op.base.next = next,
CreateOp::I18nMessage(op) => op.base.next = next,
CreateOp::Namespace(op) => op.base.next = next,
CreateOp::ProjectionDef(op) => op.base.next = next,
Expand Down Expand Up @@ -1082,6 +1091,16 @@ pub struct DeferOnOp<'a> {
pub options: Option<Box<'a, IrExpression<'a>>>,
}

/// Activate the incremental hydration runtime.
///
/// Corresponds to the ɵɵenableIncrementalHydrationRuntime() instruction, emitted
/// once per view before its first `@defer` block with hydrate triggers.
#[derive(Debug)]
pub struct EnableIncrementalHydrationRuntimeOp<'a> {
/// Common operation metadata.
pub base: CreateOpBase<'a>,
}

/// Defer with condition (update-time operation).
///
/// This operation evaluates a user-provided condition expression during
Expand Down
8 changes: 8 additions & 0 deletions crates/oxc_angular_compiler/src/pipeline/compilation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,14 @@ impl<'a> ComponentCompilationJob<'a> {
self.angular_version.map_or(true, |v: AngularVersion| v.supports_dom_property())
}

/// Check if `ɵɵenableIncrementalHydrationRuntime` is supported (Angular 22.0.0-next.11+).
///
/// Returns `true` when version is unknown (None = latest).
pub fn supports_incremental_hydration_runtime(&self) -> bool {
self.angular_version
.map_or(true, |v: AngularVersion| v.supports_incremental_hydration_runtime())
}

/// Resolve whether safe navigation (`?.`) should use legacy null semantics.
///
/// Returns `true` when `?.` must expand to the legacy `== null ? null` ternary,
Expand Down
5 changes: 2 additions & 3 deletions crates/oxc_angular_compiler/src/pipeline/ingest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ use crate::ast::r3::{
R3TextAttribute, SecurityContext,
};
use crate::ir::enums::{
BindingKind, DeferOpModifierKind, DeferTriggerKind, Namespace, TemplateKind,
BindingKind, DeferOpModifierKind, DeferTriggerKind, Namespace, TDeferDetailsFlags, TemplateKind,
};
use crate::ir::expression::{
BinaryExpr, ConditionalCaseExpr, EmptyExpr, IrBinaryOperator, IrExpression, LexicalReadExpr,
Expand Down Expand Up @@ -3314,8 +3314,7 @@ fn ingest_defer_block<'a>(
// Calculate flags based on whether hydrate triggers exist.
// Matches Angular's calcDeferBlockFlags function in ingest.ts
let flags = if defer_block.hydrate_triggers.has_any() {
// TDeferDetailsFlags.HasHydrateTriggers = 1
Some(1u32)
Some(TDeferDetailsFlags::HAS_HYDRATE_TRIGGERS.bits())
} else {
None
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//! Incremental hydration runtime phase.
//!
//! Inserts an `EnableIncrementalHydrationRuntime` op before the first `@defer`
//! block with hydrate triggers in each view, so the view emits
//! `ɵɵenableIncrementalHydrationRuntime()` once. Without that call the runtime
//! never hydrates `hydrate` blocks.
//!
//! Ported from Angular's `template/pipeline/src/phases/insert_incremental_hydration_runtime.ts`.

use crate::ir::enums::TDeferDetailsFlags;
use crate::ir::ops::{CreateOp, CreateOpBase, EnableIncrementalHydrationRuntimeOp};
use crate::pipeline::compilation::ComponentCompilationJob;

/// Inserts `EnableIncrementalHydrationRuntime` before the first hydrating `Defer` op of each view.
pub fn insert_incremental_hydration_runtime(job: &mut ComponentCompilationJob<'_>) {
if !job.supports_incremental_hydration_runtime() {
return;
}

for view in job.all_views_mut() {
let mut cursor = view.create.cursor_front();
loop {
// The new op takes the defer block's source span, as in Angular.
let hydrating_defer_span = match cursor.current() {
Some(CreateOp::Defer(defer))
if defer.flags.is_some_and(|flags| {
flags & TDeferDetailsFlags::HAS_HYDRATE_TRIGGERS.bits() != 0
}) =>
{
Some(defer.base.source_span)
}
_ => None,
};
if let Some(source_span) = hydrating_defer_span {
cursor.insert_before(CreateOp::EnableIncrementalHydrationRuntime(
EnableIncrementalHydrationRuntimeOp {
base: CreateOpBase { source_span, ..Default::default() },
},
));
break;
}
if !cursor.move_next() {
break;
}
}
}
}
8 changes: 8 additions & 0 deletions crates/oxc_angular_compiler/src/pipeline/phases/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ mod host_style_property_parsing;
pub mod i18n_closure;
mod i18n_const_collection;
mod i18n_text_extraction;
mod insert_incremental_hydration_runtime;
mod local_refs;
mod namespace;
mod naming;
Expand Down Expand Up @@ -254,6 +255,13 @@ pub static PHASES: &[Phase] = &[
run_host: None,
name: "configureDeferInstructions",
},
// Phase 19b: insertIncrementalHydrationRuntime (Template only)
Phase {
kind: CompilationJobKind::Template,
run: insert_incremental_hydration_runtime::insert_incremental_hydration_runtime,
run_host: None,
name: "insertIncrementalHydrationRuntime",
},
// Phase 20: createVariadicPipes (Template only)
Phase {
kind: CompilationJobKind::Template,
Expand Down
3 changes: 3 additions & 0 deletions crates/oxc_angular_compiler/src/pipeline/phases/reify/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -858,6 +858,9 @@ fn reify_create_op<'a>(
proj_def.def.as_ref(),
))
}
CreateOp::EnableIncrementalHydrationRuntime(_) => {
Some(create_enable_incremental_hydration_runtime_stmt(allocator, core_namespace))
}
CreateOp::DisableBindings(_) => {
// Emit disableBindings instruction
Some(create_disable_bindings_stmt(allocator, core_namespace))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,19 @@ pub fn create_disable_bindings_stmt<'a>(
)
}

/// Creates an ɵɵenableIncrementalHydrationRuntime() call statement.
pub fn create_enable_incremental_hydration_runtime_stmt<'a>(
allocator: &'a oxc_allocator::Allocator,
core_namespace: &'a str,
) -> OutputStatement<'a> {
create_instruction_call_stmt(
allocator,
core_namespace,
Identifiers::ENABLE_INCREMENTAL_HYDRATION_RUNTIME,
OxcVec::new_in(&allocator),
)
}

/// Creates an ɵɵenableBindings() call statement.
pub fn create_enable_bindings_stmt<'a>(
allocator: &'a oxc_allocator::Allocator,
Expand Down
4 changes: 4 additions & 0 deletions crates/oxc_angular_compiler/src/r3/identifiers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,10 @@ impl Identifiers {
/// Enable timer scheduling for defer.
pub const DEFER_ENABLE_TIMER_SCHEDULING: &'static str = "ɵɵdeferEnableTimerScheduling";

/// Activate the incremental hydration runtime.
pub const ENABLE_INCREMENTAL_HYDRATION_RUNTIME: &'static str =
"ɵɵenableIncrementalHydrationRuntime";

// ========================================================================
// Control Flow Instructions
// ========================================================================
Expand Down
Loading
Loading