}"
`;
+exports[`compiler: cacheStatic transform > should hoist props for root with single element excluding comments 1`] = `
+"const _Vue = Vue
+const { createElementVNode: _createElementVNode, createCommentVNode: _createCommentVNode } = _Vue
+
+const _hoisted_1 = { id: "a" }
+
+return function render(_ctx, _cache) {
+ with (_ctx) {
+ const { createCommentVNode: _createCommentVNode, createElementVNode: _createElementVNode, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
+
+ return (_openBlock(), _createElementBlock(_Fragment, null, [
+ _createCommentVNode("comment"),
+ _createElementVNode("div", _hoisted_1, _cache[0] || (_cache[0] = [
+ _createElementVNode("div", { id: "b" }, [
+ _createElementVNode("div", { id: "c" }, [
+ _createElementVNode("div", { id: "d" }, [
+ _createElementVNode("div", { id: "e" }, "hello")
+ ])
+ ])
+ ], -1 /* HOISTED */)
+ ]))
+ ], 2112 /* STABLE_FRAGMENT, DEV_ROOT_FRAGMENT */))
+ }
+}"
+`;
+
exports[`compiler: cacheStatic transform > should hoist v-for children if static 1`] = `
"const _Vue = Vue
const { createElementVNode: _createElementVNode } = _Vue
expect(generate(root).code).toMatchSnapshot()
})
+ test('should hoist props for root with single element excluding comments', () => {
+ // deeply nested div to trigger stringification condition
+ const root = transformWithCache(
+ `<!--comment--><div id="a"><div id="b"><div id="c"><div id="d"><div id="e">hello</div></div></div></div></div>`,
+ )
+ expect(root.cached.length).toBe(1)
+ expect(root.hoists).toMatchObject([createObjectMatcher({ id: 'a' })])
+
+ expect((root.codegenNode as VNodeCall).children).toMatchObject([
+ {
+ type: NodeTypes.COMMENT,
+ content: 'comment',
+ },
+ {
+ type: NodeTypes.ELEMENT,
+ codegenNode: {
+ type: NodeTypes.VNODE_CALL,
+ tag: `"div"`,
+ props: { content: `_hoisted_1` },
+ children: { type: NodeTypes.JS_CACHE_EXPRESSION },
+ },
+ },
+ ])
+ expect(generate(root).code).toMatchSnapshot()
+ })
+
describe('prefixIdentifiers', () => {
test('cache nested static tree with static interpolation', () => {
const root = transformWithCache(
helperNameMap,
} from './runtimeHelpers'
import { isVSlot } from './utils'
-import { cacheStatic, isSingleElementRoot } from './transforms/cacheStatic'
+import { cacheStatic, getSingleElementRoot } from './transforms/cacheStatic'
import type { CompilerCompatOptions } from './compat/compatConfig'
// There are two types of transforms:
const { helper } = context
const { children } = root
if (children.length === 1) {
- const child = children[0]
+ const singleElementRootChild = getSingleElementRoot(root)
// if the single child is an element, turn it into a block.
- if (isSingleElementRoot(root, child) && child.codegenNode) {
+ if (singleElementRootChild && singleElementRootChild.codegenNode) {
// single element root is never hoisted so codegenNode will never be
// SimpleExpressionNode
- const codegenNode = child.codegenNode
+ const codegenNode = singleElementRootChild.codegenNode
if (codegenNode.type === NodeTypes.VNODE_CALL) {
convertToBlock(codegenNode, context)
}
// - single <slot/>, IfNode, ForNode: already blocks.
// - single text node: always patched.
// root codegen falls through via genNode()
- root.codegenNode = child
+ root.codegenNode = children[0]
}
} else if (children.length > 1) {
// root has multiple nodes - return a fragment block.
context,
// Root node is unfortunately non-hoistable due to potential parent
// fallthrough attributes.
- isSingleElementRoot(root, root.children[0]),
+ !!getSingleElementRoot(root),
)
}
-export function isSingleElementRoot(
+export function getSingleElementRoot(
root: RootNode,
- child: TemplateChildNode,
-): child is PlainElementNode | ComponentNode | TemplateNode {
- const { children } = root
- return (
- children.length === 1 &&
- child.type === NodeTypes.ELEMENT &&
- !isSlotOutlet(child)
- )
+): PlainElementNode | ComponentNode | TemplateNode | null {
+ const children = root.children.filter(x => x.type !== NodeTypes.COMMENT)
+ return children.length === 1 &&
+ children[0].type === NodeTypes.ELEMENT &&
+ !isSlotOutlet(children[0])
+ ? children[0]
+ : null
}
function walk(
}"
`;
+exports[`stringify static html > should bail for comments 1`] = `
+"const { createCommentVNode: _createCommentVNode, createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
+
+const _hoisted_1 = { class: "a" }
+
+return function render(_ctx, _cache) {
+ return (_openBlock(), _createElementBlock(_Fragment, null, [
+ _createCommentVNode(" Comment 1 "),
+ _createElementVNode("div", _hoisted_1, [
+ _createCommentVNode(" Comment 2 "),
+ _cache[0] || (_cache[0] = _createStaticVNode("<span class=\\"b\\"></span><span class=\\"b\\"></span><span class=\\"b\\"></span><span class=\\"b\\"></span><span class=\\"b\\"></span>", 5))
+ ])
+ ], 2112 /* STABLE_FRAGMENT, DEV_ROOT_FRAGMENT */))
+}"
+`;
+
exports[`stringify static html > should bail on bindings that are cached but not stringifiable 1`] = `
"const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
expect(code).toMatchSnapshot()
})
+ test('should bail for comments', () => {
+ const { code } = compileWithStringify(
+ `<!-- Comment 1 --><div class="a"><!-- Comment 2 -->${repeat(
+ `<span class="b"/>`,
+ StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
+ )}</div>`,
+ )
+ expect(code).toMatchSnapshot()
+ })
+
test('should bail for <option> elements with null values', () => {
const { ast, code } = compileWithStringify(
`<div><select><option :value="null" />${repeat(