)
expect(ast.hoists.length).toBe(1)
// should be a normal vnode call
- expect(ast.hoists[0].type).toBe(NodeTypes.VNODE_CALL)
+ expect(ast.hoists[0]!.type).toBe(NodeTypes.VNODE_CALL)
})
test('should work on eligible content (elements with binding > 5)', () => {
`<span class="foo"></span>`,
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
)}</div>`
- )
+ ),
+ '1'
]
})
})
`<span></span>`,
StringifyThresholds.NODE_COUNT
)}</div>`
- )
+ ),
+ '1'
+ ]
+ })
+ })
+
+ test('should work for multiple adjacent nodes', () => {
+ const { ast } = compileWithStringify(
+ `<div>${repeat(
+ `<span class="foo"/>`,
+ StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
+ )}</div>`
+ )
+ // should have 5 hoisted nodes, but the other 4 should be null
+ expect(ast.hoists.length).toBe(5)
+ for (let i = 1; i < 5; i++) {
+ expect(ast.hoists[i]).toBe(null)
+ }
+ // should be optimized now
+ expect(ast.hoists[0]).toMatchObject({
+ type: NodeTypes.JS_CALL_EXPRESSION,
+ callee: CREATE_STATIC,
+ arguments: [
+ JSON.stringify(
+ repeat(
+ `<span class="foo"></span>`,
+ StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
+ )
+ ),
+ '5'
]
})
})
`<span class="foo bar">1 + false</span>`,
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
)}</div>`
- )
+ ),
+ '1'
]
})
})
`<span class="foo>ar">1 + <</span>` + `<span>&</span>`,
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
)}</div>`
- )
+ ),
+ '1'
]
})
})
ExpressionNode,
ElementTypes,
PlainElementNode,
- JSChildNode,
- createSimpleExpression
+ JSChildNode
} from '@vue/compiler-core'
import {
isVoidTag,
export const stringifyStatic: HoistTransform = (children, context) => {
let nc = 0 // current node count
let ec = 0 // current element with binding count
- const currentEligibleNodes: PlainElementNode[] = []
+ const currentChunk: PlainElementNode[] = []
- for (let i = 0; i < children.length; i++) {
- const child = children[i]
- const hoisted = getHoistedNode(child)
- if (hoisted) {
- // presence of hoisted means child must be a plain element Node
- const node = child as PlainElementNode
- const result = analyzeNode(node)
- if (result) {
- // node is stringifiable, record state
- nc += result[0]
- ec += result[1]
- currentEligibleNodes.push(node)
- continue
- }
- }
-
- // we only reach here if we ran into a node that is not stringifiable
- // check if currently analyzed nodes meet criteria for stringification.
+ const stringifyCurrentChunk = (currentIndex: number): number => {
if (
nc >= StringifyThresholds.NODE_COUNT ||
ec >= StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
// combine all currently eligible nodes into a single static vnode call
const staticCall = createCallExpression(context.helper(CREATE_STATIC), [
JSON.stringify(
- currentEligibleNodes
- .map(node => stringifyElement(node, context))
- .join('')
+ currentChunk.map(node => stringifyElement(node, context)).join('')
),
// the 2nd argument indicates the number of DOM nodes this static vnode
// will insert / hydrate
- String(currentEligibleNodes.length)
+ String(currentChunk.length)
])
// replace the first node's hoisted expression with the static vnode call
- replaceHoist(currentEligibleNodes[0], staticCall, context)
+ replaceHoist(currentChunk[0], staticCall, context)
- const n = currentEligibleNodes.length
- if (n > 1) {
- for (let j = 1; j < n; j++) {
+ if (currentChunk.length > 1) {
+ for (let i = 1; i < currentChunk.length; i++) {
// for the merged nodes, set their hoisted expression to null
- replaceHoist(
- currentEligibleNodes[j],
- createSimpleExpression(`null`, false),
- context
- )
+ replaceHoist(currentChunk[i], null, context)
}
+
// also remove merged nodes from children
- const deleteCount = n - 1
- children.splice(i - n + 1, deleteCount)
- // adjust iteration index
- i -= deleteCount
+ const deleteCount = currentChunk.length - 1
+ children.splice(currentIndex - currentChunk.length + 1, deleteCount)
+ return deleteCount
}
}
+ return 0
+ }
+ let i = 0
+ for (; i < children.length; i++) {
+ const child = children[i]
+ const hoisted = getHoistedNode(child)
+ if (hoisted) {
+ // presence of hoisted means child must be a plain element Node
+ const node = child as PlainElementNode
+ const result = analyzeNode(node)
+ if (result) {
+ // node is stringifiable, record state
+ nc += result[0]
+ ec += result[1]
+ currentChunk.push(node)
+ continue
+ }
+ }
+ // we only reach here if we ran into a node that is not stringifiable
+ // check if currently analyzed nodes meet criteria for stringification.
+ // adjust iteration index
+ i -= stringifyCurrentChunk(i)
// reset state
nc = 0
ec = 0
- currentEligibleNodes.length = 0
+ currentChunk.length = 0
}
+ // in case the last node was also stringifiable
+ stringifyCurrentChunk(i)
}
const getHoistedNode = (node: TemplateChildNode) =>
const replaceHoist = (
node: PlainElementNode,
- replacement: JSChildNode,
+ replacement: JSChildNode | null,
context: TransformContext
) => {
const hoistToReplace = (node.codegenNode as SimpleExpressionNode).hoisted!