fix(compiler-core): change node hoisting to caching per instance (#11067)

close #5256
close #9219
close #10959
This commit is contained in:
Evan You 2024-06-04 20:09:54 +08:00 committed by GitHub
parent f8eba75d0a
commit cd0ea0d479
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
35 changed files with 1195 additions and 1093 deletions

View File

@ -2,7 +2,7 @@
exports[`compiler: parse > Edge Cases > invalid html 1`] = `
{
"cached": 0,
"cached": [],
"children": [
{
"children": [
@ -86,7 +86,7 @@ exports[`compiler: parse > Edge Cases > invalid html 1`] = `
exports[`compiler: parse > Edge Cases > self closing multiple tag 1`] = `
{
"cached": 0,
"cached": [],
"children": [
{
"children": [],
@ -280,7 +280,7 @@ exports[`compiler: parse > Edge Cases > self closing multiple tag 1`] = `
exports[`compiler: parse > Edge Cases > valid html 1`] = `
{
"cached": 0,
"cached": [],
"children": [
{
"children": [
@ -498,7 +498,7 @@ exports[`compiler: parse > Edge Cases > valid html 1`] = `
exports[`compiler: parse > Errors > CDATA_IN_HTML_CONTENT > <template><![CDATA[cdata]]></template> 1`] = `
{
"cached": 0,
"cached": [],
"children": [
{
"children": [],
@ -550,7 +550,7 @@ exports[`compiler: parse > Errors > CDATA_IN_HTML_CONTENT > <template><![CDATA[c
exports[`compiler: parse > Errors > CDATA_IN_HTML_CONTENT > <template><svg><![CDATA[cdata]]></svg></template> 1`] = `
{
"cached": 0,
"cached": [],
"children": [
{
"children": [
@ -643,7 +643,7 @@ exports[`compiler: parse > Errors > CDATA_IN_HTML_CONTENT > <template><svg><![CD
exports[`compiler: parse > Errors > DUPLICATE_ATTRIBUTE > <template><div id="" id=""></div></template> 1`] = `
{
"cached": 0,
"cached": [],
"children": [
{
"children": [
@ -813,7 +813,7 @@ exports[`compiler: parse > Errors > DUPLICATE_ATTRIBUTE > <template><div id="" i
exports[`compiler: parse > Errors > EOF_BEFORE_TAG_NAME > <template>< 1`] = `
{
"cached": 0,
"cached": [],
"children": [
{
"children": [
@ -883,7 +883,7 @@ exports[`compiler: parse > Errors > EOF_BEFORE_TAG_NAME > <template>< 1`] = `
exports[`compiler: parse > Errors > EOF_BEFORE_TAG_NAME > <template></ 1`] = `
{
"cached": 0,
"cached": [],
"children": [
{
"children": [
@ -953,7 +953,7 @@ exports[`compiler: parse > Errors > EOF_BEFORE_TAG_NAME > <template></ 1`] = `
exports[`compiler: parse > Errors > EOF_IN_CDATA > <template><svg><![CDATA[ 1`] = `
{
"cached": 0,
"cached": [],
"children": [
{
"children": [
@ -1028,7 +1028,7 @@ exports[`compiler: parse > Errors > EOF_IN_CDATA > <template><svg><![CDATA[ 1`]
exports[`compiler: parse > Errors > EOF_IN_CDATA > <template><svg><![CDATA[cdata 1`] = `
{
"cached": 0,
"cached": [],
"children": [
{
"children": [
@ -1121,7 +1121,7 @@ exports[`compiler: parse > Errors > EOF_IN_CDATA > <template><svg><![CDATA[cdata
exports[`compiler: parse > Errors > EOF_IN_COMMENT > <template><!-- 1`] = `
{
"cached": 0,
"cached": [],
"children": [
{
"children": [],
@ -1173,7 +1173,7 @@ exports[`compiler: parse > Errors > EOF_IN_COMMENT > <template><!-- 1`] = `
exports[`compiler: parse > Errors > EOF_IN_COMMENT > <template><!--comment 1`] = `
{
"cached": 0,
"cached": [],
"children": [
{
"children": [
@ -1243,7 +1243,7 @@ exports[`compiler: parse > Errors > EOF_IN_COMMENT > <template><!--comment 1`] =
exports[`compiler: parse > Errors > EOF_IN_TAG > <div></div 1`] = `
{
"cached": 0,
"cached": [],
"children": [
{
"children": [],
@ -1295,7 +1295,7 @@ exports[`compiler: parse > Errors > EOF_IN_TAG > <div></div 1`] = `
exports[`compiler: parse > Errors > EOF_IN_TAG > <template><div 1`] = `
{
"cached": 0,
"cached": [],
"children": [
{
"children": [],
@ -1347,7 +1347,7 @@ exports[`compiler: parse > Errors > EOF_IN_TAG > <template><div 1`] = `
exports[`compiler: parse > Errors > EOF_IN_TAG > <template><div 1`] = `
{
"cached": 0,
"cached": [],
"children": [
{
"children": [],
@ -1399,7 +1399,7 @@ exports[`compiler: parse > Errors > EOF_IN_TAG > <template><div 1`] = `
exports[`compiler: parse > Errors > EOF_IN_TAG > <template><div id 1`] = `
{
"cached": 0,
"cached": [],
"children": [
{
"children": [],
@ -1451,7 +1451,7 @@ exports[`compiler: parse > Errors > EOF_IN_TAG > <template><div id 1`] = `
exports[`compiler: parse > Errors > EOF_IN_TAG > <template><div id = 1`] = `
{
"cached": 0,
"cached": [],
"children": [
{
"children": [],
@ -1503,7 +1503,7 @@ exports[`compiler: parse > Errors > EOF_IN_TAG > <template><div id = 1`] = `
exports[`compiler: parse > Errors > EOF_IN_TAG > <template><div id 1`] = `
{
"cached": 0,
"cached": [],
"children": [
{
"children": [],
@ -1555,7 +1555,7 @@ exports[`compiler: parse > Errors > EOF_IN_TAG > <template><div id 1`] = `
exports[`compiler: parse > Errors > EOF_IN_TAG > <template><div id="abc 1`] = `
{
"cached": 0,
"cached": [],
"children": [
{
"children": [],
@ -1607,7 +1607,7 @@ exports[`compiler: parse > Errors > EOF_IN_TAG > <template><div id="abc 1`] = `
exports[`compiler: parse > Errors > EOF_IN_TAG > <template><div id="abc" 1`] = `
{
"cached": 0,
"cached": [],
"children": [
{
"children": [],
@ -1659,7 +1659,7 @@ exports[`compiler: parse > Errors > EOF_IN_TAG > <template><div id="abc" 1`] = `
exports[`compiler: parse > Errors > EOF_IN_TAG > <template><div id="abc"/ 1`] = `
{
"cached": 0,
"cached": [],
"children": [
{
"children": [
@ -1729,7 +1729,7 @@ exports[`compiler: parse > Errors > EOF_IN_TAG > <template><div id="abc"/ 1`] =
exports[`compiler: parse > Errors > EOF_IN_TAG > <template><div id='abc 1`] = `
{
"cached": 0,
"cached": [],
"children": [
{
"children": [],
@ -1781,7 +1781,7 @@ exports[`compiler: parse > Errors > EOF_IN_TAG > <template><div id='abc 1`] = `
exports[`compiler: parse > Errors > EOF_IN_TAG > <template><div id='abc' 1`] = `
{
"cached": 0,
"cached": [],
"children": [
{
"children": [],
@ -1833,7 +1833,7 @@ exports[`compiler: parse > Errors > EOF_IN_TAG > <template><div id='abc' 1`] = `
exports[`compiler: parse > Errors > EOF_IN_TAG > <template><div id='abc'/ 1`] = `
{
"cached": 0,
"cached": [],
"children": [
{
"children": [
@ -1903,7 +1903,7 @@ exports[`compiler: parse > Errors > EOF_IN_TAG > <template><div id='abc'/ 1`] =
exports[`compiler: parse > Errors > EOF_IN_TAG > <template><div id=abc / 1`] = `
{
"cached": 0,
"cached": [],
"children": [
{
"children": [
@ -1973,7 +1973,7 @@ exports[`compiler: parse > Errors > EOF_IN_TAG > <template><div id=abc / 1`] = `
exports[`compiler: parse > Errors > EOF_IN_TAG > <template><div id=abc 1`] = `
{
"cached": 0,
"cached": [],
"children": [
{
"children": [],
@ -2025,7 +2025,7 @@ exports[`compiler: parse > Errors > EOF_IN_TAG > <template><div id=abc 1`] = `
exports[`compiler: parse > Errors > MISSING_ATTRIBUTE_VALUE > <template><div id= /></div></template> 1`] = `
{
"cached": 0,
"cached": [],
"children": [
{
"children": [
@ -2148,7 +2148,7 @@ exports[`compiler: parse > Errors > MISSING_ATTRIBUTE_VALUE > <template><div id=
exports[`compiler: parse > Errors > MISSING_ATTRIBUTE_VALUE > <template><div id= ></div></template> 1`] = `
{
"cached": 0,
"cached": [],
"children": [
{
"children": [
@ -2271,7 +2271,7 @@ exports[`compiler: parse > Errors > MISSING_ATTRIBUTE_VALUE > <template><div id=
exports[`compiler: parse > Errors > MISSING_ATTRIBUTE_VALUE > <template><div id=></div></template> 1`] = `
{
"cached": 0,
"cached": [],
"children": [
{
"children": [
@ -2394,7 +2394,7 @@ exports[`compiler: parse > Errors > MISSING_ATTRIBUTE_VALUE > <template><div id=
exports[`compiler: parse > Errors > MISSING_END_TAG_NAME > <template></></template> 1`] = `
{
"cached": 0,
"cached": [],
"children": [
{
"children": [],
@ -2446,7 +2446,7 @@ exports[`compiler: parse > Errors > MISSING_END_TAG_NAME > <template></></templa
exports[`compiler: parse > Errors > UNEXPECTED_CHARACTER_IN_ATTRIBUTE_NAME > <template><div a"bc=''></div></template> 1`] = `
{
"cached": 0,
"cached": [],
"children": [
{
"children": [
@ -2569,7 +2569,7 @@ exports[`compiler: parse > Errors > UNEXPECTED_CHARACTER_IN_ATTRIBUTE_NAME > <te
exports[`compiler: parse > Errors > UNEXPECTED_CHARACTER_IN_ATTRIBUTE_NAME > <template><div a'bc=''></div></template> 1`] = `
{
"cached": 0,
"cached": [],
"children": [
{
"children": [
@ -2692,7 +2692,7 @@ exports[`compiler: parse > Errors > UNEXPECTED_CHARACTER_IN_ATTRIBUTE_NAME > <te
exports[`compiler: parse > Errors > UNEXPECTED_CHARACTER_IN_ATTRIBUTE_NAME > <template><div a<bc=''></div></template> 1`] = `
{
"cached": 0,
"cached": [],
"children": [
{
"children": [
@ -2815,7 +2815,7 @@ exports[`compiler: parse > Errors > UNEXPECTED_CHARACTER_IN_ATTRIBUTE_NAME > <te
exports[`compiler: parse > Errors > UNEXPECTED_CHARACTER_IN_UNQUOTED_ATTRIBUTE_VALUE > <template><div foo=bar"></div></template> 1`] = `
{
"cached": 0,
"cached": [],
"children": [
{
"children": [
@ -2938,7 +2938,7 @@ exports[`compiler: parse > Errors > UNEXPECTED_CHARACTER_IN_UNQUOTED_ATTRIBUTE_V
exports[`compiler: parse > Errors > UNEXPECTED_CHARACTER_IN_UNQUOTED_ATTRIBUTE_VALUE > <template><div foo=bar'></div></template> 1`] = `
{
"cached": 0,
"cached": [],
"children": [
{
"children": [
@ -3061,7 +3061,7 @@ exports[`compiler: parse > Errors > UNEXPECTED_CHARACTER_IN_UNQUOTED_ATTRIBUTE_V
exports[`compiler: parse > Errors > UNEXPECTED_CHARACTER_IN_UNQUOTED_ATTRIBUTE_VALUE > <template><div foo=bar<div></div></template> 1`] = `
{
"cached": 0,
"cached": [],
"children": [
{
"children": [
@ -3184,7 +3184,7 @@ exports[`compiler: parse > Errors > UNEXPECTED_CHARACTER_IN_UNQUOTED_ATTRIBUTE_V
exports[`compiler: parse > Errors > UNEXPECTED_CHARACTER_IN_UNQUOTED_ATTRIBUTE_VALUE > <template><div foo=bar=baz></div></template> 1`] = `
{
"cached": 0,
"cached": [],
"children": [
{
"children": [
@ -3307,7 +3307,7 @@ exports[`compiler: parse > Errors > UNEXPECTED_CHARACTER_IN_UNQUOTED_ATTRIBUTE_V
exports[`compiler: parse > Errors > UNEXPECTED_CHARACTER_IN_UNQUOTED_ATTRIBUTE_VALUE > <template><div foo=bar\`></div></template> 1`] = `
{
"cached": 0,
"cached": [],
"children": [
{
"children": [
@ -3430,7 +3430,7 @@ exports[`compiler: parse > Errors > UNEXPECTED_CHARACTER_IN_UNQUOTED_ATTRIBUTE_V
exports[`compiler: parse > Errors > UNEXPECTED_EQUALS_SIGN_BEFORE_ATTRIBUTE_NAME > <template><div =></div></template> 1`] = `
{
"cached": 0,
"cached": [],
"children": [
{
"children": [
@ -3537,7 +3537,7 @@ exports[`compiler: parse > Errors > UNEXPECTED_EQUALS_SIGN_BEFORE_ATTRIBUTE_NAME
exports[`compiler: parse > Errors > UNEXPECTED_EQUALS_SIGN_BEFORE_ATTRIBUTE_NAME > <template><div =foo=bar></div></template> 1`] = `
{
"cached": 0,
"cached": [],
"children": [
{
"children": [
@ -3660,7 +3660,7 @@ exports[`compiler: parse > Errors > UNEXPECTED_EQUALS_SIGN_BEFORE_ATTRIBUTE_NAME
exports[`compiler: parse > Errors > UNEXPECTED_QUESTION_MARK_INSTEAD_OF_TAG_NAME > <template><?xml?></template> 1`] = `
{
"cached": 0,
"cached": [],
"children": [
{
"children": [],
@ -3712,7 +3712,7 @@ exports[`compiler: parse > Errors > UNEXPECTED_QUESTION_MARK_INSTEAD_OF_TAG_NAME
exports[`compiler: parse > Errors > UNEXPECTED_SOLIDUS_IN_TAG > <template><div a/b></div></template> 1`] = `
{
"cached": 0,
"cached": [],
"children": [
{
"children": [
@ -3850,7 +3850,7 @@ exports[`compiler: parse > Errors > UNEXPECTED_SOLIDUS_IN_TAG > <template><div a
exports[`compiler: parse > Errors > X_INVALID_END_TAG > <svg><![CDATA[</div>]]></svg> 1`] = `
{
"cached": 0,
"cached": [],
"children": [
{
"children": [
@ -3920,7 +3920,7 @@ exports[`compiler: parse > Errors > X_INVALID_END_TAG > <svg><![CDATA[</div>]]><
exports[`compiler: parse > Errors > X_INVALID_END_TAG > <svg><!--</div>--></svg> 1`] = `
{
"cached": 0,
"cached": [],
"children": [
{
"children": [
@ -3990,7 +3990,7 @@ exports[`compiler: parse > Errors > X_INVALID_END_TAG > <svg><!--</div>--></svg>
exports[`compiler: parse > Errors > X_INVALID_END_TAG > <template></div></div></template> 1`] = `
{
"cached": 0,
"cached": [],
"children": [
{
"children": [],
@ -4042,7 +4042,7 @@ exports[`compiler: parse > Errors > X_INVALID_END_TAG > <template></div></div></
exports[`compiler: parse > Errors > X_INVALID_END_TAG > <template></div></template> 1`] = `
{
"cached": 0,
"cached": [],
"children": [
{
"children": [],
@ -4094,7 +4094,7 @@ exports[`compiler: parse > Errors > X_INVALID_END_TAG > <template></div></templa
exports[`compiler: parse > Errors > X_INVALID_END_TAG > <template>{{'</div>'}}</template> 1`] = `
{
"cached": 0,
"cached": [],
"children": [
{
"children": [
@ -4182,7 +4182,7 @@ exports[`compiler: parse > Errors > X_INVALID_END_TAG > <template>{{'</div>'}}</
exports[`compiler: parse > Errors > X_INVALID_END_TAG > <template>a </ b</template> 1`] = `
{
"cached": 0,
"cached": [],
"children": [
{
"children": [
@ -4252,7 +4252,7 @@ exports[`compiler: parse > Errors > X_INVALID_END_TAG > <template>a </ b</templa
exports[`compiler: parse > Errors > X_INVALID_END_TAG > <textarea></div></textarea> 1`] = `
{
"cached": 0,
"cached": [],
"children": [
{
"children": [
@ -4322,7 +4322,7 @@ exports[`compiler: parse > Errors > X_INVALID_END_TAG > <textarea></div></textar
exports[`compiler: parse > Errors > X_MISSING_DYNAMIC_DIRECTIVE_ARGUMENT_END > <div v-foo:[sef fsef] /> 1`] = `
{
"cached": 0,
"cached": [],
"children": [
{
"children": [],
@ -4446,7 +4446,7 @@ exports[`compiler: parse > Errors > X_MISSING_DYNAMIC_DIRECTIVE_ARGUMENT_END > <
exports[`compiler: parse > Errors > X_MISSING_END_TAG > <template><div> 1`] = `
{
"cached": 0,
"cached": [],
"children": [
{
"children": [
@ -4521,7 +4521,7 @@ exports[`compiler: parse > Errors > X_MISSING_END_TAG > <template><div> 1`] = `
exports[`compiler: parse > Errors > X_MISSING_END_TAG > <template><div></template> 1`] = `
{
"cached": 0,
"cached": [],
"children": [
{
"children": [
@ -4596,7 +4596,7 @@ exports[`compiler: parse > Errors > X_MISSING_END_TAG > <template><div></templat
exports[`compiler: parse > Errors > X_MISSING_INTERPOLATION_END > <div>{{ foo</div> 1`] = `
{
"cached": 0,
"cached": [],
"children": [
{
"children": [
@ -4666,7 +4666,7 @@ exports[`compiler: parse > Errors > X_MISSING_INTERPOLATION_END > <div>{{ foo</d
exports[`compiler: parse > Errors > X_MISSING_INTERPOLATION_END > {{ 1`] = `
{
"cached": 0,
"cached": [],
"children": [
{
"content": "{{",
@ -4713,7 +4713,7 @@ exports[`compiler: parse > Errors > X_MISSING_INTERPOLATION_END > {{ 1`] = `
exports[`compiler: parse > Errors > X_MISSING_INTERPOLATION_END > {{ foo 1`] = `
{
"cached": 0,
"cached": [],
"children": [
{
"content": "{{ foo",
@ -4760,7 +4760,7 @@ exports[`compiler: parse > Errors > X_MISSING_INTERPOLATION_END > {{ foo 1`] = `
exports[`compiler: parse > Errors > X_MISSING_INTERPOLATION_END > {{}} 1`] = `
{
"cached": 0,
"cached": [],
"children": [
{
"content": {

View File

@ -1,21 +1,5 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`scopeId compiler support > should push scopeId for hoisted nodes 1`] = `
"import { createElementVNode as _createElementVNode, toDisplayString as _toDisplayString, createTextVNode as _createTextVNode, openBlock as _openBlock, createElementBlock as _createElementBlock, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from "vue"
const _withScopeId = n => (_pushScopeId("test"),n=n(),_popScopeId(),n)
const _hoisted_1 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode("div", null, "hello", -1 /* HOISTED */))
const _hoisted_2 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode("div", null, "world", -1 /* HOISTED */))
export function render(_ctx, _cache) {
return (_openBlock(), _createElementBlock("div", null, [
_hoisted_1,
_createTextVNode(_toDisplayString(_ctx.foo), 1 /* TEXT */),
_hoisted_2
]))
}"
`;
exports[`scopeId compiler support > should wrap default slot 1`] = `
"import { createElementVNode as _createElementVNode, resolveComponent as _resolveComponent, withCtx as _withCtx, openBlock as _openBlock, createBlock as _createBlock } from "vue"

View File

@ -47,7 +47,7 @@ function createRoot(options: Partial<RootNode> = {}): RootNode {
directives: [],
imports: [],
hoists: [],
cached: 0,
cached: [],
temps: 0,
codegenNode: createSimpleExpression(`null`, false),
loc: locStub,
@ -422,7 +422,7 @@ describe('compiler: codegen', () => {
test('CacheExpression', () => {
const { code } = generate(
createRoot({
cached: 1,
cached: [],
codegenNode: createCacheExpression(
1,
createSimpleExpression(`foo`, false),
@ -440,7 +440,7 @@ describe('compiler: codegen', () => {
test('CacheExpression w/ isVNode: true', () => {
const { code } = generate(
createRoot({
cached: 1,
cached: [],
codegenNode: createCacheExpression(
1,
createSimpleExpression(`foo`, false),

View File

@ -1,7 +1,4 @@
import { baseCompile } from '../src/compile'
import { POP_SCOPE_ID, PUSH_SCOPE_ID } from '../src/runtimeHelpers'
import { PatchFlags } from '@vue/shared'
import { genFlagText } from './testUtils'
/**
* Ensure all slot functions are wrapped with _withCtx
@ -57,28 +54,4 @@ describe('scopeId compiler support', () => {
expect(code).toMatch(/name: i,\s+fn: _withCtx\(/)
expect(code).toMatchSnapshot()
})
test('should push scopeId for hoisted nodes', () => {
const { ast, code } = baseCompile(
`<div><div>hello</div>{{ foo }}<div>world</div></div>`,
{
mode: 'module',
scopeId: 'test',
hoistStatic: true,
},
)
expect(ast.helpers).toContain(PUSH_SCOPE_ID)
expect(ast.helpers).toContain(POP_SCOPE_ID)
expect(ast.hoists.length).toBe(2)
;[
`const _withScopeId = n => (_pushScopeId("test"),n=n(),_popScopeId(),n)`,
`const _hoisted_1 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode("div", null, "hello", ${genFlagText(
PatchFlags.HOISTED,
)}))`,
`const _hoisted_2 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode("div", null, "world", ${genFlagText(
PatchFlags.HOISTED,
)}))`,
].forEach(c => expect(code).toMatch(c))
expect(code).toMatchSnapshot()
})
})

View File

@ -1,103 +1,87 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`compiler: hoistStatic transform > hoist element with static key 1`] = `
exports[`compiler: cacheStatic transform > cache element with static key 1`] = `
"const _Vue = Vue
const { createElementVNode: _createElementVNode } = _Vue
const _hoisted_1 = /*#__PURE__*/_createElementVNode("div", { key: "foo" }, null, -1 /* HOISTED */)
const _hoisted_2 = [
_hoisted_1
]
return function render(_ctx, _cache) {
with (_ctx) {
const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
return (_openBlock(), _createElementBlock("div", null, _hoisted_2))
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
_createElementVNode("div", { key: "foo" }, null, -1 /* CACHED */)
])))
}
}"
`;
exports[`compiler: hoistStatic transform > hoist nested static tree 1`] = `
exports[`compiler: cacheStatic transform > cache nested children array 1`] = `
"const _Vue = Vue
const { createElementVNode: _createElementVNode } = _Vue
const _hoisted_1 = /*#__PURE__*/_createElementVNode("p", null, [
/*#__PURE__*/_createElementVNode("span"),
/*#__PURE__*/_createElementVNode("span")
], -1 /* HOISTED */)
const _hoisted_2 = [
_hoisted_1
]
return function render(_ctx, _cache) {
with (_ctx) {
const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
return (_openBlock(), _createElementBlock("div", null, _hoisted_2))
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
_createElementVNode("p", null, [
_createElementVNode("span"),
_createElementVNode("span")
], -1 /* CACHED */),
_createElementVNode("p", null, [
_createElementVNode("span"),
_createElementVNode("span")
], -1 /* CACHED */)
])))
}
}"
`;
exports[`compiler: hoistStatic transform > hoist nested static tree with comments 1`] = `
exports[`compiler: cacheStatic transform > cache nested static tree with comments 1`] = `
"const _Vue = Vue
const { createElementVNode: _createElementVNode, createCommentVNode: _createCommentVNode } = _Vue
const _hoisted_1 = /*#__PURE__*/_createElementVNode("div", null, [
/*#__PURE__*/_createCommentVNode("comment")
], -1 /* HOISTED */)
const _hoisted_2 = [
_hoisted_1
]
return function render(_ctx, _cache) {
with (_ctx) {
const { createCommentVNode: _createCommentVNode, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
return (_openBlock(), _createElementBlock("div", null, _hoisted_2))
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
_createElementVNode("div", null, [
_createCommentVNode("comment")
], -1 /* CACHED */)
])))
}
}"
`;
exports[`compiler: hoistStatic transform > hoist siblings with common non-hoistable parent 1`] = `
exports[`compiler: cacheStatic transform > cache siblings including text with common non-hoistable parent 1`] = `
"const _Vue = Vue
const { createElementVNode: _createElementVNode } = _Vue
const _hoisted_1 = /*#__PURE__*/_createElementVNode("span", null, null, -1 /* HOISTED */)
const _hoisted_2 = /*#__PURE__*/_createElementVNode("div", null, null, -1 /* HOISTED */)
const _hoisted_3 = [
_hoisted_1,
_hoisted_2
]
return function render(_ctx, _cache) {
with (_ctx) {
const { createElementVNode: _createElementVNode, createTextVNode: _createTextVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
_createElementVNode("span", null, null, -1 /* CACHED */),
_createTextVNode("foo"),
_createElementVNode("div", null, null, -1 /* CACHED */)
])))
}
}"
`;
exports[`compiler: cacheStatic transform > cache single children array 1`] = `
"const _Vue = Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
return (_openBlock(), _createElementBlock("div", null, _hoisted_3))
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
_createElementVNode("span", { class: "inline" }, "hello", -1 /* CACHED */)
])))
}
}"
`;
exports[`compiler: hoistStatic transform > hoist simple element 1`] = `
"const _Vue = Vue
const { createElementVNode: _createElementVNode } = _Vue
const _hoisted_1 = /*#__PURE__*/_createElementVNode("span", { class: "inline" }, "hello", -1 /* HOISTED */)
const _hoisted_2 = [
_hoisted_1
]
return function render(_ctx, _cache) {
with (_ctx) {
const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
return (_openBlock(), _createElementBlock("div", null, _hoisted_2))
}
}"
`;
exports[`compiler: hoistStatic transform > hoist static props for elements with directives 1`] = `
exports[`compiler: cacheStatic transform > hoist static props for elements with directives 1`] = `
"const _Vue = Vue
const { createElementVNode: _createElementVNode } = _Vue
@ -118,7 +102,7 @@ return function render(_ctx, _cache) {
}"
`;
exports[`compiler: hoistStatic transform > hoist static props for elements with dynamic text children 1`] = `
exports[`compiler: cacheStatic transform > hoist static props for elements with dynamic text children 1`] = `
"const _Vue = Vue
const { createElementVNode: _createElementVNode } = _Vue
@ -135,7 +119,7 @@ return function render(_ctx, _cache) {
}"
`;
exports[`compiler: hoistStatic transform > hoist static props for elements with unhoistable children 1`] = `
exports[`compiler: cacheStatic transform > hoist static props for elements with unhoistable children 1`] = `
"const _Vue = Vue
const { createVNode: _createVNode, createElementVNode: _createElementVNode } = _Vue
@ -156,7 +140,53 @@ return function render(_ctx, _cache) {
}"
`;
exports[`compiler: hoistStatic transform > prefixIdentifiers > hoist class with static object value 1`] = `
exports[`compiler: cacheStatic transform > prefixIdentifiers > cache nested static tree with static interpolation 1`] = `
"const _Vue = Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { toDisplayString: _toDisplayString, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
_createElementVNode("span", null, "foo " + _toDisplayString(1) + " " + _toDisplayString(true), -1 /* CACHED */)
])))
}
}"
`;
exports[`compiler: cacheStatic transform > prefixIdentifiers > cache nested static tree with static prop value 1`] = `
"const _Vue = Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { toDisplayString: _toDisplayString, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
_createElementVNode("span", { foo: 0 }, _toDisplayString(1), -1 /* CACHED */)
])))
}
}"
`;
exports[`compiler: cacheStatic transform > prefixIdentifiers > clone hoisted array children in v-for + HMR mode 1`] = `
"const _Vue = Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, createElementVNode: _createElementVNode } = _Vue
return (_openBlock(), _createElementBlock("div", null, [
(_openBlock(true), _createElementBlock(_Fragment, null, _renderList(1, (i) => {
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
_createElementVNode("span", { class: "hi" }, null, -1 /* CACHED */)
]))]))
}), 256 /* UNKEYED_FRAGMENT */))
]))
}
}"
`;
exports[`compiler: cacheStatic transform > prefixIdentifiers > hoist class with static object value 1`] = `
"const _Vue = Vue
const { createElementVNode: _createElementVNode } = _Vue
@ -175,50 +205,8 @@ return function render(_ctx, _cache) {
}"
`;
exports[`compiler: hoistStatic transform > prefixIdentifiers > hoist nested static tree with static interpolation 1`] = `
exports[`compiler: cacheStatic transform > prefixIdentifiers > should NOT cache SVG with directives 1`] = `
"const _Vue = Vue
const { createElementVNode: _createElementVNode } = _Vue
const _hoisted_1 = /*#__PURE__*/_createElementVNode("span", null, "foo " + /*#__PURE__*/_toDisplayString(1) + " " + /*#__PURE__*/_toDisplayString(true), -1 /* HOISTED */)
const _hoisted_2 = [
_hoisted_1
]
return function render(_ctx, _cache) {
with (_ctx) {
const { toDisplayString: _toDisplayString, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
return (_openBlock(), _createElementBlock("div", null, _hoisted_2))
}
}"
`;
exports[`compiler: hoistStatic transform > prefixIdentifiers > hoist nested static tree with static prop value 1`] = `
"const _Vue = Vue
const { createElementVNode: _createElementVNode } = _Vue
const _hoisted_1 = /*#__PURE__*/_createElementVNode("span", { foo: 0 }, /*#__PURE__*/_toDisplayString(1), -1 /* HOISTED */)
const _hoisted_2 = [
_hoisted_1
]
return function render(_ctx, _cache) {
with (_ctx) {
const { toDisplayString: _toDisplayString, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
return (_openBlock(), _createElementBlock("div", null, _hoisted_2))
}
}"
`;
exports[`compiler: hoistStatic transform > prefixIdentifiers > should NOT hoist SVG with directives 1`] = `
"const _Vue = Vue
const { createElementVNode: _createElementVNode } = _Vue
const _hoisted_1 = /*#__PURE__*/_createElementVNode("path", { d: "M2,3H5.5L12" }, null, -1 /* HOISTED */)
const _hoisted_2 = [
_hoisted_1
]
return function render(_ctx, _cache) {
with (_ctx) {
@ -227,7 +215,9 @@ return function render(_ctx, _cache) {
const _directive_foo = _resolveDirective("foo")
return (_openBlock(), _createElementBlock("div", null, [
_withDirectives((_openBlock(), _createElementBlock("svg", null, _hoisted_2)), [
_withDirectives((_openBlock(), _createElementBlock("svg", null, _cache[0] || (_cache[0] = [
_createElementVNode("path", { d: "M2,3H5.5L12" }, null, -1 /* CACHED */)
]))), [
[_directive_foo]
])
]))
@ -235,7 +225,7 @@ return function render(_ctx, _cache) {
}"
`;
exports[`compiler: hoistStatic transform > prefixIdentifiers > should NOT hoist elements with cached handlers + other bindings 1`] = `
exports[`compiler: cacheStatic transform > prefixIdentifiers > should NOT cache elements with cached handlers + other bindings 1`] = `
"import { normalizeClass as _normalizeClass, createElementVNode as _createElementVNode, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
export function render(_ctx, _cache) {
@ -250,7 +240,7 @@ export function render(_ctx, _cache) {
}"
`;
exports[`compiler: hoistStatic transform > prefixIdentifiers > should NOT hoist elements with cached handlers 1`] = `
exports[`compiler: cacheStatic transform > prefixIdentifiers > should NOT cache elements with cached handlers 1`] = `
"import { createElementVNode as _createElementVNode, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
export function render(_ctx, _cache) {
@ -264,7 +254,7 @@ export function render(_ctx, _cache) {
}"
`;
exports[`compiler: hoistStatic transform > prefixIdentifiers > should NOT hoist expressions that refer scope variables (2) 1`] = `
exports[`compiler: cacheStatic transform > prefixIdentifiers > should NOT cache expressions that refer scope variables (2) 1`] = `
"const _Vue = Vue
return function render(_ctx, _cache) {
@ -282,7 +272,7 @@ return function render(_ctx, _cache) {
}"
`;
exports[`compiler: hoistStatic transform > prefixIdentifiers > should NOT hoist expressions that refer scope variables (v-slot) 1`] = `
exports[`compiler: cacheStatic transform > prefixIdentifiers > should NOT cache expressions that refer scope variables (v-slot) 1`] = `
"const _Vue = Vue
return function render(_ctx, _cache) {
@ -301,7 +291,7 @@ return function render(_ctx, _cache) {
}"
`;
exports[`compiler: hoistStatic transform > prefixIdentifiers > should NOT hoist expressions that refer scope variables 1`] = `
exports[`compiler: cacheStatic transform > prefixIdentifiers > should NOT cache expressions that refer scope variables 1`] = `
"const _Vue = Vue
return function render(_ctx, _cache) {
@ -319,7 +309,7 @@ return function render(_ctx, _cache) {
}"
`;
exports[`compiler: hoistStatic transform > prefixIdentifiers > should NOT hoist keyed template v-for with plain element child 1`] = `
exports[`compiler: cacheStatic transform > prefixIdentifiers > should NOT cache keyed template v-for with plain element child 1`] = `
"const _Vue = Vue
return function render(_ctx, _cache) {
@ -335,7 +325,7 @@ return function render(_ctx, _cache) {
}"
`;
exports[`compiler: hoistStatic transform > should NOT hoist components 1`] = `
exports[`compiler: cacheStatic transform > should NOT cache components 1`] = `
"const _Vue = Vue
return function render(_ctx, _cache) {
@ -351,7 +341,7 @@ return function render(_ctx, _cache) {
}"
`;
exports[`compiler: hoistStatic transform > should NOT hoist element with dynamic key 1`] = `
exports[`compiler: cacheStatic transform > should NOT cache element with dynamic key 1`] = `
"const _Vue = Vue
return function render(_ctx, _cache) {
@ -365,7 +355,7 @@ return function render(_ctx, _cache) {
}"
`;
exports[`compiler: hoistStatic transform > should NOT hoist element with dynamic props (but hoist the props list) 1`] = `
exports[`compiler: cacheStatic transform > should NOT cache element with dynamic props (but hoist the props list) 1`] = `
"const _Vue = Vue
const { createElementVNode: _createElementVNode } = _Vue
@ -382,7 +372,7 @@ return function render(_ctx, _cache) {
}"
`;
exports[`compiler: hoistStatic transform > should NOT hoist element with dynamic ref 1`] = `
exports[`compiler: cacheStatic transform > should NOT cache element with dynamic ref 1`] = `
"const _Vue = Vue
return function render(_ctx, _cache) {
@ -396,42 +386,7 @@ return function render(_ctx, _cache) {
}"
`;
exports[`compiler: hoistStatic transform > should NOT hoist root node 1`] = `
"const _Vue = Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
return (_openBlock(), _createElementBlock("div"))
}
}"
`;
exports[`compiler: hoistStatic transform > should hoist v-for children if static 1`] = `
"const _Vue = Vue
const { createElementVNode: _createElementVNode } = _Vue
const _hoisted_1 = { id: "foo" }
const _hoisted_2 = /*#__PURE__*/_createElementVNode("span", null, null, -1 /* HOISTED */)
const _hoisted_3 = [
_hoisted_2
]
return function render(_ctx, _cache) {
with (_ctx) {
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, createElementVNode: _createElementVNode } = _Vue
return (_openBlock(), _createElementBlock("div", null, [
(_openBlock(true), _createElementBlock(_Fragment, null, _renderList(list, (i) => {
return (_openBlock(), _createElementBlock("div", _hoisted_1, _hoisted_3))
}), 256 /* UNKEYED_FRAGMENT */))
]))
}
}"
`;
exports[`compiler: hoistStatic transform > should hoist v-if props/children if static 1`] = `
exports[`compiler: cacheStatic transform > should cache v-if props/children if static 1`] = `
"const _Vue = Vue
const { createElementVNode: _createElementVNode, createCommentVNode: _createCommentVNode } = _Vue
@ -439,10 +394,6 @@ const _hoisted_1 = {
key: 0,
id: "foo"
}
const _hoisted_2 = /*#__PURE__*/_createElementVNode("span", null, null, -1 /* HOISTED */)
const _hoisted_3 = [
_hoisted_2
]
return function render(_ctx, _cache) {
with (_ctx) {
@ -450,9 +401,32 @@ return function render(_ctx, _cache) {
return (_openBlock(), _createElementBlock("div", null, [
ok
? (_openBlock(), _createElementBlock("div", _hoisted_1, _hoisted_3))
? (_openBlock(), _createElementBlock("div", _hoisted_1, _cache[0] || (_cache[0] = [
_createElementVNode("span", null, null, -1 /* CACHED */)
])))
: _createCommentVNode("v-if", true)
]))
}
}"
`;
exports[`compiler: cacheStatic transform > should hoist v-for children if static 1`] = `
"const _Vue = Vue
const { createElementVNode: _createElementVNode } = _Vue
const _hoisted_1 = { id: "foo" }
return function render(_ctx, _cache) {
with (_ctx) {
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, createElementVNode: _createElementVNode } = _Vue
return (_openBlock(), _createElementBlock("div", null, [
(_openBlock(true), _createElementBlock(_Fragment, null, _renderList(list, (i) => {
return (_openBlock(), _createElementBlock("div", _hoisted_1, _cache[0] || (_cache[0] = [
_createElementVNode("span", null, null, -1 /* CACHED */)
])))
}), 256 /* UNKEYED_FRAGMENT */))
]))
}
}"
`;

View File

@ -25,18 +25,33 @@ import { createObjectMatcher, genFlagText } from '../testUtils'
import { transformText } from '../../src/transforms/transformText'
import { PatchFlags } from '@vue/shared'
const hoistedChildrenArrayMatcher = (startIndex = 1, length = 1) => ({
type: NodeTypes.JS_ARRAY_EXPRESSION,
elements: new Array(length).fill(0).map((_, i) => ({
type: NodeTypes.ELEMENT,
codegenNode: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `_hoisted_${startIndex + i}`,
},
})),
const cachedChildrenArrayMatcher = (
tags: string[],
needArraySpread = false,
) => ({
type: NodeTypes.JS_CACHE_EXPRESSION,
needArraySpread,
value: {
type: NodeTypes.JS_ARRAY_EXPRESSION,
elements: tags.map(tag => {
if (tag === '') {
return {
type: NodeTypes.TEXT_CALL,
}
} else {
return {
type: NodeTypes.ELEMENT,
codegenNode: {
type: NodeTypes.VNODE_CALL,
tag: JSON.stringify(tag),
},
}
}
}),
},
})
function transformWithHoist(template: string, options: CompilerOptions = {}) {
function transformWithCache(template: string, options: CompilerOptions = {}) {
const ast = parse(template)
transform(ast, {
hoistStatic: true,
@ -60,101 +75,253 @@ function transformWithHoist(template: string, options: CompilerOptions = {}) {
return ast
}
describe('compiler: hoistStatic transform', () => {
test('should NOT hoist root node', () => {
describe('compiler: cacheStatic transform', () => {
test('should NOT cache root node', () => {
// if the whole tree is static, the root still needs to be a block
// so that it's patched in optimized mode to skip children
const root = transformWithHoist(`<div/>`)
expect(root.hoists.length).toBe(0)
const root = transformWithCache(`<div/>`)
expect(root.codegenNode).toMatchObject({
type: NodeTypes.VNODE_CALL,
tag: `"div"`,
})
expect(generate(root).code).toMatchSnapshot()
expect(root.cached.length).toBe(0)
})
test('hoist simple element', () => {
const root = transformWithHoist(
test('cache root node children', () => {
// we don't have access to the root codegenNode during the transform
// so we only cache each child individually
const root = transformWithCache(
`<span class="inline">hello</span><span class="inline">hello</span>`,
)
expect(root.codegenNode).toMatchObject({
type: NodeTypes.VNODE_CALL,
children: [
{ codegenNode: { type: NodeTypes.JS_CACHE_EXPRESSION } },
{ codegenNode: { type: NodeTypes.JS_CACHE_EXPRESSION } },
],
})
expect(root.cached.length).toBe(2)
})
test('cache single children array', () => {
const root = transformWithCache(
`<div><span class="inline">hello</span></div>`,
)
expect(root.hoists).toMatchObject([
{
type: NodeTypes.VNODE_CALL,
tag: `"span"`,
props: createObjectMatcher({ class: 'inline' }),
children: {
type: NodeTypes.TEXT,
content: `hello`,
},
},
hoistedChildrenArrayMatcher(),
])
expect(root.codegenNode).toMatchObject({
tag: `"div"`,
props: undefined,
children: { content: `_hoisted_2` },
children: cachedChildrenArrayMatcher(['span']),
})
expect(root.cached.length).toBe(1)
expect(generate(root).code).toMatchSnapshot()
})
test('hoist nested static tree', () => {
const root = transformWithHoist(`<div><p><span/><span/></p></div>`)
expect(root.hoists).toMatchObject([
{
type: NodeTypes.VNODE_CALL,
tag: `"p"`,
props: undefined,
children: [
{ type: NodeTypes.ELEMENT, tag: `span` },
{ type: NodeTypes.ELEMENT, tag: `span` },
],
},
hoistedChildrenArrayMatcher(),
])
test('cache nested children array', () => {
const root = transformWithCache(
`<div><p><span/><span/></p><p><span/><span/></p></div>`,
)
expect((root.codegenNode as VNodeCall).children).toMatchObject(
cachedChildrenArrayMatcher(['p', 'p']),
)
expect(root.cached.length).toBe(1)
expect(generate(root).code).toMatchSnapshot()
})
test('cache nested static tree with comments', () => {
const root = transformWithCache(`<div><div><!--comment--></div></div>`)
expect((root.codegenNode as VNodeCall).children).toMatchObject(
cachedChildrenArrayMatcher(['div']),
)
expect(root.cached.length).toBe(1)
expect(generate(root).code).toMatchSnapshot()
})
test('cache siblings including text with common non-hoistable parent', () => {
const root = transformWithCache(`<div><span/>foo<div/></div>`)
expect((root.codegenNode as VNodeCall).children).toMatchObject(
cachedChildrenArrayMatcher(['span', '', 'div']),
)
expect(root.cached.length).toBe(1)
expect(generate(root).code).toMatchSnapshot()
})
test('cache inside default slot', () => {
const root = transformWithCache(`<Foo>{{x}}<span/></Foo>`)
expect((root.codegenNode as VNodeCall).children).toMatchObject({
content: '_hoisted_2',
properties: [
{
key: { content: 'default' },
value: {
type: NodeTypes.JS_FUNCTION_EXPRESSION,
returns: [
{
type: NodeTypes.TEXT_CALL,
},
// first slot child cached
{
type: NodeTypes.ELEMENT,
codegenNode: {
type: NodeTypes.JS_CACHE_EXPRESSION,
},
},
],
},
},
{
/* _ slot flag */
},
],
})
expect(generate(root).code).toMatchSnapshot()
})
test('hoist nested static tree with comments', () => {
const root = transformWithHoist(`<div><div><!--comment--></div></div>`)
expect(root.hoists).toMatchObject([
{
type: NodeTypes.VNODE_CALL,
tag: `"div"`,
props: undefined,
children: [{ type: NodeTypes.COMMENT, content: `comment` }],
},
hoistedChildrenArrayMatcher(),
])
test('cache default slot as a whole', () => {
const root = transformWithCache(`<Foo><span/><span/></Foo>`)
expect((root.codegenNode as VNodeCall).children).toMatchObject({
content: `_hoisted_2`,
properties: [
{
key: { content: 'default' },
value: {
type: NodeTypes.JS_FUNCTION_EXPRESSION,
returns: {
type: NodeTypes.JS_CACHE_EXPRESSION,
value: {
type: NodeTypes.JS_ARRAY_EXPRESSION,
elements: [
{ type: NodeTypes.ELEMENT },
{ type: NodeTypes.ELEMENT },
],
},
},
},
},
{
/* _ slot flag */
},
],
})
expect(generate(root).code).toMatchSnapshot()
})
test('hoist siblings with common non-hoistable parent', () => {
const root = transformWithHoist(`<div><span/><div/></div>`)
expect(root.hoists).toMatchObject([
{
type: NodeTypes.VNODE_CALL,
tag: `"span"`,
},
{
type: NodeTypes.VNODE_CALL,
tag: `"div"`,
},
hoistedChildrenArrayMatcher(1, 2),
])
test('cache inside named slot', () => {
const root = transformWithCache(
`<Foo><template #foo>{{x}}<span/></template></Foo>`,
)
expect((root.codegenNode as VNodeCall).children).toMatchObject({
content: '_hoisted_3',
properties: [
{
key: { content: 'foo' },
value: {
type: NodeTypes.JS_FUNCTION_EXPRESSION,
returns: [
{
type: NodeTypes.TEXT_CALL,
},
// first slot child cached
{
type: NodeTypes.ELEMENT,
codegenNode: {
type: NodeTypes.JS_CACHE_EXPRESSION,
},
},
],
},
},
{
/* _ slot flag */
},
],
})
expect(generate(root).code).toMatchSnapshot()
})
test('should NOT hoist components', () => {
const root = transformWithHoist(`<div><Comp/></div>`)
expect(root.hoists.length).toBe(0)
test('cache named slot as a whole', () => {
const root = transformWithCache(
`<Foo><template #foo><span/><span/></template></Foo>`,
)
expect((root.codegenNode as VNodeCall).children).toMatchObject({
properties: [
{
key: { content: 'foo' },
value: {
type: NodeTypes.JS_FUNCTION_EXPRESSION,
returns: {
type: NodeTypes.JS_CACHE_EXPRESSION,
value: {
type: NodeTypes.JS_ARRAY_EXPRESSION,
elements: [
{ type: NodeTypes.ELEMENT },
{ type: NodeTypes.ELEMENT },
],
},
},
},
},
{
/* _ slot flag */
},
],
})
})
test('cache dynamically named slot as a whole', () => {
const root = transformWithCache(
`<Foo><template #[foo]><span/><span/></template></Foo>`,
)
expect((root.codegenNode as VNodeCall).children).toMatchObject({
properties: [
{
key: { content: 'foo', isStatic: false },
value: {
type: NodeTypes.JS_FUNCTION_EXPRESSION,
returns: {
type: NodeTypes.JS_CACHE_EXPRESSION,
value: {
type: NodeTypes.JS_ARRAY_EXPRESSION,
elements: [
{ type: NodeTypes.ELEMENT },
{ type: NodeTypes.ELEMENT },
],
},
},
},
},
{
/* _ slot flag */
},
],
})
})
test('cache dynamically named (expression) slot as a whole', () => {
const root = transformWithCache(
`<Foo><template #[foo+1]><span/><span/></template></Foo>`,
{ prefixIdentifiers: true },
)
expect((root.codegenNode as VNodeCall).children).toMatchObject({
properties: [
{
key: { type: NodeTypes.COMPOUND_EXPRESSION },
value: {
type: NodeTypes.JS_FUNCTION_EXPRESSION,
returns: {
type: NodeTypes.JS_CACHE_EXPRESSION,
value: {
type: NodeTypes.JS_ARRAY_EXPRESSION,
elements: [
{ type: NodeTypes.ELEMENT },
{ type: NodeTypes.ELEMENT },
],
},
},
},
},
{
/* _ slot flag */
},
],
})
})
test('should NOT cache components', () => {
const root = transformWithCache(`<div><Comp/></div>`)
expect((root.codegenNode as VNodeCall).children).toMatchObject([
{
type: NodeTypes.ELEMENT,
@ -164,11 +331,12 @@ describe('compiler: hoistStatic transform', () => {
},
},
])
expect(root.cached.length).toBe(0)
expect(generate(root).code).toMatchSnapshot()
})
test('should NOT hoist element with dynamic props (but hoist the props list)', () => {
const root = transformWithHoist(`<div><div :id="foo"/></div>`)
test('should NOT cache element with dynamic props (but hoist the props list)', () => {
const root = transformWithCache(`<div><div :id="foo"/></div>`)
expect(root.hoists.length).toBe(1)
expect((root.codegenNode as VNodeCall).children).toMatchObject([
{
@ -189,31 +357,23 @@ describe('compiler: hoistStatic transform', () => {
},
},
])
expect(root.cached.length).toBe(0)
expect(generate(root).code).toMatchSnapshot()
})
test('hoist element with static key', () => {
const root = transformWithHoist(`<div><div key="foo"/></div>`)
expect(root.hoists.length).toBe(2)
expect(root.hoists).toMatchObject([
{
type: NodeTypes.VNODE_CALL,
tag: `"div"`,
props: createObjectMatcher({ key: 'foo' }),
},
hoistedChildrenArrayMatcher(),
])
test('cache element with static key', () => {
const root = transformWithCache(`<div><div key="foo"/></div>`)
expect(root.codegenNode).toMatchObject({
tag: `"div"`,
props: undefined,
children: { content: `_hoisted_2` },
children: cachedChildrenArrayMatcher(['div']),
})
expect(root.cached.length).toBe(1)
expect(generate(root).code).toMatchSnapshot()
})
test('should NOT hoist element with dynamic key', () => {
const root = transformWithHoist(`<div><div :key="foo"/></div>`)
expect(root.hoists.length).toBe(0)
test('should NOT cache element with dynamic key', () => {
const root = transformWithCache(`<div><div :key="foo"/></div>`)
expect((root.codegenNode as VNodeCall).children).toMatchObject([
{
type: NodeTypes.ELEMENT,
@ -226,12 +386,12 @@ describe('compiler: hoistStatic transform', () => {
},
},
])
expect(root.cached.length).toBe(0)
expect(generate(root).code).toMatchSnapshot()
})
test('should NOT hoist element with dynamic ref', () => {
const root = transformWithHoist(`<div><div :ref="foo"/></div>`)
expect(root.hoists.length).toBe(0)
test('should NOT cache element with dynamic ref', () => {
const root = transformWithCache(`<div><div :ref="foo"/></div>`)
expect((root.codegenNode as VNodeCall).children).toMatchObject([
{
type: NodeTypes.ELEMENT,
@ -246,11 +406,12 @@ describe('compiler: hoistStatic transform', () => {
},
},
])
expect(root.cached.length).toBe(0)
expect(generate(root).code).toMatchSnapshot()
})
test('hoist static props for elements with directives', () => {
const root = transformWithHoist(`<div><div id="foo" v-foo/></div>`)
const root = transformWithCache(`<div><div id="foo" v-foo/></div>`)
expect(root.hoists).toMatchObject([createObjectMatcher({ id: 'foo' })])
expect((root.codegenNode as VNodeCall).children).toMatchObject([
{
@ -270,11 +431,12 @@ describe('compiler: hoistStatic transform', () => {
},
},
])
expect(root.cached.length).toBe(0)
expect(generate(root).code).toMatchSnapshot()
})
test('hoist static props for elements with dynamic text children', () => {
const root = transformWithHoist(
const root = transformWithCache(
`<div><div id="foo">{{ hello }}</div></div>`,
)
expect(root.hoists).toMatchObject([createObjectMatcher({ id: 'foo' })])
@ -290,11 +452,12 @@ describe('compiler: hoistStatic transform', () => {
},
},
])
expect(root.cached.length).toBe(0)
expect(generate(root).code).toMatchSnapshot()
})
test('hoist static props for elements with unhoistable children', () => {
const root = transformWithHoist(`<div><div id="foo"><Comp/></div></div>`)
const root = transformWithCache(`<div><div id="foo"><Comp/></div></div>`)
expect(root.hoists).toMatchObject([createObjectMatcher({ id: 'foo' })])
expect((root.codegenNode as VNodeCall).children).toMatchObject([
{
@ -307,11 +470,12 @@ describe('compiler: hoistStatic transform', () => {
},
},
])
expect(root.cached.length).toBe(0)
expect(generate(root).code).toMatchSnapshot()
})
test('should hoist v-if props/children if static', () => {
const root = transformWithHoist(
test('should cache v-if props/children if static', () => {
const root = transformWithCache(
`<div><div v-if="ok" id="foo"><span/></div></div>`,
)
expect(root.hoists).toMatchObject([
@ -319,40 +483,31 @@ describe('compiler: hoistStatic transform', () => {
key: `[0]`, // key injected by v-if branch
id: 'foo',
}),
{
type: NodeTypes.VNODE_CALL,
tag: `"span"`,
},
hoistedChildrenArrayMatcher(2),
])
expect(
((root.children[0] as ElementNode).children[0] as IfNode).codegenNode,
).toMatchObject({
type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
consequent: {
// blocks should NOT be hoisted
// blocks should NOT be cached
type: NodeTypes.VNODE_CALL,
tag: `"div"`,
props: { content: `_hoisted_1` },
children: { content: `_hoisted_3` },
children: cachedChildrenArrayMatcher(['span']),
},
})
expect(root.cached.length).toBe(1)
expect(generate(root).code).toMatchSnapshot()
})
test('should hoist v-for children if static', () => {
const root = transformWithHoist(
const root = transformWithCache(
`<div><div v-for="i in list" id="foo"><span/></div></div>`,
)
expect(root.hoists).toMatchObject([
createObjectMatcher({
id: 'foo',
}),
{
type: NodeTypes.VNODE_CALL,
tag: `"span"`,
},
hoistedChildrenArrayMatcher(2),
])
const forBlockCodegen = (
(root.children[0] as ElementNode).children[0] as ForNode
@ -372,78 +527,47 @@ describe('compiler: hoistStatic transform', () => {
type: NodeTypes.VNODE_CALL,
tag: `"div"`,
props: { content: `_hoisted_1` },
children: { content: `_hoisted_3` },
children: cachedChildrenArrayMatcher(['span']),
})
expect(root.cached.length).toBe(1)
expect(generate(root).code).toMatchSnapshot()
})
describe('prefixIdentifiers', () => {
test('hoist nested static tree with static interpolation', () => {
const root = transformWithHoist(
test('cache nested static tree with static interpolation', () => {
const root = transformWithCache(
`<div><span>foo {{ 1 }} {{ true }}</span></div>`,
{
prefixIdentifiers: true,
},
)
expect(root.hoists).toMatchObject([
{
type: NodeTypes.VNODE_CALL,
tag: `"span"`,
props: undefined,
children: {
type: NodeTypes.COMPOUND_EXPRESSION,
},
},
hoistedChildrenArrayMatcher(),
])
expect(root.codegenNode).toMatchObject({
tag: `"div"`,
props: undefined,
children: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `_hoisted_2`,
},
children: cachedChildrenArrayMatcher(['span']),
})
expect(root.cached.length).toBe(1)
expect(generate(root).code).toMatchSnapshot()
})
test('hoist nested static tree with static prop value', () => {
const root = transformWithHoist(
test('cache nested static tree with static prop value', () => {
const root = transformWithCache(
`<div><span :foo="0">{{ 1 }}</span></div>`,
{
prefixIdentifiers: true,
},
)
expect(root.hoists).toMatchObject([
{
type: NodeTypes.VNODE_CALL,
tag: `"span"`,
props: createObjectMatcher({ foo: `[0]` }),
children: {
type: NodeTypes.INTERPOLATION,
content: {
content: `1`,
isStatic: false,
constType: ConstantTypes.CAN_STRINGIFY,
},
},
},
hoistedChildrenArrayMatcher(),
])
expect(root.codegenNode).toMatchObject({
tag: `"div"`,
props: undefined,
children: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `_hoisted_2`,
},
children: cachedChildrenArrayMatcher(['span']),
})
expect(root.cached.length).toBe(1)
expect(generate(root).code).toMatchSnapshot()
})
test('hoist class with static object value', () => {
const root = transformWithHoist(
const root = transformWithCache(
`<div><span :class="{ foo: true }">{{ bar }}</span></div>`,
{
prefixIdentifiers: true,
@ -504,44 +628,44 @@ describe('compiler: hoistStatic transform', () => {
expect(generate(root).code).toMatchSnapshot()
})
test('should NOT hoist expressions that refer scope variables', () => {
const root = transformWithHoist(
test('should NOT cache expressions that refer scope variables', () => {
const root = transformWithCache(
`<div><p v-for="o in list"><span>{{ o }}</span></p></div>`,
{
prefixIdentifiers: true,
},
)
expect(root.hoists.length).toBe(0)
expect(root.cached.length).toBe(0)
expect(generate(root).code).toMatchSnapshot()
})
test('should NOT hoist expressions that refer scope variables (2)', () => {
const root = transformWithHoist(
test('should NOT cache expressions that refer scope variables (2)', () => {
const root = transformWithCache(
`<div><p v-for="o in list"><span>{{ o + 'foo' }}</span></p></div>`,
{
prefixIdentifiers: true,
},
)
expect(root.hoists.length).toBe(0)
expect(root.cached.length).toBe(0)
expect(generate(root).code).toMatchSnapshot()
})
test('should NOT hoist expressions that refer scope variables (v-slot)', () => {
const root = transformWithHoist(
test('should NOT cache expressions that refer scope variables (v-slot)', () => {
const root = transformWithCache(
`<Comp v-slot="{ foo }">{{ foo }}</Comp>`,
{
prefixIdentifiers: true,
},
)
expect(root.hoists.length).toBe(0)
expect(root.cached.length).toBe(0)
expect(generate(root).code).toMatchSnapshot()
})
test('should NOT hoist elements with cached handlers', () => {
const root = transformWithHoist(
test('should NOT cache elements with cached handlers', () => {
const root = transformWithCache(
`<div><div><div @click="foo"/></div></div>`,
{
prefixIdentifiers: true,
@ -549,7 +673,7 @@ describe('compiler: hoistStatic transform', () => {
},
)
expect(root.cached).toBe(1)
expect(root.cached.length).toBe(1)
expect(root.hoists.length).toBe(0)
expect(
generate(root, {
@ -559,8 +683,8 @@ describe('compiler: hoistStatic transform', () => {
).toMatchSnapshot()
})
test('should NOT hoist elements with cached handlers + other bindings', () => {
const root = transformWithHoist(
test('should NOT cache elements with cached handlers + other bindings', () => {
const root = transformWithCache(
`<div><div><div :class="{}" @click="foo"/></div></div>`,
{
prefixIdentifiers: true,
@ -568,7 +692,7 @@ describe('compiler: hoistStatic transform', () => {
},
)
expect(root.cached).toBe(1)
expect(root.cached.length).toBe(1)
expect(root.hoists.length).toBe(0)
expect(
generate(root, {
@ -578,32 +702,66 @@ describe('compiler: hoistStatic transform', () => {
).toMatchSnapshot()
})
test('should NOT hoist keyed template v-for with plain element child', () => {
const root = transformWithHoist(
test('should NOT cache keyed template v-for with plain element child', () => {
const root = transformWithCache(
`<div><template v-for="item in items" :key="item"><span/></template></div>`,
)
expect(root.hoists.length).toBe(0)
expect(generate(root).code).toMatchSnapshot()
})
test('should NOT hoist SVG with directives', () => {
const root = transformWithHoist(
test('should NOT cache SVG with directives', () => {
const root = transformWithCache(
`<div><svg v-foo><path d="M2,3H5.5L12"/></svg></div>`,
)
expect(root.hoists.length).toBe(2)
expect(root.cached.length).toBe(1)
expect(root.codegenNode).toMatchObject({
children: [
{
tag: 'svg',
// only cache the children, not the svg tag itself
codegenNode: {
children: {
type: NodeTypes.JS_CACHE_EXPRESSION,
},
},
},
],
})
expect(generate(root).code).toMatchSnapshot()
})
test('clone hoisted array children in HMR mode', () => {
const root = transformWithHoist(`<div><span class="hi"></span></div>`, {
hmr: true,
})
expect(root.hoists.length).toBe(2)
expect(root.codegenNode).toMatchObject({
children: {
content: '[..._hoisted_2]',
test('clone hoisted array children in v-for + HMR mode', () => {
const root = transformWithCache(
`<div><div v-for="i in 1"><span class="hi"></span></div></div>`,
{
hmr: true,
},
)
expect(root.cached.length).toBe(1)
const forBlockCodegen = (
(root.children[0] as ElementNode).children[0] as ForNode
).codegenNode
expect(forBlockCodegen).toMatchObject({
type: NodeTypes.VNODE_CALL,
tag: FRAGMENT,
props: undefined,
children: {
type: NodeTypes.JS_CALL_EXPRESSION,
callee: RENDER_LIST,
},
patchFlag: genFlagText(PatchFlags.UNKEYED_FRAGMENT),
})
const innerBlockCodegen = forBlockCodegen!.children.arguments[1]
expect(innerBlockCodegen.returns).toMatchObject({
type: NodeTypes.VNODE_CALL,
tag: `"div"`,
children: cachedChildrenArrayMatcher(
['span'],
true /* needArraySpread */,
),
})
expect(generate(root).code).toMatchSnapshot()
})
})
})

View File

@ -399,7 +399,7 @@ describe('compiler: transform v-model', () => {
prefixIdentifiers: true,
cacheHandlers: true,
})
expect(root.cached).toBe(1)
expect(root.cached.length).toBe(1)
const codegen = (root.children[0] as PlainElementNode)
.codegenNode as VNodeCall
// should not list cached prop in dynamicProps
@ -417,7 +417,7 @@ describe('compiler: transform v-model', () => {
cacheHandlers: true,
},
)
expect(root.cached).toBe(0)
expect(root.cached.length).toBe(0)
const codegen = (
(root.children[0] as ForNode).children[0] as PlainElementNode
).codegenNode as VNodeCall
@ -433,7 +433,7 @@ describe('compiler: transform v-model', () => {
cacheHandlers: true,
})
expect(root.cached).not.toBe(2)
expect(root.cached).toBe(1)
expect(root.cached.length).toBe(1)
})
test('should mark update handler dynamic if it refers slot scope variables', () => {

View File

@ -504,7 +504,7 @@ describe('compiler: transform v-on', () => {
prefixIdentifiers: true,
cacheHandlers: true,
})
expect(root.cached).toBe(1)
expect(root.cached.length).toBe(1)
const vnodeCall = node.codegenNode as VNodeCall
// should not treat cached handler as dynamicProp, so no flags
expect(vnodeCall.patchFlag).toBeUndefined()
@ -525,7 +525,7 @@ describe('compiler: transform v-on', () => {
prefixIdentifiers: true,
cacheHandlers: true,
})
expect(root.cached).toBe(1)
expect(root.cached.length).toBe(1)
const vnodeCall = node.codegenNode as VNodeCall
// should not treat cached handler as dynamicProp, so no flags
expect(vnodeCall.patchFlag).toBeUndefined()
@ -550,7 +550,7 @@ describe('compiler: transform v-on', () => {
prefixIdentifiers: true,
cacheHandlers: true,
})
expect(root.cached).toBe(1)
expect(root.cached.length).toBe(1)
const vnodeCall = node.codegenNode as VNodeCall
// should not treat cached handler as dynamicProp, so no flags
expect(vnodeCall.patchFlag).toBeUndefined()
@ -587,7 +587,7 @@ describe('compiler: transform v-on', () => {
cacheHandlers: true,
isNativeTag: tag => tag === 'div',
})
expect(root.cached).toBe(0)
expect(root.cached.length).toBe(0)
})
test('should not be cached inside v-once', () => {
@ -599,7 +599,7 @@ describe('compiler: transform v-on', () => {
},
)
expect(root.cached).not.toBe(2)
expect(root.cached).toBe(1)
expect(root.cached.length).toBe(1)
})
test('inline function expression handler', () => {
@ -607,7 +607,7 @@ describe('compiler: transform v-on', () => {
prefixIdentifiers: true,
cacheHandlers: true,
})
expect(root.cached).toBe(1)
expect(root.cached.length).toBe(1)
const vnodeCall = node.codegenNode as VNodeCall
// should not treat cached handler as dynamicProp, so no flags
expect(vnodeCall.patchFlag).toBeUndefined()
@ -631,7 +631,7 @@ describe('compiler: transform v-on', () => {
cacheHandlers: true,
},
)
expect(root.cached).toBe(1)
expect(root.cached.length).toBe(1)
const vnodeCall = node.codegenNode as VNodeCall
// should not treat cached handler as dynamicProp, so no flags
expect(vnodeCall.patchFlag).toBeUndefined()
@ -656,7 +656,7 @@ describe('compiler: transform v-on', () => {
},
)
expect(root.cached).toBe(1)
expect(root.cached.length).toBe(1)
const vnodeCall = node.codegenNode as VNodeCall
// should not treat cached handler as dynamicProp, so no flags
expect(vnodeCall.patchFlag).toBeUndefined()
@ -688,7 +688,7 @@ describe('compiler: transform v-on', () => {
cacheHandlers: true,
},
)
expect(root.cached).toBe(1)
expect(root.cached.length).toBe(1)
const vnodeCall = node.codegenNode as VNodeCall
// should not treat cached handler as dynamicProp, so no flags
expect(vnodeCall.patchFlag).toBeUndefined()
@ -713,8 +713,8 @@ describe('compiler: transform v-on', () => {
prefixIdentifiers: true,
cacheHandlers: true,
})
expect(root.cached).toBe(1)
expect(root.cached).toBe(1)
expect(root.cached.length).toBe(1)
expect(root.cached.length).toBe(1)
const vnodeCall = node.codegenNode as VNodeCall
// should not treat cached handler as dynamicProp, so no flags
expect(vnodeCall.patchFlag).toBeUndefined()

View File

@ -22,7 +22,7 @@ function transformWithOnce(template: string, options: CompilerOptions = {}) {
describe('compiler: v-once transform', () => {
test('as root node', () => {
const root = transformWithOnce(`<div :id="foo" v-once />`)
expect(root.cached).toBe(1)
expect(root.cached.length).toBe(1)
expect(root.helpers).toContain(SET_BLOCK_TRACKING)
expect(root.codegenNode).toMatchObject({
type: NodeTypes.JS_CACHE_EXPRESSION,
@ -37,7 +37,7 @@ describe('compiler: v-once transform', () => {
test('on nested plain element', () => {
const root = transformWithOnce(`<div><div :id="foo" v-once /></div>`)
expect(root.cached).toBe(1)
expect(root.cached.length).toBe(1)
expect(root.helpers).toContain(SET_BLOCK_TRACKING)
expect((root.children[0] as any).children[0].codegenNode).toMatchObject({
type: NodeTypes.JS_CACHE_EXPRESSION,
@ -52,7 +52,7 @@ describe('compiler: v-once transform', () => {
test('on component', () => {
const root = transformWithOnce(`<div><Comp :id="foo" v-once /></div>`)
expect(root.cached).toBe(1)
expect(root.cached.length).toBe(1)
expect(root.helpers).toContain(SET_BLOCK_TRACKING)
expect((root.children[0] as any).children[0].codegenNode).toMatchObject({
type: NodeTypes.JS_CACHE_EXPRESSION,
@ -67,7 +67,7 @@ describe('compiler: v-once transform', () => {
test('on slot outlet', () => {
const root = transformWithOnce(`<div><slot v-once /></div>`)
expect(root.cached).toBe(1)
expect(root.cached.length).toBe(1)
expect(root.helpers).toContain(SET_BLOCK_TRACKING)
expect((root.children[0] as any).children[0].codegenNode).toMatchObject({
type: NodeTypes.JS_CACHE_EXPRESSION,
@ -84,7 +84,7 @@ describe('compiler: v-once transform', () => {
test('inside v-once', () => {
const root = transformWithOnce(`<div v-once><div v-once/></div>`)
expect(root.cached).not.toBe(2)
expect(root.cached).toBe(1)
expect(root.cached.length).toBe(1)
})
// cached nodes should be ignored by hoistStatic transform
@ -92,7 +92,7 @@ describe('compiler: v-once transform', () => {
const root = transformWithOnce(`<div><div v-once /></div>`, {
hoistStatic: true,
})
expect(root.cached).toBe(1)
expect(root.cached.length).toBe(1)
expect(root.helpers).toContain(SET_BLOCK_TRACKING)
expect(root.hoists.length).toBe(0)
expect((root.children[0] as any).children[0].codegenNode).toMatchObject({
@ -108,7 +108,7 @@ describe('compiler: v-once transform', () => {
test('with v-if/else', () => {
const root = transformWithOnce(`<div v-if="BOOLEAN" v-once /><p v-else/>`)
expect(root.cached).toBe(1)
expect(root.cached.length).toBe(1)
expect(root.helpers).toContain(SET_BLOCK_TRACKING)
expect(root.children[0]).toMatchObject({
type: NodeTypes.IF,
@ -132,7 +132,7 @@ describe('compiler: v-once transform', () => {
test('with v-for', () => {
const root = transformWithOnce(`<div v-for="i in list" v-once />`)
expect(root.cached).toBe(1)
expect(root.cached.length).toBe(1)
expect(root.helpers).toContain(SET_BLOCK_TRACKING)
expect(root.children[0]).toMatchObject({
type: NodeTypes.FOR,

View File

@ -110,7 +110,7 @@ export interface RootNode extends Node {
directives: string[]
hoists: (JSChildNode | null)[]
imports: ImportItem[]
cached: number
cached: (CacheExpression | null)[]
temps: number
ssrHelpers?: symbol[]
codegenNode?: TemplateChildNode | JSChildNode | BlockStatement
@ -218,7 +218,7 @@ export interface DirectiveNode extends Node {
export enum ConstantTypes {
NOT_CONSTANT = 0,
CAN_SKIP_PATCH,
CAN_HOIST,
CAN_CACHE,
CAN_STRINGIFY,
}
@ -330,6 +330,7 @@ export interface VNodeCall extends Node {
| SlotsExpression // component slots
| ForRenderListExpression // v-for fragment call
| SimpleExpressionNode // hoisted
| CacheExpression // cached
| undefined
patchFlag: string | undefined
dynamicProps: string | SimpleExpressionNode | undefined
@ -416,7 +417,8 @@ export interface CacheExpression extends Node {
type: NodeTypes.JS_CACHE_EXPRESSION
index: number
value: JSChildNode
isVNode: boolean
needPauseTracking: boolean
needArraySpread: boolean
}
export interface MemoExpression extends CallExpression {
@ -511,7 +513,7 @@ export interface SlotsObjectProperty extends Property {
}
export interface SlotFunctionExpression extends FunctionExpression {
returns: TemplateChildNode[]
returns: TemplateChildNode[] | CacheExpression
}
// createSlots({ ... }, [
@ -598,7 +600,7 @@ export function createRoot(
directives: [],
hoists: [],
imports: [],
cached: 0,
cached: [],
temps: 0,
codegenNode: undefined,
loc: locStub,
@ -771,13 +773,14 @@ export function createConditionalExpression(
export function createCacheExpression(
index: number,
value: JSChildNode,
isVNode: boolean = false,
needPauseTracking: boolean = false,
): CacheExpression {
return {
type: NodeTypes.JS_CACHE_EXPRESSION,
index,
value,
isVNode,
needPauseTracking: needPauseTracking,
needArraySpread: false,
loc: locStub,
}
}

View File

@ -43,8 +43,6 @@ import {
CREATE_TEXT,
CREATE_VNODE,
OPEN_BLOCK,
POP_SCOPE_ID,
PUSH_SCOPE_ID,
RESOLVE_COMPONENT,
RESOLVE_DIRECTIVE,
RESOLVE_FILTER,
@ -473,11 +471,6 @@ function genModulePreamble(
ssrRuntimeModuleName,
} = context
if (genScopeId && ast.hoists.length) {
ast.helpers.add(PUSH_SCOPE_ID)
ast.helpers.add(POP_SCOPE_ID)
}
// generate import statements for helpers
if (ast.helpers.size) {
const helpers = Array.from(ast.helpers)
@ -566,33 +559,14 @@ function genHoists(hoists: (JSChildNode | null)[], context: CodegenContext) {
return
}
context.pure = true
const { push, newline, helper, scopeId, mode } = context
const genScopeId = !__BROWSER__ && scopeId != null && mode !== 'function'
const { push, newline } = context
newline()
// generate inlined withScopeId helper
if (genScopeId) {
push(
`const _withScopeId = n => (${helper(
PUSH_SCOPE_ID,
)}("${scopeId}"),n=n(),${helper(POP_SCOPE_ID)}(),n)`,
)
newline()
}
for (let i = 0; i < hoists.length; i++) {
const exp = hoists[i]
if (exp) {
const needScopeIdWrapper = genScopeId && exp.type === NodeTypes.VNODE_CALL
push(
`const _hoisted_${i + 1} = ${
needScopeIdWrapper ? `${PURE_ANNOTATION} _withScopeId(() => ` : ``
}`,
)
push(`const _hoisted_${i + 1} = `)
genNode(exp, context)
if (needScopeIdWrapper) {
push(`)`)
}
newline()
}
}
@ -1007,15 +981,19 @@ function genConditionalExpression(
function genCacheExpression(node: CacheExpression, context: CodegenContext) {
const { push, helper, indent, deindent, newline } = context
const { needPauseTracking, needArraySpread } = node
if (needArraySpread) {
push(`[...(`)
}
push(`_cache[${node.index}] || (`)
if (node.isVNode) {
if (needPauseTracking) {
indent()
push(`${helper(SET_BLOCK_TRACKING)}(-1),`)
newline()
}
push(`_cache[${node.index}] = `)
genNode(node.value, context)
if (node.isVNode) {
if (needPauseTracking) {
push(`,`)
newline()
push(`${helper(SET_BLOCK_TRACKING)}(1),`)
@ -1024,6 +1002,9 @@ function genCacheExpression(node: CacheExpression, context: CodegenContext) {
deindent()
}
push(`)`)
if (needArraySpread) {
push(`)]`)
}
}
function genTemplateLiteral(node: TemplateLiteral, context: CodegenContext) {

View File

@ -67,7 +67,7 @@ export {
type PropsExpression,
} from './transforms/transformElement'
export { processSlotOutlet } from './transforms/transformSlotOutlet'
export { getConstantType } from './transforms/hoistStatic'
export { getConstantType } from './transforms/cacheStatic'
export { generateCodeFrame } from '@vue/shared'
// v2 compat only

View File

@ -250,7 +250,7 @@ export interface TransformOptions
*/
prefixIdentifiers?: boolean
/**
* Hoist static VNodes and props objects to `_hoisted_x` constants
* Cache static VNodes and props objects to `_hoisted_x` constants
* @default false
*/
hoistStatic?: boolean

View File

@ -32,7 +32,14 @@ export const CAMELIZE = Symbol(__DEV__ ? `camelize` : ``)
export const CAPITALIZE = Symbol(__DEV__ ? `capitalize` : ``)
export const TO_HANDLER_KEY = Symbol(__DEV__ ? `toHandlerKey` : ``)
export const SET_BLOCK_TRACKING = Symbol(__DEV__ ? `setBlockTracking` : ``)
/**
* @deprecated no longer needed in 3.5+ because we no longer hoist element nodes
* but kept for backwards compat
*/
export const PUSH_SCOPE_ID = Symbol(__DEV__ ? `pushScopeId` : ``)
/**
* @deprecated kept for backwards compat
*/
export const POP_SCOPE_ID = Symbol(__DEV__ ? `popScopeId` : ``)
export const WITH_CTX = Symbol(__DEV__ ? `withCtx` : ``)
export const UNREF = Symbol(__DEV__ ? `unref` : ``)

View File

@ -38,7 +38,7 @@ import {
helperNameMap,
} from './runtimeHelpers'
import { isVSlot } from './utils'
import { hoistStatic, isSingleElementRoot } from './transforms/hoistStatic'
import { cacheStatic, isSingleElementRoot } from './transforms/cacheStatic'
import type { CompilerCompatOptions } from './compat/compatConfig'
// There are two types of transforms:
@ -93,7 +93,7 @@ export interface TransformContext
hoists: (JSChildNode | null)[]
imports: ImportItem[]
temps: number
cached: number
cached: (CacheExpression | null)[]
identifiers: { [name: string]: number | undefined }
scopes: {
vFor: number
@ -117,7 +117,7 @@ export interface TransformContext
addIdentifiers(exp: ExpressionNode | string): void
removeIdentifiers(exp: ExpressionNode | string): void
hoist(exp: string | JSChildNode | ArrayExpression): SimpleExpressionNode
cache<T extends JSChildNode>(exp: T, isVNode?: boolean): CacheExpression | T
cache(exp: JSChildNode, isVNode?: boolean): CacheExpression
constantCache: WeakMap<TemplateChildNode, ConstantTypes>
// 2.x Compat only
@ -185,9 +185,9 @@ export function createTransformContext(
directives: new Set(),
hoists: [],
imports: [],
cached: [],
constantCache: new WeakMap(),
temps: 0,
cached: 0,
identifiers: Object.create(null),
scopes: {
vFor: 0,
@ -291,13 +291,19 @@ export function createTransformContext(
`_hoisted_${context.hoists.length}`,
false,
exp.loc,
ConstantTypes.CAN_HOIST,
ConstantTypes.CAN_CACHE,
)
identifier.hoisted = exp
return identifier
},
cache(exp, isVNode = false) {
return createCacheExpression(context.cached++, exp, isVNode)
const cacheExp = createCacheExpression(
context.cached.length,
exp,
isVNode,
)
context.cached.push(cacheExp)
return cacheExp
},
}
@ -324,7 +330,7 @@ export function transform(root: RootNode, options: TransformOptions) {
const context = createTransformContext(root, options)
traverseNode(root, context)
if (options.hoistStatic) {
hoistStatic(root, context)
cacheStatic(root, context)
}
if (!options.ssr) {
createRootCodegen(root, context)

View File

@ -1,16 +1,20 @@
import {
type CacheExpression,
type CallExpression,
type ComponentNode,
ConstantTypes,
ElementTypes,
type ExpressionNode,
type JSChildNode,
NodeTypes,
type ParentNode,
type PlainElementNode,
type RootNode,
type SimpleExpressionNode,
type SlotFunctionExpression,
type TemplateChildNode,
type TemplateNode,
type TextCallNode,
type VNodeCall,
createArrayExpression,
getVNodeBlockHelper,
@ -18,7 +22,7 @@ import {
} from '../ast'
import type { TransformContext } from '../transform'
import { PatchFlags, isArray, isString, isSymbol } from '@vue/shared'
import { isSlotOutlet } from '../utils'
import { findDir, isSlotOutlet } from '../utils'
import {
GUARD_REACTIVE_PROPS,
NORMALIZE_CLASS,
@ -27,9 +31,10 @@ import {
OPEN_BLOCK,
} from '../runtimeHelpers'
export function hoistStatic(root: RootNode, context: TransformContext) {
export function cacheStatic(root: RootNode, context: TransformContext) {
walk(
root,
undefined,
context,
// Root node is unfortunately non-hoistable due to potential parent
// fallthrough attributes.
@ -51,16 +56,16 @@ export function isSingleElementRoot(
function walk(
node: ParentNode,
parent: ParentNode | undefined,
context: TransformContext,
doNotHoistNode: boolean = false,
inFor = false,
) {
const { children } = node
const originalCount = children.length
let hoistedCount = 0
const toCache: (PlainElementNode | TextCallNode)[] = []
for (let i = 0; i < children.length; i++) {
const child = children[i]
// only plain elements & text calls are eligible for hoisting.
// only plain elements & text calls are eligible for caching.
if (
child.type === NodeTypes.ELEMENT &&
child.tagType === ElementTypes.ELEMENT
@ -69,11 +74,10 @@ function walk(
? ConstantTypes.NOT_CONSTANT
: getConstantType(child, context)
if (constantType > ConstantTypes.NOT_CONSTANT) {
if (constantType >= ConstantTypes.CAN_HOIST) {
if (constantType >= ConstantTypes.CAN_CACHE) {
;(child.codegenNode as VNodeCall).patchFlag =
PatchFlags.HOISTED + (__DEV__ ? ` /* HOISTED */` : ``)
child.codegenNode = context.hoist(child.codegenNode!)
hoistedCount++
PatchFlags.CACHED + (__DEV__ ? ` /* CACHED */` : ``)
toCache.push(child)
continue
}
} else {
@ -87,7 +91,7 @@ function walk(
flag === PatchFlags.NEED_PATCH ||
flag === PatchFlags.TEXT) &&
getGeneratedPropsConstantType(child, context) >=
ConstantTypes.CAN_HOIST
ConstantTypes.CAN_CACHE
) {
const props = getNodeProps(child)
if (props) {
@ -99,6 +103,14 @@ function walk(
}
}
}
} else if (child.type === NodeTypes.TEXT_CALL) {
const constantType = doNotHoistNode
? ConstantTypes.NOT_CONSTANT
: getConstantType(child, context)
if (constantType >= ConstantTypes.CAN_CACHE) {
toCache.push(child)
continue
}
}
// walk further
@ -107,54 +119,122 @@ function walk(
if (isComponent) {
context.scopes.vSlot++
}
walk(child, context)
walk(child, node, context, false, inFor)
if (isComponent) {
context.scopes.vSlot--
}
} else if (child.type === NodeTypes.FOR) {
// Do not hoist v-for single child because it has to be a block
walk(child, context, child.children.length === 1)
walk(child, node, context, child.children.length === 1, true)
} else if (child.type === NodeTypes.IF) {
for (let i = 0; i < child.branches.length; i++) {
// Do not hoist v-if single child because it has to be a block
walk(
child.branches[i],
node,
context,
child.branches[i].children.length === 1,
inFor,
)
}
}
}
if (hoistedCount && context.transformHoist) {
context.transformHoist(children, context, node)
let cachedAsArray = false
if (toCache.length === children.length && node.type === NodeTypes.ELEMENT) {
if (
node.tagType === ElementTypes.ELEMENT &&
node.codegenNode &&
node.codegenNode.type === NodeTypes.VNODE_CALL &&
isArray(node.codegenNode.children)
) {
// all children were hoisted - the entire children array is cacheable.
node.codegenNode.children = getCacheExpression(
createArrayExpression(node.codegenNode.children),
)
cachedAsArray = true
} else if (
node.tagType === ElementTypes.COMPONENT &&
node.codegenNode &&
node.codegenNode.type === NodeTypes.VNODE_CALL &&
node.codegenNode.children &&
!isArray(node.codegenNode.children) &&
node.codegenNode.children.type === NodeTypes.JS_OBJECT_EXPRESSION
) {
// default slot
const slot = getSlotNode(node.codegenNode, 'default')
if (slot) {
slot.returns = getCacheExpression(
createArrayExpression(slot.returns as TemplateChildNode[]),
)
cachedAsArray = true
}
} else if (
node.tagType === ElementTypes.TEMPLATE &&
parent &&
parent.type === NodeTypes.ELEMENT &&
parent.tagType === ElementTypes.COMPONENT &&
parent.codegenNode &&
parent.codegenNode.type === NodeTypes.VNODE_CALL &&
parent.codegenNode.children &&
!isArray(parent.codegenNode.children) &&
parent.codegenNode.children.type === NodeTypes.JS_OBJECT_EXPRESSION
) {
// named <template> slot
const slotName = findDir(node, 'slot', true)
const slot =
slotName &&
slotName.arg &&
getSlotNode(parent.codegenNode, slotName.arg)
if (slot) {
slot.returns = getCacheExpression(
createArrayExpression(slot.returns as TemplateChildNode[]),
)
cachedAsArray = true
}
}
}
// all children were hoisted - the entire children array is hoistable.
if (
hoistedCount &&
hoistedCount === originalCount &&
node.type === NodeTypes.ELEMENT &&
node.tagType === ElementTypes.ELEMENT &&
node.codegenNode &&
node.codegenNode.type === NodeTypes.VNODE_CALL &&
isArray(node.codegenNode.children)
) {
const hoisted = context.hoist(
createArrayExpression(node.codegenNode.children),
)
// #6978, #7138, #7114
// a hoisted children array inside v-for can caused HMR errors since
// it might be mutated when mounting the v-for list
if (context.hmr) {
hoisted.content = `[...${hoisted.content}]`
if (!cachedAsArray) {
for (const child of toCache) {
child.codegenNode = context.cache(child.codegenNode!)
}
node.codegenNode.children = hoisted
}
function getCacheExpression(value: JSChildNode): CacheExpression {
const exp = context.cache(value)
// #6978, #7138, #7114
// a cached children array inside v-for can caused HMR errors since
// it might be mutated when mounting the first item
if (inFor && context.hmr) {
exp.needArraySpread = true
}
return exp
}
function getSlotNode(
node: VNodeCall,
name: string | ExpressionNode,
): SlotFunctionExpression | undefined {
if (
node.children &&
!isArray(node.children) &&
node.children.type === NodeTypes.JS_OBJECT_EXPRESSION
) {
const slot = node.children.properties.find(
p => p.key === name || (p.key as SimpleExpressionNode).content === name,
)
return slot && slot.value
}
}
if (toCache.length && context.transformHoist) {
context.transformHoist(children, context, node)
}
}
export function getConstantType(
node: TemplateChildNode | SimpleExpressionNode,
node: TemplateChildNode | SimpleExpressionNode | CacheExpression,
context: TransformContext,
): ConstantTypes {
const { constantCache } = context
@ -284,6 +364,8 @@ export function getConstantType(
}
}
return returnType
case NodeTypes.JS_CACHE_EXPRESSION:
return ConstantTypes.CAN_CACHE
default:
if (__DEV__) {
const exhaustiveCheck: never = node

View File

@ -57,7 +57,7 @@ import {
toValidAssetId,
} from '../utils'
import { buildSlots } from './vSlot'
import { getConstantType } from './hoistStatic'
import { getConstantType } from './cacheStatic'
import { BindingTypes } from '../options'
import {
CompilerDeprecationTypes,

View File

@ -250,7 +250,7 @@ export function processExpression(
if (isLiteral) {
node.constType = ConstantTypes.CAN_STRINGIFY
} else {
node.constType = ConstantTypes.CAN_HOIST
node.constType = ConstantTypes.CAN_CACHE
}
}
return node

View File

@ -11,7 +11,7 @@ import {
import { isText } from '../utils'
import { CREATE_TEXT } from '../runtimeHelpers'
import { PatchFlagNames, PatchFlags } from '@vue/shared'
import { getConstantType } from './hoistStatic'
import { getConstantType } from './cacheStatic'
// Merge adjacent text nodes and expressions into a single expression
// e.g. <div>abc {{ d }} {{ e }}</div> should have a single expression node as child.

View File

@ -224,8 +224,10 @@ export const transformFor = createStructuralDirectiveTransform(
renderExp.arguments.push(
loop as ForIteratorExpression,
createSimpleExpression(`_cache`),
createSimpleExpression(String(context.cached++)),
createSimpleExpression(String(context.cached.length)),
)
// increment cache count
context.cached.push(null)
} else {
renderExp.arguments.push(
createFunctionExpression(

View File

@ -248,7 +248,7 @@ function createChildrenCodegenNode(
`${keyIndex}`,
false,
locStub,
ConstantTypes.CAN_HOIST,
ConstantTypes.CAN_CACHE,
),
)
const { children } = branch

View File

@ -33,8 +33,10 @@ export const transformMemo: NodeTransform = (node, context) => {
dir.exp!,
createFunctionExpression(undefined, codegenNode),
`_cache`,
String(context.cached++),
String(context.cached.length),
]) as MemoExpression
// increment cache count
context.cached.push(null)
}
}
}

View File

@ -148,7 +148,7 @@ export const transformModel: DirectiveTransform = (dir, node, context) => {
`{ ${modifiers} }`,
false,
dir.loc,
ConstantTypes.CAN_HOIST,
ConstantTypes.CAN_CACHE,
),
),
)

View File

@ -1,5 +1,6 @@
import {
type BlockCodegenNode,
type CacheExpression,
type CallExpression,
type DirectiveNode,
type ElementNode,
@ -438,7 +439,12 @@ export function toValidAssetId(
// Check if a node contains expressions that reference current context scope ids
export function hasScopeRef(
node: TemplateChildNode | IfBranchNode | ExpressionNode | undefined,
node:
| TemplateChildNode
| IfBranchNode
| ExpressionNode
| CacheExpression
| undefined,
ids: TransformContext['identifiers'],
): boolean {
if (!node || Object.keys(ids).length === 0) {
@ -481,6 +487,7 @@ export function hasScopeRef(
return hasScopeRef(node.content, ids)
case NodeTypes.TEXT:
case NodeTypes.COMMENT:
case NodeTypes.JS_CACHE_EXPRESSION:
return false
default:
if (__DEV__) {

View File

@ -1,96 +1,128 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`stringify static html > escape 1`] = `
"const { toDisplayString: _toDisplayString, normalizeClass: _normalizeClass, createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
return function render(_ctx, _cache) {
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
_createStaticVNode("<div><span class=\\"foo&gt;ar\\">1 + &lt;</span><span>&amp;</span><span class=\\"foo&gt;ar\\">1 + &lt;</span><span>&amp;</span><span class=\\"foo&gt;ar\\">1 + &lt;</span><span>&amp;</span><span class=\\"foo&gt;ar\\">1 + &lt;</span><span>&amp;</span><span class=\\"foo&gt;ar\\">1 + &lt;</span><span>&amp;</span></div>", 1)
])))
}"
`;
exports[`stringify static html > serializing constant bindings 1`] = `
"const { toDisplayString: _toDisplayString, normalizeClass: _normalizeClass, createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
return function render(_ctx, _cache) {
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
_createStaticVNode("<div style=\\"color:red;\\"><span class=\\"foo bar\\">1 + false</span><span class=\\"foo bar\\">1 + false</span><span class=\\"foo bar\\">1 + false</span><span class=\\"foo bar\\">1 + false</span><span class=\\"foo bar\\">1 + false</span></div>", 1)
])))
}"
`;
exports[`stringify static html > should bail for <option> elements with number values 1`] = `
"const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
const _hoisted_1 = /*#__PURE__*/_createElementVNode("select", null, [
/*#__PURE__*/_createElementVNode("option", { value: 1 }),
/*#__PURE__*/_createElementVNode("option", { value: 1 }),
/*#__PURE__*/_createElementVNode("option", { value: 1 }),
/*#__PURE__*/_createElementVNode("option", { value: 1 }),
/*#__PURE__*/_createElementVNode("option", { value: 1 })
], -1 /* HOISTED */)
const _hoisted_2 = [
_hoisted_1
]
return function render(_ctx, _cache) {
return (_openBlock(), _createElementBlock("div", null, _hoisted_2))
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
_createElementVNode("select", null, [
_createElementVNode("option", { value: 1 }),
_createElementVNode("option", { value: 1 }),
_createElementVNode("option", { value: 1 }),
_createElementVNode("option", { value: 1 }),
_createElementVNode("option", { value: 1 })
], -1 /* CACHED */)
])))
}"
`;
exports[`stringify static html > should bail on bindings that are hoisted but not stringifiable 1`] = `
exports[`stringify static html > should bail on bindings that are cached but not stringifiable 1`] = `
"const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
const _hoisted_1 = /*#__PURE__*/_createElementVNode("div", null, [
/*#__PURE__*/_createElementVNode("span", { class: "foo" }, "foo"),
/*#__PURE__*/_createElementVNode("span", { class: "foo" }, "foo"),
/*#__PURE__*/_createElementVNode("span", { class: "foo" }, "foo"),
/*#__PURE__*/_createElementVNode("span", { class: "foo" }, "foo"),
/*#__PURE__*/_createElementVNode("span", { class: "foo" }, "foo"),
/*#__PURE__*/_createElementVNode("img", { src: _imports_0_ })
], -1 /* HOISTED */)
const _hoisted_2 = [
_hoisted_1
]
return function render(_ctx, _cache) {
return (_openBlock(), _createElementBlock("div", null, _hoisted_2))
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
_createElementVNode("div", null, [
_createElementVNode("span", { class: "foo" }, "foo"),
_createElementVNode("span", { class: "foo" }, "foo"),
_createElementVNode("span", { class: "foo" }, "foo"),
_createElementVNode("span", { class: "foo" }, "foo"),
_createElementVNode("span", { class: "foo" }, "foo"),
_createElementVNode("img", { src: _imports_0_ })
], -1 /* CACHED */)
])))
}"
`;
exports[`stringify static html > should work for <option> elements with string values 1`] = `
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
const _hoisted_1 = /*#__PURE__*/_createStaticVNode("<select><option value=\\"1\\"></option><option value=\\"1\\"></option><option value=\\"1\\"></option><option value=\\"1\\"></option><option value=\\"1\\"></option></select>", 1)
const _hoisted_2 = [
_hoisted_1
]
return function render(_ctx, _cache) {
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
_createStaticVNode("<select><option value=\\"1\\"></option><option value=\\"1\\"></option><option value=\\"1\\"></option><option value=\\"1\\"></option><option value=\\"1\\"></option></select>", 1)
])))
}"
`;
exports[`stringify static html > should work for multiple adjacent nodes 1`] = `
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
return function render(_ctx, _cache) {
return (_openBlock(), _createElementBlock("div", null, _hoisted_2))
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
_createStaticVNode("<span class=\\"foo\\"></span><span class=\\"foo\\"></span><span class=\\"foo\\"></span><span class=\\"foo\\"></span><span class=\\"foo\\"></span>", 5)
])))
}"
`;
exports[`stringify static html > should work on eligible content (elements > 20) 1`] = `
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
return function render(_ctx, _cache) {
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
_createStaticVNode("<div><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></div>", 1)
])))
}"
`;
exports[`stringify static html > should work on eligible content (elements with binding > 5) 1`] = `
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
return function render(_ctx, _cache) {
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
_createStaticVNode("<div><span class=\\"foo\\"></span><span class=\\"foo\\"></span><span class=\\"foo\\"></span><span class=\\"foo\\"></span><span class=\\"foo\\"></span></div>", 1)
])))
}"
`;
exports[`stringify static html > should work with bindings that are non-static but stringifiable 1`] = `
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
const _hoisted_1 = /*#__PURE__*/_createStaticVNode("<div><span class=\\"foo\\">foo</span><span class=\\"foo\\">foo</span><span class=\\"foo\\">foo</span><span class=\\"foo\\">foo</span><span class=\\"foo\\">foo</span><img src=\\"" + _imports_0_ + "\\"></div>", 1)
const _hoisted_2 = [
_hoisted_1
]
return function render(_ctx, _cache) {
return (_openBlock(), _createElementBlock("div", null, _hoisted_2))
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
_createStaticVNode("<div><span class=\\"foo\\">foo</span><span class=\\"foo\\">foo</span><span class=\\"foo\\">foo</span><span class=\\"foo\\">foo</span><span class=\\"foo\\">foo</span><img src=\\"" + _imports_0_ + "\\"></div>", 1)
])))
}"
`;
exports[`stringify static html > stringify v-html 1`] = `
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode } = Vue
const _hoisted_1 = /*#__PURE__*/_createStaticVNode("<pre data-type=\\"js\\"><code><span>show-it </span></code></pre><div class><span class>1</span><span class>2</span></div>", 2)
return function render(_ctx, _cache) {
return _hoisted_1
return _cache[0] || (_cache[0] = _createStaticVNode("<pre data-type=\\"js\\"><code><span>show-it </span></code></pre><div class><span class>1</span><span class>2</span></div>", 2))
}"
`;
exports[`stringify static html > stringify v-text 1`] = `
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode } = Vue
const _hoisted_1 = /*#__PURE__*/_createStaticVNode("<pre data-type=\\"js\\"><code>&lt;span&gt;show-it &lt;/span&gt;</code></pre><div class><span class>1</span><span class>2</span></div>", 2)
return function render(_ctx, _cache) {
return _hoisted_1
return _cache[0] || (_cache[0] = _createStaticVNode("<pre data-type=\\"js\\"><code>&lt;span&gt;show-it &lt;/span&gt;</code></pre><div class><span class>1</span><span class>2</span></div>", 2))
}"
`;
exports[`stringify static html > stringify v-text with escape 1`] = `
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode } = Vue
const _hoisted_1 = /*#__PURE__*/_createStaticVNode("<pre data-type=\\"js\\"><code>text1</code></pre><div class><span class>1</span><span class>2</span></div>", 2)
return function render(_ctx, _cache) {
return _hoisted_1
return _cache[0] || (_cache[0] = _createStaticVNode("<pre data-type=\\"js\\"><code>text1</code></pre><div class><span class>1</span><span class>2</span></div>", 2))
}"
`;

View File

@ -23,134 +23,147 @@ describe('stringify static html', () => {
return code.repeat(n)
}
/**
* Assert cached node NOT stringified
*/
function cachedArrayBailedMatcher(n = 1) {
return {
type: NodeTypes.JS_CACHE_EXPRESSION,
value: {
type: NodeTypes.JS_ARRAY_EXPRESSION,
elements: new Array(n).fill(0).map(() => ({
// should remain VNODE_CALL instead of JS_CALL_EXPRESSION
codegenNode: { type: NodeTypes.VNODE_CALL },
})),
},
}
}
/**
* Assert cached node is stringified (no content check)
*/
function cachedArraySuccessMatcher(n = 1) {
return {
type: NodeTypes.JS_CACHE_EXPRESSION,
value: {
type: NodeTypes.JS_ARRAY_EXPRESSION,
elements: new Array(n).fill(0).map(() => ({
type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_STATIC,
})),
},
}
}
/**
* Assert cached node stringified with desired content and node count
*/
function cachedArrayStaticNodeMatcher(content: string, count: number) {
return {
type: NodeTypes.JS_CACHE_EXPRESSION,
value: {
type: NodeTypes.JS_ARRAY_EXPRESSION,
elements: [
{
type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_STATIC,
arguments: [JSON.stringify(content), String(count)],
},
],
},
}
}
test('should bail on non-eligible static trees', () => {
const { ast } = compileWithStringify(
`<div><div><div>hello</div><div>hello</div></div></div>`,
)
// should be a normal vnode call
expect(ast.hoists[0]!.type).toBe(NodeTypes.VNODE_CALL)
// should be cached children array
expect(ast.cached[0]!.value.type).toBe(NodeTypes.JS_ARRAY_EXPRESSION)
})
test('should work on eligible content (elements with binding > 5)', () => {
const { ast } = compileWithStringify(
const { code, ast } = compileWithStringify(
`<div><div>${repeat(
`<span class="foo"/>`,
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
)}</div></div>`,
)
// should be optimized now
expect(ast.hoists).toMatchObject([
{
type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_STATIC,
arguments: [
JSON.stringify(
`<div>${repeat(
`<span class="foo"></span>`,
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
)}</div>`,
),
'1',
],
}, // the children array is hoisted as well
{
type: NodeTypes.JS_ARRAY_EXPRESSION,
},
expect(ast.cached).toMatchObject([
cachedArrayStaticNodeMatcher(
`<div>${repeat(
`<span class="foo"></span>`,
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
)}</div>`,
1,
),
])
expect(code).toMatchSnapshot()
})
test('should work on eligible content (elements > 20)', () => {
const { ast } = compileWithStringify(
const { code, ast } = compileWithStringify(
`<div><div>${repeat(
`<span/>`,
StringifyThresholds.NODE_COUNT,
)}</div></div>`,
)
// should be optimized now
expect(ast.hoists).toMatchObject([
{
type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_STATIC,
arguments: [
JSON.stringify(
`<div>${repeat(
`<span></span>`,
StringifyThresholds.NODE_COUNT,
)}</div>`,
),
'1',
],
},
// the children array is hoisted as well
{
type: NodeTypes.JS_ARRAY_EXPRESSION,
},
expect(ast.cached).toMatchObject([
cachedArrayStaticNodeMatcher(
`<div>${repeat(`<span></span>`, StringifyThresholds.NODE_COUNT)}</div>`,
1,
),
])
expect(code).toMatchSnapshot()
})
test('should work for multiple adjacent nodes', () => {
const { ast } = compileWithStringify(
const { ast, code } = compileWithStringify(
`<div>${repeat(
`<span class="foo"/>`,
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
)}</div>`,
)
// should have 6 hoisted nodes (including the entire array),
// but 2~5 should be null because they are merged into 1
expect(ast.hoists).toMatchObject([
{
type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_STATIC,
arguments: [
JSON.stringify(
repeat(
`<span class="foo"></span>`,
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
),
),
'5',
],
},
null,
null,
null,
null,
{
type: NodeTypes.JS_ARRAY_EXPRESSION,
},
expect(ast.cached).toMatchObject([
cachedArrayStaticNodeMatcher(
repeat(
`<span class="foo"></span>`,
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
),
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
),
])
expect(code).toMatchSnapshot()
})
test('serializing constant bindings', () => {
const { ast } = compileWithStringify(
const { ast, code } = compileWithStringify(
`<div><div :style="{ color: 'red' }">${repeat(
`<span :class="[{ foo: true }, { bar: true }]">{{ 1 }} + {{ false }}</span>`,
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
)}</div></div>`,
)
// should be optimized now
expect(ast.hoists).toMatchObject([
{
type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_STATIC,
arguments: [
JSON.stringify(
`<div style="color:red;">${repeat(
`<span class="foo bar">1 + false</span>`,
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
)}</div>`,
),
'1',
],
},
{
type: NodeTypes.JS_ARRAY_EXPRESSION,
},
expect(ast.cached).toMatchObject([
cachedArrayStaticNodeMatcher(
`<div style="color:red;">${repeat(
`<span class="foo bar">1 + false</span>`,
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
)}</div>`,
1,
),
])
expect(code).toMatchSnapshot()
})
test('escape', () => {
const { ast } = compileWithStringify(
const { ast, code } = compileWithStringify(
`<div><div>${repeat(
`<span :class="'foo' + '&gt;ar'">{{ 1 }} + {{ '<' }}</span>` +
`<span>&amp;</span>`,
@ -158,27 +171,19 @@ describe('stringify static html', () => {
)}</div></div>`,
)
// should be optimized now
expect(ast.hoists).toMatchObject([
{
type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_STATIC,
arguments: [
JSON.stringify(
`<div>${repeat(
`<span class="foo&gt;ar">1 + &lt;</span>` + `<span>&amp;</span>`,
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
)}</div>`,
),
'1',
],
},
{
type: NodeTypes.JS_ARRAY_EXPRESSION,
},
expect(ast.cached).toMatchObject([
cachedArrayStaticNodeMatcher(
`<div>${repeat(
`<span class="foo&gt;ar">1 + &lt;</span>` + `<span>&amp;</span>`,
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
)}</div>`,
1,
),
])
expect(code).toMatchSnapshot()
})
test('should bail on bindings that are hoisted but not stringifiable', () => {
test('should bail on bindings that are cached but not stringifiable', () => {
const { ast, code } = compile(
`<div><div>${repeat(
`<span class="foo">foo</span>`,
@ -195,7 +200,7 @@ describe('stringify static html', () => {
'_imports_0_',
false,
node.loc,
ConstantTypes.CAN_HOIST,
ConstantTypes.CAN_CACHE,
)
node.props[0] = {
type: NodeTypes.DIRECTIVE,
@ -210,17 +215,7 @@ describe('stringify static html', () => {
],
},
)
expect(ast.hoists).toMatchObject([
{
// the expression and the tree are still hoistable
// but should stay NodeTypes.VNODE_CALL
// if it's stringified it will be NodeTypes.JS_CALL_EXPRESSION
type: NodeTypes.VNODE_CALL,
},
{
type: NodeTypes.JS_ARRAY_EXPRESSION,
},
])
expect(ast.cached).toMatchObject([cachedArrayBailedMatcher()])
expect(code).toMatchSnapshot()
})
@ -258,35 +253,19 @@ describe('stringify static html', () => {
],
},
)
expect(ast.hoists).toMatchObject([
{
// the hoisted node should be NodeTypes.JS_CALL_EXPRESSION
// of `createStaticVNode()` instead of dynamic NodeTypes.VNODE_CALL
type: NodeTypes.JS_CALL_EXPRESSION,
},
{
type: NodeTypes.JS_ARRAY_EXPRESSION,
},
])
expect(ast.cached).toMatchObject([cachedArraySuccessMatcher()])
expect(code).toMatchSnapshot()
})
// #1128
test('should bail on non attribute bindings', () => {
test('should bail on non-attribute bindings', () => {
const { ast } = compileWithStringify(
`<div><div><input indeterminate>${repeat(
`<span class="foo">foo</span>`,
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
)}</div></div>`,
)
expect(ast.hoists).toMatchObject([
{
type: NodeTypes.VNODE_CALL, // not CALL_EXPRESSION
},
{
type: NodeTypes.JS_ARRAY_EXPRESSION,
},
])
expect(ast.cached).toMatchObject([cachedArrayBailedMatcher()])
const { ast: ast2 } = compileWithStringify(
`<div><div><input :indeterminate="true">${repeat(
@ -294,46 +273,23 @@ describe('stringify static html', () => {
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
)}</div></div>`,
)
expect(ast2.hoists).toMatchObject([
{
type: NodeTypes.VNODE_CALL, // not CALL_EXPRESSION
},
{
type: NodeTypes.JS_ARRAY_EXPRESSION,
},
])
})
expect(ast2.cached).toMatchObject([cachedArrayBailedMatcher()])
test('should bail on non attribute bindings', () => {
const { ast } = compileWithStringify(
const { ast: ast3 } = compileWithStringify(
`<div><div>${repeat(
`<span class="foo">foo</span>`,
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
)}<input indeterminate></div></div>`,
)
expect(ast.hoists).toMatchObject([
{
type: NodeTypes.VNODE_CALL, // not CALL_EXPRESSION
},
{
type: NodeTypes.JS_ARRAY_EXPRESSION,
},
])
expect(ast3.cached).toMatchObject([cachedArrayBailedMatcher()])
const { ast: ast2 } = compileWithStringify(
const { ast: ast4 } = compileWithStringify(
`<div><div>${repeat(
`<span class="foo">foo</span>`,
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
)}<input :indeterminate="true"></div></div>`,
)
expect(ast2.hoists).toMatchObject([
{
type: NodeTypes.VNODE_CALL, // not CALL_EXPRESSION
},
{
type: NodeTypes.JS_ARRAY_EXPRESSION,
},
])
expect(ast4.cached).toMatchObject([cachedArrayBailedMatcher()])
})
test('should bail on tags that has placement constraints (eg.tables related tags)', () => {
@ -343,14 +299,7 @@ describe('stringify static html', () => {
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
)}</tbody></table>`,
)
expect(ast.hoists).toMatchObject([
{
type: NodeTypes.VNODE_CALL, // not CALL_EXPRESSION
},
{
type: NodeTypes.JS_ARRAY_EXPRESSION,
},
])
expect(ast.cached).toMatchObject([cachedArrayBailedMatcher()])
})
test('should bail inside slots', () => {
@ -360,14 +309,9 @@ describe('stringify static html', () => {
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
)}</foo>`,
)
expect(ast.hoists.length).toBe(
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
)
ast.hoists.forEach(node => {
expect(node).toMatchObject({
type: NodeTypes.VNODE_CALL, // not CALL_EXPRESSION
})
})
expect(ast.cached).toMatchObject([
cachedArrayBailedMatcher(StringifyThresholds.ELEMENT_WITH_BINDING_COUNT),
])
const { ast: ast2 } = compileWithStringify(
`<foo><template #foo>${repeat(
@ -375,14 +319,9 @@ describe('stringify static html', () => {
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
)}</template></foo>`,
)
expect(ast2.hoists.length).toBe(
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
)
ast2.hoists.forEach(node => {
expect(node).toMatchObject({
type: NodeTypes.VNODE_CALL, // not CALL_EXPRESSION
})
})
expect(ast2.cached).toMatchObject([
cachedArrayBailedMatcher(StringifyThresholds.ELEMENT_WITH_BINDING_COUNT),
])
})
test('should remove attribute for `null`', () => {
@ -392,19 +331,13 @@ describe('stringify static html', () => {
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
)}</div>`,
)
expect(ast.hoists[0]).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_STATIC,
arguments: [
JSON.stringify(
`${repeat(
`<span></span>`,
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
)}`,
),
'5',
],
})
expect(ast.cached).toMatchObject([
cachedArrayStaticNodeMatcher(
repeat(`<span></span>`, StringifyThresholds.ELEMENT_WITH_BINDING_COUNT),
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
),
])
})
// #6617
@ -415,19 +348,24 @@ describe('stringify static html', () => {
StringifyThresholds.NODE_COUNT,
)}`,
)
expect(ast.hoists[0]).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_STATIC,
arguments: [
JSON.stringify(
`<button>enable</button>${repeat(
`<div></div>`,
StringifyThresholds.NODE_COUNT,
)}`,
),
'21',
],
})
expect(ast.cached).toMatchObject([
{
type: NodeTypes.JS_CACHE_EXPRESSION,
value: {
type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_STATIC,
arguments: [
JSON.stringify(
`<button>enable</button>${repeat(
`<div></div>`,
StringifyThresholds.NODE_COUNT,
)}`,
),
'21',
],
},
},
])
})
test('should stringify svg', () => {
@ -439,19 +377,16 @@ describe('stringify static html', () => {
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
)}</svg></div>`,
)
expect(ast.hoists[0]).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_STATIC,
arguments: [
JSON.stringify(
`${svg}${repeat(
repeated,
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
)}</svg>`,
),
'1',
],
})
expect(ast.cached).toMatchObject([
cachedArrayStaticNodeMatcher(
`${svg}${repeat(
repeated,
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
)}</svg>`,
1,
),
])
})
// #5439
@ -494,23 +429,14 @@ describe('stringify static html', () => {
)}</select></div>`,
)
// should be optimized now
expect(ast.hoists).toMatchObject([
{
type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_STATIC,
arguments: [
JSON.stringify(
`<select>${repeat(
`<option value="1"></option>`,
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
)}</select>`,
),
'1',
],
},
{
type: NodeTypes.JS_ARRAY_EXPRESSION,
},
expect(ast.cached).toMatchObject([
cachedArrayStaticNodeMatcher(
`<select>${repeat(
`<option value="1"></option>`,
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
)}</select>`,
1,
),
])
expect(code).toMatchSnapshot()
})
@ -522,14 +448,7 @@ describe('stringify static html', () => {
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
)}</select></div>`,
)
expect(ast.hoists).toMatchObject([
{
type: NodeTypes.VNODE_CALL,
},
{
type: NodeTypes.JS_ARRAY_EXPRESSION,
},
])
expect(ast.cached).toMatchObject([cachedArrayBailedMatcher()])
expect(code).toMatchSnapshot()
})
})

View File

@ -268,7 +268,7 @@ describe('compiler-dom: transform v-on', () => {
prefixIdentifiers: true,
cacheHandlers: true,
})
expect(root.cached).toBe(1)
expect(root.cached.length).toBe(1)
// should not treat cached handler as dynamicProp, so it should have no
// dynamicProps flags and only the hydration flag
expect((root as any).children[0].codegenNode.patchFlag).toBe(

View File

@ -3,12 +3,12 @@
*/
import {
CREATE_STATIC,
type CacheExpression,
ConstantTypes,
type ElementNode,
ElementTypes,
type ExpressionNode,
type HoistTransform,
type JSChildNode,
Namespaces,
NodeTypes,
type PlainElementNode,
@ -16,11 +16,14 @@ import {
type TemplateChildNode,
type TextCallNode,
type TransformContext,
type VNodeCall,
createArrayExpression,
createCallExpression,
isStaticArgOf,
} from '@vue/compiler-core'
import {
escapeHtml,
isArray,
isBooleanAttr,
isKnownHtmlAttr,
isKnownSvgAttr,
@ -76,6 +79,14 @@ export const stringifyStatic: HoistTransform = (children, context, parent) => {
return
}
const isParentCached =
parent.type === NodeTypes.ELEMENT &&
parent.codegenNode &&
parent.codegenNode.type === NodeTypes.VNODE_CALL &&
parent.codegenNode.children &&
!isArray(parent.codegenNode.children) &&
parent.codegenNode.children.type === NodeTypes.JS_CACHE_EXPRESSION
let nc = 0 // current node count
let ec = 0 // current element with binding count
const currentChunk: StringifiableNode[] = []
@ -94,19 +105,31 @@ export const stringifyStatic: HoistTransform = (children, context, parent) => {
// will insert / hydrate
String(currentChunk.length),
])
// replace the first node's hoisted expression with the static vnode call
replaceHoist(currentChunk[0], staticCall, context)
if (currentChunk.length > 1) {
for (let i = 1; i < currentChunk.length; i++) {
// for the merged nodes, set their hoisted expression to null
replaceHoist(currentChunk[i], null, context)
if (isParentCached) {
;((parent.codegenNode as VNodeCall).children as CacheExpression).value =
createArrayExpression([staticCall])
} else {
// replace the first node's hoisted expression with the static vnode call
;(currentChunk[0].codegenNode as CacheExpression).value = staticCall
if (currentChunk.length > 1) {
// remove merged nodes from children
const deleteCount = currentChunk.length - 1
children.splice(currentIndex - currentChunk.length + 1, deleteCount)
// also adjust index for the remaining cache items
const cacheIndex = context.cached.indexOf(
currentChunk[currentChunk.length - 1]
.codegenNode as CacheExpression,
)
if (cacheIndex > -1) {
for (let i = cacheIndex; i < context.cached.length; i++) {
const c = context.cached[i]
if (c) c.index -= deleteCount
}
context.cached.splice(cacheIndex - deleteCount + 1, deleteCount)
}
return deleteCount
}
// also remove merged nodes from children
const deleteCount = currentChunk.length - 1
children.splice(currentIndex - currentChunk.length + 1, deleteCount)
return deleteCount
}
}
return 0
@ -115,16 +138,15 @@ export const stringifyStatic: HoistTransform = (children, context, parent) => {
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 stringifiable node
const node = child as StringifiableNode
const result = analyzeNode(node)
const isCached = isParentCached || getCachedNode(child)
if (isCached) {
// presence of cached means child must be a stringifiable node
const result = analyzeNode(child as StringifiableNode)
if (result) {
// node is stringifiable, record state
nc += result[0]
ec += result[1]
currentChunk.push(node)
currentChunk.push(child as StringifiableNode)
continue
}
}
@ -141,12 +163,19 @@ export const stringifyStatic: HoistTransform = (children, context, parent) => {
stringifyCurrentChunk(i)
}
const getHoistedNode = (node: TemplateChildNode) =>
((node.type === NodeTypes.ELEMENT && node.tagType === ElementTypes.ELEMENT) ||
node.type == NodeTypes.TEXT_CALL) &&
node.codegenNode &&
node.codegenNode.type === NodeTypes.SIMPLE_EXPRESSION &&
node.codegenNode.hoisted
const getCachedNode = (
node: TemplateChildNode,
): CacheExpression | undefined => {
if (
((node.type === NodeTypes.ELEMENT &&
node.tagType === ElementTypes.ELEMENT) ||
node.type === NodeTypes.TEXT_CALL) &&
node.codegenNode &&
node.codegenNode.type === NodeTypes.JS_CACHE_EXPRESSION
) {
return node.codegenNode
}
}
const dataAriaRE = /^(data|aria)-/
const isStringifiableAttr = (name: string, ns: Namespaces) => {
@ -159,21 +188,12 @@ const isStringifiableAttr = (name: string, ns: Namespaces) => {
)
}
const replaceHoist = (
node: StringifiableNode,
replacement: JSChildNode | null,
context: TransformContext,
) => {
const hoistToReplace = (node.codegenNode as SimpleExpressionNode).hoisted!
context.hoists[context.hoists.indexOf(hoistToReplace)] = replacement
}
const isNonStringifiable = /*#__PURE__*/ makeMap(
`caption,thead,tr,th,tbody,td,tfoot,colgroup,col`,
)
/**
* for a hoisted node, analyze it and return:
* for a cached node, analyze it and return:
* - false: bailed (contains non-stringifiable props or runtime constant)
* - [nc, ec] where
* - nc is the number of nodes inside
@ -381,7 +401,7 @@ function evaluateConstant(exp: ExpressionNode): string {
} else if (c.type === NodeTypes.INTERPOLATION) {
res += toDisplayString(evaluateConstant(c.content))
} else {
res += evaluateConstant(c)
res += evaluateConstant(c as ExpressionNode)
}
})
return res

View File

@ -851,8 +851,6 @@ return (_ctx, _cache) => {
exports[`SFC compile <script setup> > inlineTemplate mode > should work 1`] = `
"import { toDisplayString as _toDisplayString, createElementVNode as _createElementVNode, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
const _hoisted_1 = /*#__PURE__*/_createElementVNode("div", null, "static", -1 /* HOISTED */)
import { ref } from 'vue'
export default {
@ -863,7 +861,7 @@ export default {
return (_ctx, _cache) => {
return (_openBlock(), _createElementBlock(_Fragment, null, [
_createElementVNode("div", null, _toDisplayString(count.value), 1 /* TEXT */),
_hoisted_1
_cache[0] || (_cache[0] = _createElementVNode("div", null, "static", -1 /* CACHED */))
], 64 /* STABLE_FRAGMENT */))
}
}

View File

@ -38,13 +38,11 @@ import _imports_0 from '@svg/file.svg'
const _hoisted_1 = _imports_0 + '#fragment'
const _hoisted_2 = /*#__PURE__*/_createElementVNode("use", { href: _hoisted_1 }, null, -1 /* HOISTED */)
const _hoisted_3 = /*#__PURE__*/_createElementVNode("use", { href: _hoisted_1 }, null, -1 /* HOISTED */)
export function render(_ctx, _cache) {
return (_openBlock(), _createElementBlock(_Fragment, null, [
_hoisted_2,
_hoisted_3
_cache[0] || (_cache[0] = _createElementVNode("use", { href: _hoisted_1 }, null, -1 /* CACHED */)),
_cache[1] || (_cache[1] = _createElementVNode("use", { href: _hoisted_1 }, null, -1 /* CACHED */))
], 64 /* STABLE_FRAGMENT */))
}"
`;
@ -82,13 +80,10 @@ import _imports_0 from './bar.png'
import _imports_1 from '/bar.png'
const _hoisted_1 = /*#__PURE__*/_createStaticVNode("<img src=\\"" + _imports_0 + "\\"><img src=\\"" + _imports_1 + "\\"><img src=\\"https://foo.bar/baz.png\\"><img src=\\"//foo.bar/baz.png\\"><img src=\\"" + _imports_0 + "\\">", 5)
const _hoisted_6 = [
_hoisted_1
]
export function render(_ctx, _cache) {
return (_openBlock(), _createElementBlock("div", null, _hoisted_6))
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
_createStaticVNode("<img src=\\"" + _imports_0 + "\\"><img src=\\"" + _imports_1 + "\\"><img src=\\"https://foo.bar/baz.png\\"><img src=\\"//foo.bar/baz.png\\"><img src=\\"" + _imports_0 + "\\">", 5)
])))
}"
`;

View File

@ -7,13 +7,11 @@ import _imports_0 from '@/logo.png'
const _hoisted_1 = _imports_0 + ', ' + _imports_0 + ' 2x'
const _hoisted_2 = _imports_0 + ' 1x, ' + "/foo/logo.png" + ' 2x'
const _hoisted_3 = /*#__PURE__*/_createElementVNode("img", { srcset: _hoisted_1 }, null, -1 /* HOISTED */)
const _hoisted_4 = /*#__PURE__*/_createElementVNode("img", { srcset: _hoisted_2 }, null, -1 /* HOISTED */)
export function render(_ctx, _cache) {
return (_openBlock(), _createElementBlock(_Fragment, null, [
_hoisted_3,
_hoisted_4
_cache[0] || (_cache[0] = _createElementVNode("img", { srcset: _hoisted_1 }, null, -1 /* CACHED */)),
_cache[1] || (_cache[1] = _createElementVNode("img", { srcset: _hoisted_2 }, null, -1 /* CACHED */))
], 64 /* STABLE_FRAGMENT */))
}"
`;
@ -31,69 +29,57 @@ const _hoisted_5 = _imports_0 + ' 2x, ' + _imports_0
const _hoisted_6 = _imports_0 + ' 2x, ' + _imports_0 + ' 3x'
const _hoisted_7 = _imports_0 + ', ' + _imports_0 + ' 2x, ' + _imports_0 + ' 3x'
const _hoisted_8 = "/logo.png" + ', ' + _imports_0 + ' 2x'
const _hoisted_9 = /*#__PURE__*/_createElementVNode("img", {
src: "./logo.png",
srcset: ""
}, null, -1 /* HOISTED */)
const _hoisted_10 = /*#__PURE__*/_createElementVNode("img", {
src: "./logo.png",
srcset: _hoisted_1
}, null, -1 /* HOISTED */)
const _hoisted_11 = /*#__PURE__*/_createElementVNode("img", {
src: "./logo.png",
srcset: _hoisted_2
}, null, -1 /* HOISTED */)
const _hoisted_12 = /*#__PURE__*/_createElementVNode("img", {
src: "./logo.png",
srcset: _hoisted_3
}, null, -1 /* HOISTED */)
const _hoisted_13 = /*#__PURE__*/_createElementVNode("img", {
src: "./logo.png",
srcset: _hoisted_4
}, null, -1 /* HOISTED */)
const _hoisted_14 = /*#__PURE__*/_createElementVNode("img", {
src: "./logo.png",
srcset: _hoisted_5
}, null, -1 /* HOISTED */)
const _hoisted_15 = /*#__PURE__*/_createElementVNode("img", {
src: "./logo.png",
srcset: _hoisted_6
}, null, -1 /* HOISTED */)
const _hoisted_16 = /*#__PURE__*/_createElementVNode("img", {
src: "./logo.png",
srcset: _hoisted_7
}, null, -1 /* HOISTED */)
const _hoisted_17 = /*#__PURE__*/_createElementVNode("img", {
src: "/logo.png",
srcset: "/logo.png, /logo.png 2x"
}, null, -1 /* HOISTED */)
const _hoisted_18 = /*#__PURE__*/_createElementVNode("img", {
src: "https://example.com/logo.png",
srcset: "https://example.com/logo.png, https://example.com/logo.png 2x"
}, null, -1 /* HOISTED */)
const _hoisted_19 = /*#__PURE__*/_createElementVNode("img", {
src: "/logo.png",
srcset: _hoisted_8
}, null, -1 /* HOISTED */)
const _hoisted_20 = /*#__PURE__*/_createElementVNode("img", {
src: "data:image/png;base64,i",
srcset: "data:image/png;base64,i 1x, data:image/png;base64,i 2x"
}, null, -1 /* HOISTED */)
export function render(_ctx, _cache) {
return (_openBlock(), _createElementBlock(_Fragment, null, [
_hoisted_9,
_hoisted_10,
_hoisted_11,
_hoisted_12,
_hoisted_13,
_hoisted_14,
_hoisted_15,
_hoisted_16,
_hoisted_17,
_hoisted_18,
_hoisted_19,
_hoisted_20
_cache[0] || (_cache[0] = _createElementVNode("img", {
src: "./logo.png",
srcset: ""
}, null, -1 /* CACHED */)),
_cache[1] || (_cache[1] = _createElementVNode("img", {
src: "./logo.png",
srcset: _hoisted_1
}, null, -1 /* CACHED */)),
_cache[2] || (_cache[2] = _createElementVNode("img", {
src: "./logo.png",
srcset: _hoisted_2
}, null, -1 /* CACHED */)),
_cache[3] || (_cache[3] = _createElementVNode("img", {
src: "./logo.png",
srcset: _hoisted_3
}, null, -1 /* CACHED */)),
_cache[4] || (_cache[4] = _createElementVNode("img", {
src: "./logo.png",
srcset: _hoisted_4
}, null, -1 /* CACHED */)),
_cache[5] || (_cache[5] = _createElementVNode("img", {
src: "./logo.png",
srcset: _hoisted_5
}, null, -1 /* CACHED */)),
_cache[6] || (_cache[6] = _createElementVNode("img", {
src: "./logo.png",
srcset: _hoisted_6
}, null, -1 /* CACHED */)),
_cache[7] || (_cache[7] = _createElementVNode("img", {
src: "./logo.png",
srcset: _hoisted_7
}, null, -1 /* CACHED */)),
_cache[8] || (_cache[8] = _createElementVNode("img", {
src: "/logo.png",
srcset: "/logo.png, /logo.png 2x"
}, null, -1 /* CACHED */)),
_cache[9] || (_cache[9] = _createElementVNode("img", {
src: "https://example.com/logo.png",
srcset: "https://example.com/logo.png, https://example.com/logo.png 2x"
}, null, -1 /* CACHED */)),
_cache[10] || (_cache[10] = _createElementVNode("img", {
src: "/logo.png",
srcset: _hoisted_8
}, null, -1 /* CACHED */)),
_cache[11] || (_cache[11] = _createElementVNode("img", {
src: "data:image/png;base64,i",
srcset: "data:image/png;base64,i 1x, data:image/png;base64,i 2x"
}, null, -1 /* CACHED */))
], 64 /* STABLE_FRAGMENT */))
}"
`;
@ -101,69 +87,56 @@ export function render(_ctx, _cache) {
exports[`compiler sfc: transform srcset > transform srcset w/ base 1`] = `
"import { createElementVNode as _createElementVNode, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
const _hoisted_1 = /*#__PURE__*/_createElementVNode("img", {
src: "./logo.png",
srcset: ""
}, null, -1 /* HOISTED */)
const _hoisted_2 = /*#__PURE__*/_createElementVNode("img", {
src: "./logo.png",
srcset: "/foo/logo.png"
}, null, -1 /* HOISTED */)
const _hoisted_3 = /*#__PURE__*/_createElementVNode("img", {
src: "./logo.png",
srcset: "/foo/logo.png 2x"
}, null, -1 /* HOISTED */)
const _hoisted_4 = /*#__PURE__*/_createElementVNode("img", {
src: "./logo.png",
srcset: "/foo/logo.png 2x"
}, null, -1 /* HOISTED */)
const _hoisted_5 = /*#__PURE__*/_createElementVNode("img", {
src: "./logo.png",
srcset: "/foo/logo.png, /foo/logo.png 2x"
}, null, -1 /* HOISTED */)
const _hoisted_6 = /*#__PURE__*/_createElementVNode("img", {
src: "./logo.png",
srcset: "/foo/logo.png 2x, /foo/logo.png"
}, null, -1 /* HOISTED */)
const _hoisted_7 = /*#__PURE__*/_createElementVNode("img", {
src: "./logo.png",
srcset: "/foo/logo.png 2x, /foo/logo.png 3x"
}, null, -1 /* HOISTED */)
const _hoisted_8 = /*#__PURE__*/_createElementVNode("img", {
src: "./logo.png",
srcset: "/foo/logo.png, /foo/logo.png 2x, /foo/logo.png 3x"
}, null, -1 /* HOISTED */)
const _hoisted_9 = /*#__PURE__*/_createElementVNode("img", {
src: "/logo.png",
srcset: "/logo.png, /logo.png 2x"
}, null, -1 /* HOISTED */)
const _hoisted_10 = /*#__PURE__*/_createElementVNode("img", {
src: "https://example.com/logo.png",
srcset: "https://example.com/logo.png, https://example.com/logo.png 2x"
}, null, -1 /* HOISTED */)
const _hoisted_11 = /*#__PURE__*/_createElementVNode("img", {
src: "/logo.png",
srcset: "/logo.png, /foo/logo.png 2x"
}, null, -1 /* HOISTED */)
const _hoisted_12 = /*#__PURE__*/_createElementVNode("img", {
src: "data:image/png;base64,i",
srcset: "data:image/png;base64,i 1x, data:image/png;base64,i 2x"
}, null, -1 /* HOISTED */)
export function render(_ctx, _cache) {
return (_openBlock(), _createElementBlock(_Fragment, null, [
_hoisted_1,
_hoisted_2,
_hoisted_3,
_hoisted_4,
_hoisted_5,
_hoisted_6,
_hoisted_7,
_hoisted_8,
_hoisted_9,
_hoisted_10,
_hoisted_11,
_hoisted_12
_cache[0] || (_cache[0] = _createElementVNode("img", {
src: "./logo.png",
srcset: ""
}, null, -1 /* CACHED */)),
_cache[1] || (_cache[1] = _createElementVNode("img", {
src: "./logo.png",
srcset: "/foo/logo.png"
}, null, -1 /* CACHED */)),
_cache[2] || (_cache[2] = _createElementVNode("img", {
src: "./logo.png",
srcset: "/foo/logo.png 2x"
}, null, -1 /* CACHED */)),
_cache[3] || (_cache[3] = _createElementVNode("img", {
src: "./logo.png",
srcset: "/foo/logo.png 2x"
}, null, -1 /* CACHED */)),
_cache[4] || (_cache[4] = _createElementVNode("img", {
src: "./logo.png",
srcset: "/foo/logo.png, /foo/logo.png 2x"
}, null, -1 /* CACHED */)),
_cache[5] || (_cache[5] = _createElementVNode("img", {
src: "./logo.png",
srcset: "/foo/logo.png 2x, /foo/logo.png"
}, null, -1 /* CACHED */)),
_cache[6] || (_cache[6] = _createElementVNode("img", {
src: "./logo.png",
srcset: "/foo/logo.png 2x, /foo/logo.png 3x"
}, null, -1 /* CACHED */)),
_cache[7] || (_cache[7] = _createElementVNode("img", {
src: "./logo.png",
srcset: "/foo/logo.png, /foo/logo.png 2x, /foo/logo.png 3x"
}, null, -1 /* CACHED */)),
_cache[8] || (_cache[8] = _createElementVNode("img", {
src: "/logo.png",
srcset: "/logo.png, /logo.png 2x"
}, null, -1 /* CACHED */)),
_cache[9] || (_cache[9] = _createElementVNode("img", {
src: "https://example.com/logo.png",
srcset: "https://example.com/logo.png, https://example.com/logo.png 2x"
}, null, -1 /* CACHED */)),
_cache[10] || (_cache[10] = _createElementVNode("img", {
src: "/logo.png",
srcset: "/logo.png, /foo/logo.png 2x"
}, null, -1 /* CACHED */)),
_cache[11] || (_cache[11] = _createElementVNode("img", {
src: "data:image/png;base64,i",
srcset: "data:image/png;base64,i 1x, data:image/png;base64,i 2x"
}, null, -1 /* CACHED */))
], 64 /* STABLE_FRAGMENT */))
}"
`;
@ -183,69 +156,57 @@ const _hoisted_6 = _imports_0 + ' 2x, ' + _imports_0 + ' 3x'
const _hoisted_7 = _imports_0 + ', ' + _imports_0 + ' 2x, ' + _imports_0 + ' 3x'
const _hoisted_8 = _imports_1 + ', ' + _imports_1 + ' 2x'
const _hoisted_9 = _imports_1 + ', ' + _imports_0 + ' 2x'
const _hoisted_10 = /*#__PURE__*/_createElementVNode("img", {
src: "./logo.png",
srcset: ""
}, null, -1 /* HOISTED */)
const _hoisted_11 = /*#__PURE__*/_createElementVNode("img", {
src: "./logo.png",
srcset: _hoisted_1
}, null, -1 /* HOISTED */)
const _hoisted_12 = /*#__PURE__*/_createElementVNode("img", {
src: "./logo.png",
srcset: _hoisted_2
}, null, -1 /* HOISTED */)
const _hoisted_13 = /*#__PURE__*/_createElementVNode("img", {
src: "./logo.png",
srcset: _hoisted_3
}, null, -1 /* HOISTED */)
const _hoisted_14 = /*#__PURE__*/_createElementVNode("img", {
src: "./logo.png",
srcset: _hoisted_4
}, null, -1 /* HOISTED */)
const _hoisted_15 = /*#__PURE__*/_createElementVNode("img", {
src: "./logo.png",
srcset: _hoisted_5
}, null, -1 /* HOISTED */)
const _hoisted_16 = /*#__PURE__*/_createElementVNode("img", {
src: "./logo.png",
srcset: _hoisted_6
}, null, -1 /* HOISTED */)
const _hoisted_17 = /*#__PURE__*/_createElementVNode("img", {
src: "./logo.png",
srcset: _hoisted_7
}, null, -1 /* HOISTED */)
const _hoisted_18 = /*#__PURE__*/_createElementVNode("img", {
src: "/logo.png",
srcset: _hoisted_8
}, null, -1 /* HOISTED */)
const _hoisted_19 = /*#__PURE__*/_createElementVNode("img", {
src: "https://example.com/logo.png",
srcset: "https://example.com/logo.png, https://example.com/logo.png 2x"
}, null, -1 /* HOISTED */)
const _hoisted_20 = /*#__PURE__*/_createElementVNode("img", {
src: "/logo.png",
srcset: _hoisted_9
}, null, -1 /* HOISTED */)
const _hoisted_21 = /*#__PURE__*/_createElementVNode("img", {
src: "data:image/png;base64,i",
srcset: "data:image/png;base64,i 1x, data:image/png;base64,i 2x"
}, null, -1 /* HOISTED */)
export function render(_ctx, _cache) {
return (_openBlock(), _createElementBlock(_Fragment, null, [
_hoisted_10,
_hoisted_11,
_hoisted_12,
_hoisted_13,
_hoisted_14,
_hoisted_15,
_hoisted_16,
_hoisted_17,
_hoisted_18,
_hoisted_19,
_hoisted_20,
_hoisted_21
_cache[0] || (_cache[0] = _createElementVNode("img", {
src: "./logo.png",
srcset: ""
}, null, -1 /* CACHED */)),
_cache[1] || (_cache[1] = _createElementVNode("img", {
src: "./logo.png",
srcset: _hoisted_1
}, null, -1 /* CACHED */)),
_cache[2] || (_cache[2] = _createElementVNode("img", {
src: "./logo.png",
srcset: _hoisted_2
}, null, -1 /* CACHED */)),
_cache[3] || (_cache[3] = _createElementVNode("img", {
src: "./logo.png",
srcset: _hoisted_3
}, null, -1 /* CACHED */)),
_cache[4] || (_cache[4] = _createElementVNode("img", {
src: "./logo.png",
srcset: _hoisted_4
}, null, -1 /* CACHED */)),
_cache[5] || (_cache[5] = _createElementVNode("img", {
src: "./logo.png",
srcset: _hoisted_5
}, null, -1 /* CACHED */)),
_cache[6] || (_cache[6] = _createElementVNode("img", {
src: "./logo.png",
srcset: _hoisted_6
}, null, -1 /* CACHED */)),
_cache[7] || (_cache[7] = _createElementVNode("img", {
src: "./logo.png",
srcset: _hoisted_7
}, null, -1 /* CACHED */)),
_cache[8] || (_cache[8] = _createElementVNode("img", {
src: "/logo.png",
srcset: _hoisted_8
}, null, -1 /* CACHED */)),
_cache[9] || (_cache[9] = _createElementVNode("img", {
src: "https://example.com/logo.png",
srcset: "https://example.com/logo.png, https://example.com/logo.png 2x"
}, null, -1 /* CACHED */)),
_cache[10] || (_cache[10] = _createElementVNode("img", {
src: "/logo.png",
srcset: _hoisted_9
}, null, -1 /* CACHED */)),
_cache[11] || (_cache[11] = _createElementVNode("img", {
src: "data:image/png;base64,i",
srcset: "data:image/png;base64,i 1x, data:image/png;base64,i 2x"
}, null, -1 /* CACHED */))
], 64 /* STABLE_FRAGMENT */))
}"
`;
@ -265,12 +226,10 @@ const _hoisted_6 = _imports_0 + ' 2x, ' + _imports_0 + ' 3x'
const _hoisted_7 = _imports_0 + ', ' + _imports_0 + ' 2x, ' + _imports_0 + ' 3x'
const _hoisted_8 = _imports_1 + ', ' + _imports_1 + ' 2x'
const _hoisted_9 = _imports_1 + ', ' + _imports_0 + ' 2x'
const _hoisted_10 = /*#__PURE__*/_createStaticVNode("<img src=\\"./logo.png\\" srcset=\\"\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_1 + "\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_2 + "\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_3 + "\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_4 + "\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_5 + "\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_6 + "\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_7 + "\\"><img src=\\"/logo.png\\" srcset=\\"" + _hoisted_8 + "\\"><img src=\\"https://example.com/logo.png\\" srcset=\\"https://example.com/logo.png, https://example.com/logo.png 2x\\"><img src=\\"/logo.png\\" srcset=\\"" + _hoisted_9 + "\\"><img src=\\"data:image/png;base64,i\\" srcset=\\"data:image/png;base64,i 1x, data:image/png;base64,i 2x\\">", 12)
const _hoisted_22 = [
_hoisted_10
]
export function render(_ctx, _cache) {
return (_openBlock(), _createElementBlock("div", null, _hoisted_22))
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
_createStaticVNode("<img src=\\"./logo.png\\" srcset=\\"\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_1 + "\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_2 + "\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_3 + "\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_4 + "\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_5 + "\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_6 + "\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_7 + "\\"><img src=\\"/logo.png\\" srcset=\\"" + _hoisted_8 + "\\"><img src=\\"https://example.com/logo.png\\" srcset=\\"https://example.com/logo.png, https://example.com/logo.png 2x\\"><img src=\\"/logo.png\\" srcset=\\"" + _hoisted_9 + "\\"><img src=\\"data:image/png;base64,i\\" srcset=\\"data:image/png;base64,i 1x, data:image/png;base64,i 2x\\">", 12)
])))
}"
`;

View File

@ -1122,7 +1122,7 @@ describe('SSR hydration', () => {
'input',
{ type: 'checkbox', indeterminate: '' },
null,
PatchFlags.HOISTED,
PatchFlags.CACHED,
),
)
expect((container.firstChild as any).indeterminate).toBe(true)

View File

@ -366,7 +366,7 @@ export function createHydrationFunctions(
const forcePatch = type === 'input' || type === 'option'
// skip props & children if this is hoisted static nodes
// #5405 in dev, always hydrate children for HMR
if (__DEV__ || forcePatch || patchFlag !== PatchFlags.HOISTED) {
if (__DEV__ || forcePatch || patchFlag !== PatchFlags.CACHED) {
if (dirs) {
invokeDirectiveHook(vnode, null, parentComponent, 'created')
}

View File

@ -650,7 +650,7 @@ export function cloneVNode<T, U>(
scopeId: vnode.scopeId,
slotScopeIds: vnode.slotScopeIds,
children:
__DEV__ && patchFlag === PatchFlags.HOISTED && isArray(children)
__DEV__ && patchFlag === PatchFlags.CACHED && isArray(children)
? (children as VNode[]).map(deepCloneVNode)
: children,
target: vnode.target,
@ -663,7 +663,7 @@ export function cloneVNode<T, U>(
// fast paths only.
patchFlag:
extraProps && vnode.type !== Fragment
? patchFlag === PatchFlags.HOISTED // hoisted node
? patchFlag === PatchFlags.CACHED // hoisted node
? PatchFlags.FULL_PROPS
: patchFlag | PatchFlags.FULL_PROPS
: patchFlag,
@ -772,7 +772,7 @@ export function normalizeVNode(child: VNodeChild): VNode {
// optimized normalization for template-compiled render fns
export function cloneIfMounted(child: VNode): VNode {
return (child.el === null && child.patchFlag !== PatchFlags.HOISTED) ||
return (child.el === null && child.patchFlag !== PatchFlags.CACHED) ||
child.memo
? child
: cloneVNode(child)

View File

@ -109,10 +109,10 @@ export enum PatchFlags {
*/
/**
* Indicates a hoisted static vnode. This is a hint for hydration to skip
* Indicates a cached static vnode. This is also a hint for hydration to skip
* the entire sub tree since static content never needs to be updated.
*/
HOISTED = -1,
CACHED = -1,
/**
* A special flag that indicates that the diffing algorithm should bail out
* of optimized mode. For example, on block fragments created by renderSlot()
@ -139,6 +139,6 @@ export const PatchFlagNames: Record<PatchFlags, string> = {
[PatchFlags.NEED_PATCH]: `NEED_PATCH`,
[PatchFlags.DYNAMIC_SLOTS]: `DYNAMIC_SLOTS`,
[PatchFlags.DEV_ROOT_FRAGMENT]: `DEV_ROOT_FRAGMENT`,
[PatchFlags.HOISTED]: `HOISTED`,
[PatchFlags.CACHED]: `HOISTED`,
[PatchFlags.BAIL]: `BAIL`,
}