1
- /** @import { Expression, Statement } from 'estree' */
1
+ /** @import { Expression } from 'estree' */
2
2
/** @import { Location } from 'locate-character' */
3
3
/** @import { AST } from '#compiler' */
4
4
/** @import { ComponentContext, ComponentServerTransformState } from '../types.js' */
@@ -12,7 +12,8 @@ import {
12
12
process_children ,
13
13
build_template ,
14
14
build_attribute_value ,
15
- call_child_renderer
15
+ create_child_block ,
16
+ PromiseOptimiser
16
17
} from './shared/utils.js' ;
17
18
18
19
/**
@@ -27,21 +28,38 @@ export function RegularElement(node, context) {
27
28
...context . state ,
28
29
namespace,
29
30
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 : [ ]
31
34
} ;
32
35
33
36
const node_is_void = is_void ( node . name ) ;
34
37
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
38
43
39
44
if ( ( node . name === 'script' || node . name === 'style' ) && node . fragment . nodes . length === 1 ) {
40
- context . state . template . push (
45
+ state . template . push (
41
46
b . literal ( /** @type {AST.Text } */ ( node . fragment . nodes [ 0 ] ) . data ) ,
42
47
b . literal ( `</${ node . name } >` )
43
48
) ;
44
49
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
+
45
63
return ;
46
64
}
47
65
@@ -77,114 +95,92 @@ export function RegularElement(node, context) {
77
95
) ;
78
96
}
79
97
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 (
86
101
( 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
89
117
) ;
90
118
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 )
118
132
) ;
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 ) ;
146
135
}
136
+
137
+ return ;
147
138
}
148
139
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
+
158
155
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
170
159
) ;
171
160
} else {
172
161
const inner_state = { ...state , template : [ ] , init : [ ] } ;
173
162
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 ) ] )
185
167
) ;
186
168
}
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 ) {
188
184
// if this is a `<textarea>` value or a contenteditable binding, we only add
189
185
// the body if the attribute/binding is falsy
190
186
const inner_state = { ...state , template : [ ] , init : [ ] } ;
@@ -209,22 +205,23 @@ export function RegularElement(node, context) {
209
205
process_children ( trimmed , { ...context , state } ) ;
210
206
}
211
207
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
-
223
208
if ( ! node_is_void ) {
224
209
state . template . push ( b . literal ( `</${ node . name } >` ) ) ;
225
210
}
226
211
227
212
if ( dev ) {
228
213
state . template . push ( b . stmt ( b . call ( '$.pop_element' ) ) ) ;
229
214
}
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
+ }
230
227
}
0 commit comments