@@ -10,7 +10,11 @@ import {
10
10
createCallExpression ,
11
11
HoistTransform ,
12
12
CREATE_STATIC ,
13
- ExpressionNode
13
+ ExpressionNode ,
14
+ ElementTypes ,
15
+ PlainElementNode ,
16
+ JSChildNode ,
17
+ createSimpleExpression
14
18
} from '@vue/compiler-core'
15
19
import {
16
20
isVoidTag ,
@@ -24,41 +28,113 @@ import {
24
28
stringifyStyle
25
29
} from '@vue/shared'
26
30
31
+ export const enum StringifyThresholds {
32
+ ELEMENT_WITH_BINDING_COUNT = 5 ,
33
+ NODE_COUNT = 20
34
+ }
35
+
27
36
// Turn eligible hoisted static trees into stringied static nodes, e.g.
28
37
// const _hoisted_1 = createStaticVNode(`<div class="foo">bar</div>`)
29
38
// This is only performed in non-in-browser compilations.
30
- export const stringifyStatic : HoistTransform = ( node , context ) => {
31
- if ( shouldOptimize ( node ) ) {
32
- return createCallExpression ( context . helper ( CREATE_STATIC ) , [
33
- JSON . stringify ( stringifyElement ( node , context ) )
34
- ] )
35
- } else {
36
- return node . codegenNode !
39
+ export const stringifyStatic : HoistTransform = ( children , context ) => {
40
+ let nc = 0 // current node count
41
+ let ec = 0 // current element with binding count
42
+ const currentEligibleNodes : PlainElementNode [ ] = [ ]
43
+
44
+ for ( let i = 0 ; i < children . length ; i ++ ) {
45
+ const child = children [ i ]
46
+ const hoisted = getHoistedNode ( child )
47
+ if ( hoisted ) {
48
+ // presence of hoisted means child must be a plain element Node
49
+ const node = child as PlainElementNode
50
+ const result = analyzeNode ( node )
51
+ if ( result ) {
52
+ // node is stringifiable, record state
53
+ nc += result [ 0 ]
54
+ ec += result [ 1 ]
55
+ currentEligibleNodes . push ( node )
56
+ continue
57
+ }
58
+ }
59
+
60
+ // we only reach here if we ran into a node that is not stringifiable
61
+ // check if currently analyzed nodes meet criteria for stringification.
62
+ if (
63
+ nc >= StringifyThresholds . NODE_COUNT ||
64
+ ec >= StringifyThresholds . ELEMENT_WITH_BINDING_COUNT
65
+ ) {
66
+ // combine all currently eligible nodes into a single static vnode call
67
+ const staticCall = createCallExpression ( context . helper ( CREATE_STATIC ) , [
68
+ JSON . stringify (
69
+ currentEligibleNodes
70
+ . map ( node => stringifyElement ( node , context ) )
71
+ . join ( '' )
72
+ ) ,
73
+ // the 2nd argument indicates the number of DOM nodes this static vnode
74
+ // will insert / hydrate
75
+ String ( currentEligibleNodes . length )
76
+ ] )
77
+ // replace the first node's hoisted expression with the static vnode call
78
+ replaceHoist ( currentEligibleNodes [ 0 ] , staticCall , context )
79
+
80
+ const n = currentEligibleNodes . length
81
+ if ( n > 1 ) {
82
+ for ( let j = 1 ; j < n ; j ++ ) {
83
+ // for the merged nodes, set their hoisted expression to null
84
+ replaceHoist (
85
+ currentEligibleNodes [ j ] ,
86
+ createSimpleExpression ( `null` , false ) ,
87
+ context
88
+ )
89
+ }
90
+ // also remove merged nodes from children
91
+ const deleteCount = n - 1
92
+ children . splice ( i - n + 1 , deleteCount )
93
+ // adjust iteration index
94
+ i -= deleteCount
95
+ }
96
+ }
97
+
98
+ // reset state
99
+ nc = 0
100
+ ec = 0
101
+ currentEligibleNodes . length = 0
37
102
}
38
103
}
39
104
40
- export const enum StringifyThresholds {
41
- ELEMENT_WITH_BINDING_COUNT = 5 ,
42
- NODE_COUNT = 20
43
- }
105
+ const getHoistedNode = ( node : TemplateChildNode ) =>
106
+ node . type === NodeTypes . ELEMENT &&
107
+ node . tagType === ElementTypes . ELEMENT &&
108
+ node . codegenNode &&
109
+ node . codegenNode . type === NodeTypes . SIMPLE_EXPRESSION &&
110
+ node . codegenNode . hoisted
44
111
45
112
const dataAriaRE = / ^ ( d a t a | a r i a ) - /
46
113
const isStringifiableAttr = ( name : string ) => {
47
114
return isKnownAttr ( name ) || dataAriaRE . test ( name )
48
115
}
49
116
50
- // Opt-in heuristics based on:
51
- // 1. number of elements with attributes > 5.
52
- // 2. OR: number of total nodes > 20
53
- // For some simple trees, the performance can actually be worse.
54
- // it is only worth it when the tree is complex enough
55
- // (e.g. big piece of static content)
56
- function shouldOptimize ( node : ElementNode ) : boolean {
57
- let bindingThreshold = StringifyThresholds . ELEMENT_WITH_BINDING_COUNT
58
- let nodeThreshold = StringifyThresholds . NODE_COUNT
117
+ const replaceHoist = (
118
+ node : PlainElementNode ,
119
+ replacement : JSChildNode ,
120
+ context : TransformContext
121
+ ) => {
122
+ const hoistToReplace = ( node . codegenNode as SimpleExpressionNode ) . hoisted !
123
+ context . hoists [ context . hoists . indexOf ( hoistToReplace ) ] = replacement
124
+ }
59
125
126
+ /**
127
+ * for a hoisted node, analyze it and return:
128
+ * - false: bailed (contains runtime constant)
129
+ * - [x, y] where
130
+ * - x is the number of nodes inside
131
+ * - y is the number of element with bindings inside
132
+ */
133
+ function analyzeNode ( node : PlainElementNode ) : [ number , number ] | false {
134
+ let nc = 1 // node count
135
+ let ec = node . props . length > 0 ? 1 : 0 // element w/ binding count
60
136
let bailed = false
61
- const bail = ( ) => {
137
+ const bail = ( ) : false => {
62
138
bailed = true
63
139
return false
64
140
}
@@ -67,7 +143,7 @@ function shouldOptimize(node: ElementNode): boolean {
67
143
// output compared to imperative node insertions.
68
144
// probably only need to check for most common case
69
145
// i.e. non-phrasing-content tags inside `<p>`
70
- function walk ( node : ElementNode ) {
146
+ function walk ( node : ElementNode ) : boolean {
71
147
for ( let i = 0 ; i < node . props . length ; i ++ ) {
72
148
const p = node . props [ i ]
73
149
// bail on non-attr bindings
@@ -97,26 +173,28 @@ function shouldOptimize(node: ElementNode): boolean {
97
173
}
98
174
}
99
175
for ( let i = 0 ; i < node . children . length ; i ++ ) {
100
- if ( -- nodeThreshold === 0 ) {
176
+ nc ++
177
+ if ( nc >= StringifyThresholds . NODE_COUNT ) {
101
178
return true
102
179
}
103
180
const child = node . children [ i ]
104
181
if ( child . type === NodeTypes . ELEMENT ) {
105
- if ( child . props . length > 0 && -- bindingThreshold === 0 ) {
106
- return true
107
- }
108
- if ( walk ( child ) ) {
109
- return true
182
+ if ( child . props . length > 0 ) {
183
+ ec ++
184
+ if ( ec >= StringifyThresholds . ELEMENT_WITH_BINDING_COUNT ) {
185
+ return true
186
+ }
110
187
}
188
+ walk ( child )
111
189
if ( bailed ) {
112
190
return false
113
191
}
114
192
}
115
193
}
116
- return false
194
+ return true
117
195
}
118
196
119
- return walk ( node )
197
+ return walk ( node ) ? [ nc , ec ] : false
120
198
}
121
199
122
200
function stringifyElement (
0 commit comments