Skip to content

Commit baa6973

Browse files
committed
wip(compiler): tests for new stringification
1 parent dbf627f commit baa6973

File tree

5 files changed

+84
-51
lines changed

5 files changed

+84
-51
lines changed

packages/compiler-core/src/ast.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ export interface RootNode extends Node {
103103
helpers: symbol[]
104104
components: string[]
105105
directives: string[]
106-
hoists: JSChildNode[]
106+
hoists: (JSChildNode | null)[]
107107
imports: ImportItem[]
108108
cached: number
109109
temps: number

packages/compiler-core/src/codegen.ts

+6-4
Original file line numberDiff line numberDiff line change
@@ -434,7 +434,7 @@ function genAssets(
434434
}
435435
}
436436

437-
function genHoists(hoists: JSChildNode[], context: CodegenContext) {
437+
function genHoists(hoists: (JSChildNode | null)[], context: CodegenContext) {
438438
if (!hoists.length) {
439439
return
440440
}
@@ -451,9 +451,11 @@ function genHoists(hoists: JSChildNode[], context: CodegenContext) {
451451
}
452452

453453
hoists.forEach((exp, i) => {
454-
push(`const _hoisted_${i + 1} = `)
455-
genNode(exp, context)
456-
newline()
454+
if (exp) {
455+
push(`const _hoisted_${i + 1} = `)
456+
genNode(exp, context)
457+
newline()
458+
}
457459
})
458460

459461
if (genScopeId) {

packages/compiler-core/src/transform.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ export interface TransformContext extends Required<TransformOptions> {
8282
helpers: Set<symbol>
8383
components: Set<string>
8484
directives: Set<string>
85-
hoists: JSChildNode[]
85+
hoists: (JSChildNode | null)[]
8686
imports: Set<ImportItem>
8787
temps: number
8888
cached: number

packages/compiler-dom/__tests__/transforms/stringifyStatic.spec.ts

+37-5
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ describe('stringify static html', () => {
3131
)
3232
expect(ast.hoists.length).toBe(1)
3333
// should be a normal vnode call
34-
expect(ast.hoists[0].type).toBe(NodeTypes.VNODE_CALL)
34+
expect(ast.hoists[0]!.type).toBe(NodeTypes.VNODE_CALL)
3535
})
3636

3737
test('should work on eligible content (elements with binding > 5)', () => {
@@ -52,7 +52,8 @@ describe('stringify static html', () => {
5252
`<span class="foo"></span>`,
5353
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
5454
)}</div>`
55-
)
55+
),
56+
'1'
5657
]
5758
})
5859
})
@@ -75,7 +76,36 @@ describe('stringify static html', () => {
7576
`<span></span>`,
7677
StringifyThresholds.NODE_COUNT
7778
)}</div>`
78-
)
79+
),
80+
'1'
81+
]
82+
})
83+
})
84+
85+
test('should work for multiple adjacent nodes', () => {
86+
const { ast } = compileWithStringify(
87+
`<div>${repeat(
88+
`<span class="foo"/>`,
89+
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
90+
)}</div>`
91+
)
92+
// should have 5 hoisted nodes, but the other 4 should be null
93+
expect(ast.hoists.length).toBe(5)
94+
for (let i = 1; i < 5; i++) {
95+
expect(ast.hoists[i]).toBe(null)
96+
}
97+
// should be optimized now
98+
expect(ast.hoists[0]).toMatchObject({
99+
type: NodeTypes.JS_CALL_EXPRESSION,
100+
callee: CREATE_STATIC,
101+
arguments: [
102+
JSON.stringify(
103+
repeat(
104+
`<span class="foo"></span>`,
105+
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
106+
)
107+
),
108+
'5'
79109
]
80110
})
81111
})
@@ -98,7 +128,8 @@ describe('stringify static html', () => {
98128
`<span class="foo bar">1 + false</span>`,
99129
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
100130
)}</div>`
101-
)
131+
),
132+
'1'
102133
]
103134
})
104135
})
@@ -122,7 +153,8 @@ describe('stringify static html', () => {
122153
`<span class="foo&gt;ar">1 + &lt;</span>` + `<span>&amp;</span>`,
123154
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
124155
)}</div>`
125-
)
156+
),
157+
'1'
126158
]
127159
})
128160
})

