33//! Configures @defer block instructions with trigger and dependency information.
44//!
55//! This phase processes DeferOp and DeferOnOp to:
6- //! 1. Collect timing configs into the constant pool
6+ //! 1. Wrap timing configs in ConstCollectedExpr (resolved to consts later by Phase 53)
77//! 2. Link defer triggers to their target defer blocks
88//! 3. Set up main, placeholder, loading, and error template slots
99//! 4. Configure timing parameters (minimum time, loading after, etc.)
1010//!
1111//! Ported from Angular's `template/pipeline/src/phases/defer_configs.ts`.
12+ //!
13+ //! Key difference from old approach: instead of calling `job.add_const()` here (which
14+ //! places timer configs before i18n consts), we create ConstCollectedExpr wrappers.
15+ //! These are resolved by Phase 53 (collectConstExpressions) which runs AFTER Phase 52
16+ //! (collectI18nConsts), ensuring correct const array ordering.
1217
13- use oxc_allocator:: Vec as OxcVec ;
18+ use oxc_allocator:: { Box , Vec as OxcVec } ;
1419
20+ use crate :: ast:: expression:: { AbsoluteSourceSpan , LiteralPrimitive , LiteralValue , ParseSpan } ;
1521use crate :: ir:: enums:: DeferOpModifierKind ;
22+ use crate :: ir:: expression:: { ConstCollectedExpr , IrExpression , IrLiteralArrayExpr } ;
1623use crate :: ir:: ops:: { CreateOp , UpdateOp , XrefId } ;
17- use crate :: pipeline:: compilation:: { ComponentCompilationJob , ConstValue } ;
24+ use crate :: pipeline:: compilation:: ComponentCompilationJob ;
1825
1926/// Collected timing config for a defer block.
2027#[ derive( Clone ) ]
@@ -28,7 +35,7 @@ struct DeferTimingConfig {
2835/// Configures defer instructions with trigger and dependency information.
2936///
3037/// This phase:
31- /// 1. Collects timing configs and adds them to the constant pool
38+ /// 1. Collects timing configs and wraps them in ConstCollectedExpr for later resolution
3239/// 2. Collects all DeferOp blocks and their associated DeferOnOp triggers
3340/// 3. Links triggers to their target defer blocks
3441/// 4. Validates timing parameters
@@ -54,51 +61,123 @@ pub fn configure_defer_instructions(job: &mut ComponentCompilationJob<'_>) {
5461 } )
5562 . collect ( ) ;
5663
57- // Create const pool entries for timing configs
58- // Map: xref -> (placeholder_config_index, loading_config_index)
59- let mut config_indices: std:: vec:: Vec < ( XrefId , Option < u32 > , Option < u32 > ) > = Vec :: new ( ) ;
64+ // Build ConstCollectedExpr wrappers for each defer block's timing configs.
65+ // These wrap a LiteralArray (e.g. [100, null]) in ConstCollectedExpr so that
66+ // Phase 53 (collectConstExpressions) will lift them to consts AFTER i18n consts.
67+ //
68+ // This matches Angular TS's defer_configs.ts which uses:
69+ // op.loadingConfig = new ir.ConstCollectedExpr(literalOrArrayLiteral([...]))
70+ let mut config_exprs: std:: vec:: Vec < (
71+ XrefId ,
72+ Option < Box < ' _ , IrExpression < ' _ > > > ,
73+ Option < Box < ' _ , IrExpression < ' _ > > > ,
74+ ) > = Vec :: new ( ) ;
75+
76+ let span = ParseSpan { start : 0 , end : 0 } ;
77+ let source_span = AbsoluteSourceSpan { start : 0 , end : 0 } ;
6078
6179 for config in & timing_configs {
62- let mut placeholder_config_idx = None ;
63- let mut loading_config_idx = None ;
80+ let mut placeholder_config_expr = None ;
81+ let mut loading_config_expr = None ;
6482
6583 // Create loading config: [minimumTime, afterTime]
66- // Note: Angular processes loadingConfig before placeholderConfig in transformExpressionsInOp
67- // (see ir/src/expression.ts lines 1177-1186), so we must add loading config first
68- // to get the correct const pool index order.
84+ // Angular processes loadingConfig before placeholderConfig in transformExpressionsInOp
85+ // (see ir/src/expression.ts lines 1241-1251), so we create loading config first.
86+ // Angular uses `literalOrArrayLiteral([op.loadingMinimumTime, op.loadingAfterTime])`
87+ // which emits `null` for missing values, not `0`.
6988 if config. loading_minimum_time . is_some ( ) || config. loading_after_time . is_some ( ) {
70- let min_time = config. loading_minimum_time . unwrap_or ( 0 ) ;
71- let after_time = config. loading_after_time . unwrap_or ( 0 ) ;
72- let mut entries = OxcVec :: new_in ( allocator) ;
73- entries. push ( ConstValue :: Number ( min_time as f64 ) ) ;
74- entries. push ( ConstValue :: Number ( after_time as f64 ) ) ;
75- let const_value = ConstValue :: Array ( entries) ;
76- loading_config_idx = Some ( job. add_const ( const_value) ) ;
89+ let mut elements = OxcVec :: with_capacity_in ( 2 , allocator) ;
90+
91+ // minimumTime: number or null
92+ let min_val = match config. loading_minimum_time {
93+ Some ( t) => LiteralValue :: Number ( t as f64 ) ,
94+ None => LiteralValue :: Null ,
95+ } ;
96+ elements. push ( IrExpression :: Ast ( Box :: new_in (
97+ crate :: ast:: expression:: AngularExpression :: LiteralPrimitive ( Box :: new_in (
98+ LiteralPrimitive { span, source_span, value : min_val } ,
99+ allocator,
100+ ) ) ,
101+ allocator,
102+ ) ) ) ;
103+
104+ // afterTime: number or null
105+ let after_val = match config. loading_after_time {
106+ Some ( t) => LiteralValue :: Number ( t as f64 ) ,
107+ None => LiteralValue :: Null ,
108+ } ;
109+ elements. push ( IrExpression :: Ast ( Box :: new_in (
110+ crate :: ast:: expression:: AngularExpression :: LiteralPrimitive ( Box :: new_in (
111+ LiteralPrimitive { span, source_span, value : after_val } ,
112+ allocator,
113+ ) ) ,
114+ allocator,
115+ ) ) ) ;
116+
117+ let array_expr = IrExpression :: LiteralArray ( Box :: new_in (
118+ IrLiteralArrayExpr { elements, source_span : None } ,
119+ allocator,
120+ ) ) ;
121+
122+ loading_config_expr = Some ( Box :: new_in (
123+ IrExpression :: ConstCollected ( Box :: new_in (
124+ ConstCollectedExpr {
125+ expr : Box :: new_in ( array_expr, allocator) ,
126+ source_span : None ,
127+ } ,
128+ allocator,
129+ ) ) ,
130+ allocator,
131+ ) ) ;
77132 }
78133
79134 // Create placeholder config: [minimumTime]
80135 if let Some ( min_time) = config. placeholder_minimum_time {
81- let mut entries = OxcVec :: new_in ( allocator) ;
82- entries. push ( ConstValue :: Number ( min_time as f64 ) ) ;
83- let const_value = ConstValue :: Array ( entries) ;
84- placeholder_config_idx = Some ( job. add_const ( const_value) ) ;
136+ let mut elements = OxcVec :: with_capacity_in ( 1 , allocator) ;
137+ elements. push ( IrExpression :: Ast ( Box :: new_in (
138+ crate :: ast:: expression:: AngularExpression :: LiteralPrimitive ( Box :: new_in (
139+ LiteralPrimitive {
140+ span,
141+ source_span,
142+ value : LiteralValue :: Number ( min_time as f64 ) ,
143+ } ,
144+ allocator,
145+ ) ) ,
146+ allocator,
147+ ) ) ) ;
148+
149+ let array_expr = IrExpression :: LiteralArray ( Box :: new_in (
150+ IrLiteralArrayExpr { elements, source_span : None } ,
151+ allocator,
152+ ) ) ;
153+
154+ placeholder_config_expr = Some ( Box :: new_in (
155+ IrExpression :: ConstCollected ( Box :: new_in (
156+ ConstCollectedExpr {
157+ expr : Box :: new_in ( array_expr, allocator) ,
158+ source_span : None ,
159+ } ,
160+ allocator,
161+ ) ) ,
162+ allocator,
163+ ) ) ;
85164 }
86165
87- config_indices . push ( ( config. xref , placeholder_config_idx , loading_config_idx ) ) ;
166+ config_exprs . push ( ( config. xref , placeholder_config_expr , loading_config_expr ) ) ;
88167 }
89168
90- // Update DeferOp with config indices
169+ // Update DeferOp with config expressions
91170 let view_xrefs: std:: vec:: Vec < XrefId > = job. all_views ( ) . map ( |v| v. xref ) . collect ( ) ;
92171 for view_xref in view_xrefs {
93172 if let Some ( view) = job. view_mut ( view_xref) {
94173 for op in view. create . iter_mut ( ) {
95174 if let CreateOp :: Defer ( defer) = op {
96- // Find the config indices for this defer block
97- if let Some ( ( _, placeholder_idx , loading_idx ) ) =
98- config_indices . iter ( ) . find ( |( xref, _, _) | * xref == defer. xref )
175+ // Find the config expressions for this defer block
176+ if let Some ( ( _, placeholder_expr , loading_expr ) ) =
177+ config_exprs . iter_mut ( ) . find ( |( xref, _, _) | * xref == defer. xref )
99178 {
100- defer. placeholder_config = * placeholder_idx ;
101- defer. loading_config = * loading_idx ;
179+ defer. placeholder_config = placeholder_expr . take ( ) ;
180+ defer. loading_config = loading_expr . take ( ) ;
102181 }
103182 }
104183 }
0 commit comments