1- /** @import { Expression, Statement } from 'estree' */
1+ /** @import { Expression } from 'estree' */
22/** @import { Location } from 'locate-character' */
33/** @import { AST } from '#compiler' */
44/** @import { ComponentContext, ComponentServerTransformState } from '../types.js' */
@@ -12,7 +12,8 @@ import {
1212 process_children ,
1313 build_template ,
1414 build_attribute_value ,
15- call_child_renderer
15+ create_child_block ,
16+ PromiseOptimiser
1617} from './shared/utils.js' ;
1718
1819/**
@@ -27,21 +28,38 @@ export function RegularElement(node, context) {
2728 ...context . state ,
2829 namespace,
2930 preserve_whitespace :
30- context . state . preserve_whitespace || node . name === 'pre' || node . name === 'textarea'
31+ context . state . preserve_whitespace || node . name === 'pre' || node . name === 'textarea' ,
32+ init : [ ] ,
33+ template : [ ]
3134 } ;
3235
3336 const node_is_void = is_void ( node . name ) ;
3437
35- context . state . template . push ( b . literal ( `<${ node . name } ` ) ) ;
36- const body = build_element_attributes ( node , { ...context , state } ) ;
37- context . state . template . push ( b . literal ( node_is_void ? '/>' : '>' ) ) ; // add `/>` for XHTML compliance
38+ const optimiser = new PromiseOptimiser ( ) ;
39+
40+ state . template . push ( b . literal ( `<${ node . name } ` ) ) ;
41+ const body = build_element_attributes ( node , { ...context , state } , optimiser . transform ) ;
42+ state . template . push ( b . literal ( node_is_void ? '/>' : '>' ) ) ; // add `/>` for XHTML compliance
3843
3944 if ( ( node . name === 'script' || node . name === 'style' ) && node . fragment . nodes . length === 1 ) {
40- context . state . template . push (
45+ state . template . push (
4146 b . literal ( /** @type {AST.Text } */ ( node . fragment . nodes [ 0 ] ) . data ) ,
4247 b . literal ( `</${ node . name } >` )
4348 ) ;
4449
50+ // TODO this is a real edge case, would be good to DRY this out
51+ if ( optimiser . expressions . length > 0 ) {
52+ context . state . template . push (
53+ create_child_block (
54+ b . block ( [ optimiser . apply ( ) , ...state . init , ...build_template ( state . template ) ] ) ,
55+ true
56+ )
57+ ) ;
58+ } else {
59+ context . state . init . push ( ...state . init ) ;
60+ context . state . template . push ( ...state . template ) ;
61+ }
62+
4563 return ;
4664 }
4765
@@ -77,114 +95,92 @@ export function RegularElement(node, context) {
7795 ) ;
7896 }
7997
80- let select_with_value = false ;
81- let select_with_value_async = false ;
82- const template_start = state . template . length ;
83-
84- if ( node . name === 'select' ) {
85- const value = node . attributes . find (
98+ if (
99+ node . name === 'select' &&
100+ node . attributes . some (
86101 ( attribute ) =>
87- ( attribute . type === 'Attribute' || attribute . type === 'BindDirective' ) &&
88- attribute . name === 'value'
102+ ( ( attribute . type === 'Attribute' || attribute . type === 'BindDirective' ) &&
103+ attribute . name === 'value' ) ||
104+ attribute . type === 'SpreadAttribute'
105+ )
106+ ) {
107+ const attributes = build_spread_object (
108+ node ,
109+ node . attributes . filter (
110+ ( attribute ) =>
111+ attribute . type === 'Attribute' ||
112+ attribute . type === 'BindDirective' ||
113+ attribute . type === 'SpreadAttribute'
114+ ) ,
115+ context ,
116+ optimiser . transform
89117 ) ;
90118
91- const spread = node . attributes . find ( ( attribute ) => attribute . type === 'SpreadAttribute' ) ;
92- if ( spread ) {
93- select_with_value = true ;
94- select_with_value_async ||= spread . metadata . expression . has_await ;
95-
96- state . template . push (
97- b . stmt (
98- b . assignment (
99- '=' ,
100- b . id ( '$$renderer.local.select_value' ) ,
101- b . member (
102- build_spread_object (
103- node ,
104- node . attributes . filter (
105- ( attribute ) =>
106- attribute . type === 'Attribute' ||
107- attribute . type === 'BindDirective' ||
108- attribute . type === 'SpreadAttribute'
109- ) ,
110- context
111- ) ,
112- 'value' ,
113- false ,
114- true
115- )
116- )
117- )
119+ const inner_state = { ...state , template : [ ] , init : [ ] } ;
120+ process_children ( trimmed , { ...context , state : inner_state } ) ;
121+
122+ const fn = b . arrow (
123+ [ b . id ( '$$renderer' ) ] ,
124+ b . block ( [ ...state . init , ...build_template ( inner_state . template ) ] )
125+ ) ;
126+
127+ const statement = b . stmt ( b . call ( '$$renderer.select' , attributes , fn ) ) ;
128+
129+ if ( optimiser . expressions . length > 0 ) {
130+ context . state . template . push (
131+ create_child_block ( b . block ( [ optimiser . apply ( ) , ...state . init , statement ] ) , true )
118132 ) ;
119- } else if ( value ) {
120- select_with_value = true ;
121-
122- if ( value . type === 'Attribute' && value . value !== true ) {
123- select_with_value_async ||= ( Array . isArray ( value . value ) ? value . value : [ value . value ] ) . some (
124- ( tag ) => tag . type === 'ExpressionTag' && tag . metadata . expression . has_await
125- ) ;
126- }
127-
128- const left = b . id ( '$$renderer.local.select_value' ) ;
129- if ( value . type === 'Attribute' ) {
130- state . template . push (
131- b . stmt ( b . assignment ( '=' , left , build_attribute_value ( value . value , context ) ) )
132- ) ;
133- } else if ( value . type === 'BindDirective' ) {
134- state . template . push (
135- b . stmt (
136- b . assignment (
137- '=' ,
138- left ,
139- value . expression . type === 'SequenceExpression'
140- ? /** @type {Expression } */ ( context . visit ( b . call ( value . expression . expressions [ 0 ] ) ) )
141- : /** @type {Expression } */ ( context . visit ( value . expression ) )
142- )
143- )
144- ) ;
145- }
133+ } else {
134+ context . state . template . push ( ...state . init , statement ) ;
146135 }
136+
137+ return ;
147138 }
148139
149- if (
150- node . name === 'option' &&
151- ! node . attributes . some (
152- ( attribute ) =>
153- attribute . type === 'SpreadAttribute' ||
154- ( ( attribute . type === 'Attribute' || attribute . type === 'BindDirective' ) &&
155- attribute . name === 'value' )
156- )
157- ) {
140+ if ( node . name === 'option' ) {
141+ const attributes = build_spread_object (
142+ node ,
143+ node . attributes . filter (
144+ ( attribute ) =>
145+ attribute . type === 'Attribute' ||
146+ attribute . type === 'BindDirective' ||
147+ attribute . type === 'SpreadAttribute'
148+ ) ,
149+ context ,
150+ optimiser . transform
151+ ) ;
152+
153+ let body ;
154+
158155 if ( node . metadata . synthetic_value_node ) {
159- state . template . push (
160- b . stmt (
161- b . call (
162- '$.simple_valueless_option' ,
163- b . id ( '$$renderer' ) ,
164- b . thunk (
165- node . metadata . synthetic_value_node . expression ,
166- node . metadata . synthetic_value_node . metadata . expression . has_await
167- )
168- )
169- )
156+ body = optimiser . transform (
157+ node . metadata . synthetic_value_node . expression ,
158+ node . metadata . synthetic_value_node . metadata . expression
170159 ) ;
171160 } else {
172161 const inner_state = { ...state , template : [ ] , init : [ ] } ;
173162 process_children ( trimmed , { ...context , state : inner_state } ) ;
174- state . template . push (
175- b . stmt (
176- b . call (
177- '$.valueless_option' ,
178- b . id ( '$$renderer' ) ,
179- b . arrow (
180- [ b . id ( '$$renderer' ) ] ,
181- b . block ( [ ...inner_state . init , ...build_template ( inner_state . template ) ] )
182- )
183- )
184- )
163+
164+ body = b . arrow (
165+ [ b . id ( '$$renderer' ) ] ,
166+ b . block ( [ ...state . init , ...build_template ( inner_state . template ) ] )
185167 ) ;
186168 }
187- } else if ( body !== null ) {
169+
170+ const statement = b . stmt ( b . call ( '$$renderer.option' , attributes , body ) ) ;
171+
172+ if ( optimiser . expressions . length > 0 ) {
173+ context . state . template . push (
174+ create_child_block ( b . block ( [ optimiser . apply ( ) , ...state . init , statement ] ) , true )
175+ ) ;
176+ } else {
177+ context . state . template . push ( ...state . init , statement ) ;
178+ }
179+
180+ return ;
181+ }
182+
183+ if ( body !== null ) {
188184 // if this is a `<textarea>` value or a contenteditable binding, we only add
189185 // the body if the attribute/binding is falsy
190186 const inner_state = { ...state , template : [ ] , init : [ ] } ;
@@ -209,22 +205,23 @@ export function RegularElement(node, context) {
209205 process_children ( trimmed , { ...context , state } ) ;
210206 }
211207
212- if ( select_with_value ) {
213- // we need to create a child scope so that the `select_value` only applies children of this select element
214- // in an async world, we could technically have two adjacent select elements with async children, in which case
215- // the second element's select_value would override the first element's select_value if the children of the first
216- // element hadn't resolved prior to hitting the second element.
217- const elements = state . template . splice ( template_start , Infinity ) ;
218- state . template . push (
219- call_child_renderer ( b . block ( build_template ( elements ) ) , select_with_value_async )
220- ) ;
221- }
222-
223208 if ( ! node_is_void ) {
224209 state . template . push ( b . literal ( `</${ node . name } >` ) ) ;
225210 }
226211
227212 if ( dev ) {
228213 state . template . push ( b . stmt ( b . call ( '$.pop_element' ) ) ) ;
229214 }
215+
216+ if ( optimiser . expressions . length > 0 ) {
217+ context . state . template . push (
218+ create_child_block (
219+ b . block ( [ optimiser . apply ( ) , ...state . init , ...build_template ( state . template ) ] ) ,
220+ true
221+ )
222+ ) ;
223+ } else {
224+ context . state . init . push ( ...state . init ) ;
225+ context . state . template . push ( ...state . template ) ;
226+ }
230227}
0 commit comments