packages/compiler-dom/src/transforms/stringifyStatic.ts

+39-40
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@ import {
1313
ExpressionNode,
1414
ElementTypes,
1515
PlainElementNode,
16-
JSChildNode,
17-
createSimpleExpression
16+
JSChildNode
1817
} from '@vue/compiler-core'
1918
import {
2019
isVoidTag,
@@ -39,67 +38,67 @@ export const enum StringifyThresholds {
3938
export const stringifyStatic: HoistTransform = (children, context) => {
4039
let nc = 0 // current node count
4140
let ec = 0 // current element with binding count
42-
const currentEligibleNodes: PlainElementNode[] = []
41+
const currentChunk: PlainElementNode[] = []
4342

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.
43+
const stringifyCurrentChunk = (currentIndex: number): number => {
6244
if (
6345
nc >= StringifyThresholds.NODE_COUNT ||
6446
ec >= StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
6547
) {
6648
// combine all currently eligible nodes into a single static vnode call
6749
const staticCall = createCallExpression(context.helper(CREATE_STATIC), [
6850
JSON.stringify(
69-
currentEligibleNodes
70-
.map(node => stringifyElement(node, context))
71-
.join('')
51+
currentChunk.map(node => stringifyElement(node, context)).join('')
7252
),
7353
// the 2nd argument indicates the number of DOM nodes this static vnode
7454
// will insert / hydrate
75-
String(currentEligibleNodes.length)
55+
String(currentChunk.length)
7656
])
7757
// replace the first node's hoisted expression with the static vnode call
78-
replaceHoist(currentEligibleNodes[0], staticCall, context)
58+
replaceHoist(currentChunk[0], staticCall, context)
7959

80-
const n = currentEligibleNodes.length
81-
if (n > 1) {
82-
for (let j = 1; j < n; j++) {
60+
if (currentChunk.length > 1) {
61+
for (let i = 1; i < currentChunk.length; i++) {
8362
// for the merged nodes, set their hoisted expression to null
84-
replaceHoist(
85-
currentEligibleNodes[j],
86-
createSimpleExpression(`null`, false),
87-
context
88-
)
63+
replaceHoist(currentChunk[i], null, context)
8964
}
65+
9066
// 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
67+
const deleteCount = currentChunk.length - 1
68+
children.splice(currentIndex - currentChunk.length + 1, deleteCount)
69+
return deleteCount
9570
}
9671
}
72+
return 0
73+
}
9774

75+
let i = 0
76+
for (; i < children.length; i++) {
77+
const child = children[i]
78+
const hoisted = getHoistedNode(child)
79+
if (hoisted) {
80+
// presence of hoisted means child must be a plain element Node
81+
const node = child as PlainElementNode
82+
const result = analyzeNode(node)
83+
if (result) {
84+
// node is stringifiable, record state
85+
nc += result[0]
86+
ec += result[1]
87+
currentChunk.push(node)
88+
continue
89+
}
90+
}
91+
// we only reach here if we ran into a node that is not stringifiable
92+
// check if currently analyzed nodes meet criteria for stringification.
93+
// adjust iteration index
94+
i -= stringifyCurrentChunk(i)
9895
// reset state
9996
nc = 0
10097
ec = 0
101-
currentEligibleNodes.length = 0
98+
currentChunk.length = 0
10299
}
100+
// in case the last node was also stringifiable
101+
stringifyCurrentChunk(i)
103102
}
104103

105104
const getHoistedNode = (node: TemplateChildNode) =>
@@ -116,7 +115,7 @@ const isStringifiableAttr = (name: string) => {
116115

117116
const replaceHoist = (
118117
node: PlainElementNode,
119-
replacement: JSChildNode,
118+
replacement: JSChildNode | null,
120119
context: TransformContext
121120
) => {
122121
const hoistToReplace = (node.codegenNode as SimpleExpressionNode).hoisted!

0 commit comments

Comments
 (0)