]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(compiler-core): change node hoisting to caching per instance (#11067)
authorEvan You <yyx990803@gmail.com>
Tue, 4 Jun 2024 12:09:54 +0000 (20:09 +0800)
committerGitHub <noreply@github.com>
Tue, 4 Jun 2024 12:09:54 +0000 (20:09 +0800)
close #5256
close #9219
close #10959

35 files changed:
packages/compiler-core/__tests__/__snapshots__/parse.spec.ts.snap
packages/compiler-core/__tests__/__snapshots__/scopeId.spec.ts.snap
packages/compiler-core/__tests__/codegen.spec.ts
packages/compiler-core/__tests__/scopeId.spec.ts
packages/compiler-core/__tests__/transforms/__snapshots__/cacheStatic.spec.ts.snap [moved from packages/compiler-core/__tests__/transforms/__snapshots__/hoistStatic.spec.ts.snap with 67% similarity]
packages/compiler-core/__tests__/transforms/cacheStatic.spec.ts [moved from packages/compiler-core/__tests__/transforms/hoistStatic.spec.ts with 52% similarity]
packages/compiler-core/__tests__/transforms/vModel.spec.ts
packages/compiler-core/__tests__/transforms/vOn.spec.ts
packages/compiler-core/__tests__/transforms/vOnce.spec.ts
packages/compiler-core/src/ast.ts
packages/compiler-core/src/codegen.ts
packages/compiler-core/src/index.ts
packages/compiler-core/src/options.ts
packages/compiler-core/src/runtimeHelpers.ts
packages/compiler-core/src/transform.ts
packages/compiler-core/src/transforms/cacheStatic.ts [moved from packages/compiler-core/src/transforms/hoistStatic.ts with 72% similarity]
packages/compiler-core/src/transforms/transformElement.ts
packages/compiler-core/src/transforms/transformExpression.ts
packages/compiler-core/src/transforms/transformText.ts
packages/compiler-core/src/transforms/vFor.ts
packages/compiler-core/src/transforms/vIf.ts
packages/compiler-core/src/transforms/vMemo.ts
packages/compiler-core/src/transforms/vModel.ts
packages/compiler-core/src/utils.ts
packages/compiler-dom/__tests__/transforms/__snapshots__/stringifyStatic.spec.ts.snap
packages/compiler-dom/__tests__/transforms/stringifyStatic.spec.ts
packages/compiler-dom/__tests__/transforms/vOn.spec.ts
packages/compiler-dom/src/transforms/stringifyStatic.ts
packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap
packages/compiler-sfc/__tests__/__snapshots__/templateTransformAssetUrl.spec.ts.snap
packages/compiler-sfc/__tests__/__snapshots__/templateTransformSrcset.spec.ts.snap
packages/runtime-core/__tests__/hydration.spec.ts
packages/runtime-core/src/hydration.ts
packages/runtime-core/src/vnode.ts
packages/shared/src/patchFlags.ts

index 678548e35b5093a2c4b537f1eddcfb8b1175130e..942eed4c4dcb5cade41e073158cf0b4c8996733c 100644 (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": {
index 445d3fd13ed047251a0b43f04c195ddf0078bf20..97aff163ecbead2ee3a977cdfb93fb2b51818bce 100644 (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"
 
index 9c9230756343d0867a176df82a6b580ac016a8b7..307cbbc8dcff1e972ad8dc2a3a8ed99e9d60ea9d 100644 (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),
index c7a21df7a7879dad57701c17fbbf0333e771d644..7d1d27e115f3b5729036176d910e5eec351d3b6e 100644 (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()
-  })
 })
similarity index 67%
rename from packages/compiler-core/__tests__/transforms/__snapshots__/hoistStatic.spec.ts.snap
rename to packages/compiler-core/__tests__/transforms/__snapshots__/cacheStatic.spec.ts.snap
index ef3e2b8f4d69f9c73a3a982b28c0ee46b11ce184..fc1446df5e233bd6dab1a235eecb0261d5663a46 100644 (file)
 // 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, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
+    const { createElementVNode: _createElementVNode, createTextVNode: _createTextVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
 
-    return (_openBlock(), _createElementBlock("div", null, _hoisted_3))
+    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: hoistStatic transform > hoist simple element 1`] = `
+exports[`compiler: cacheStatic transform > cache single children array 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))
+    return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
+      _createElementVNode("span", { class: "inline" }, "hello", -1 /* CACHED */)
+    ])))
   }
 }"
 `;
 
-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,69 +140,73 @@ 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
-const { createElementVNode: _createElementVNode } = _Vue
-
-const _hoisted_1 = {
-  class: /*#__PURE__*/_normalizeClass({ foo: true })
-}
 
 return function render(_ctx, _cache) {
   with (_ctx) {
-    const { toDisplayString: _toDisplayString, normalizeClass: _normalizeClass, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
+    const { toDisplayString: _toDisplayString, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
 
-    return (_openBlock(), _createElementBlock("div", null, [
-      _createElementVNode("span", _hoisted_1, _toDisplayString(_ctx.bar), 1 /* TEXT */)
-    ]))
+    return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
+      _createElementVNode("span", null, "foo " + _toDisplayString(1) + " " + _toDisplayString(true), -1 /* CACHED */)
+    ])))
   }
 }"
 `;
 
-exports[`compiler: hoistStatic transform > prefixIdentifiers > hoist nested static tree with static interpolation 1`] = `
+exports[`compiler: cacheStatic transform > prefixIdentifiers > cache nested static tree with static prop value 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))
+    return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
+      _createElementVNode("span", { foo: 0 }, _toDisplayString(1), -1 /* CACHED */)
+    ])))
   }
 }"
 `;
 
-exports[`compiler: hoistStatic transform > prefixIdentifiers > hoist nested static tree with static prop value 1`] = `
+exports[`compiler: cacheStatic transform > prefixIdentifiers > clone hoisted array children in v-for + HMR mode 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
+    const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, createElementVNode: _createElementVNode } = _Vue
 
-    return (_openBlock(), _createElementBlock("div", null, _hoisted_2))
+    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: hoistStatic transform > prefixIdentifiers > should NOT hoist SVG with directives 1`] = `
+exports[`compiler: cacheStatic transform > prefixIdentifiers > hoist class with static object value 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
-]
+const _hoisted_1 = {
+  class: /*#__PURE__*/_normalizeClass({ foo: true })
+}
+
+return function render(_ctx, _cache) {
+  with (_ctx) {
+    const { toDisplayString: _toDisplayString, normalizeClass: _normalizeClass, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
+
+    return (_openBlock(), _createElementBlock("div", null, [
+      _createElementVNode("span", _hoisted_1, _toDisplayString(_ctx.bar), 1 /* TEXT */)
+    ]))
+  }
+}"
+`;
+
+exports[`compiler: cacheStatic transform > prefixIdentifiers > should NOT cache SVG with directives 1`] = `
+"const _Vue = Vue
 
 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,27 +386,35 @@ return function render(_ctx, _cache) {
 }"
 `;
 
-exports[`compiler: hoistStatic transform > should NOT hoist root node 1`] = `
+exports[`compiler: cacheStatic transform > should cache v-if props/children if static 1`] = `
 "const _Vue = Vue
+const { createElementVNode: _createElementVNode, createCommentVNode: _createCommentVNode } = _Vue
+
+const _hoisted_1 = {
+  key: 0,
+  id: "foo"
+}
 
 return function render(_ctx, _cache) {
   with (_ctx) {
-    const { openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
+    const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock, createCommentVNode: _createCommentVNode } = _Vue
 
-    return (_openBlock(), _createElementBlock("div"))
+    return (_openBlock(), _createElementBlock("div", null, [
+      ok
+        ? (_openBlock(), _createElementBlock("div", _hoisted_1, _cache[0] || (_cache[0] = [
+            _createElementVNode("span", null, null, -1 /* CACHED */)
+          ])))
+        : _createCommentVNode("v-if", true)
+    ]))
   }
 }"
 `;
 
-exports[`compiler: hoistStatic transform > should hoist v-for children if static 1`] = `
+exports[`compiler: cacheStatic 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) {
@@ -424,35 +422,11 @@ return function render(_ctx, _cache) {
 
     return (_openBlock(), _createElementBlock("div", null, [
       (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(list, (i) => {
-        return (_openBlock(), _createElementBlock("div", _hoisted_1, _hoisted_3))
+        return (_openBlock(), _createElementBlock("div", _hoisted_1, _cache[0] || (_cache[0] = [
+          _createElementVNode("span", null, null, -1 /* CACHED */)
+        ])))
       }), 256 /* UNKEYED_FRAGMENT */))
     ]))
   }
 }"
 `;
-
-exports[`compiler: hoistStatic transform > should hoist v-if props/children if static 1`] = `
-"const _Vue = Vue
-const { createElementVNode: _createElementVNode, createCommentVNode: _createCommentVNode } = _Vue
-
-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) {
-    const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock, createCommentVNode: _createCommentVNode } = _Vue
-
-    return (_openBlock(), _createElementBlock("div", null, [
-      ok
-        ? (_openBlock(), _createElementBlock("div", _hoisted_1, _hoisted_3))
-        : _createCommentVNode("v-if", true)
-    ]))
-  }
-}"
-`;
similarity index 52%
rename from packages/compiler-core/__tests__/transforms/hoistStatic.spec.ts
rename to packages/compiler-core/__tests__/transforms/cacheStatic.spec.ts
index d6c46b52eb3a3cb04f784f62cb4d329d09e4f578..8cfe83838013886adc87b5f8b33981d674e809af 100644 (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('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('hoist simple element', () => {
-    const root = transformWithHoist(
+  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({
+    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: {
-          content: '[..._hoisted_2]',
+          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()
     })
   })
 })
index 5d94aca27778e7cce041c318878961bf8f0b9308..82dd4909fd653e0faecff7db07582cb19841fdf5 100644 (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', () => {
index b1c37e3f74e46d372460108d6a097b839fe297e2..4a285e627b191a6e1898f62ae339bb9a4b6c0b49 100644 (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()
index 30149c62df6ae2a504803fe1f5c213eb3cd1d000..5f47a0fdbd99cf9417a838f6b46c7343fa7af6b7 100644 (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,
index 91354b1b40bf1e3a3eb9039773f7c046d530586e..2dec11b0291018927ced62cee7ccc9784774a825 100644 (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,
   }
 }
index 987293d512497ed455480f2d418ac7cd9d1bfc7f..779106d489016a78d6b533c9eec6577e8cf784a1 100644 (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) {
index 47628b6697906559dbcb5c01c5d72c9ffed7315f..29e5f681300aadc918134985832e0c55ff31f925 100644 (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
index 5a8cd0079b950594b3a3ac7d007cac36e9493323..16b35d3ee85f8a3e9c3fed7d29c63a3a4a131c99 100644 (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
index 47ab73e011a1243ae074b2fe557805ef67b70aec..ded149e59b7f1c56638ccb56e95a3abe0d59fda6 100644 (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` : ``)
index 69821f7f879f30263cc6e216b26cf50aa27c290b..a3553b499b25ef8d1eaeb623d6e5423ed63a892e 100644 (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)
similarity index 72%
rename from packages/compiler-core/src/transforms/hoistStatic.ts
rename to packages/compiler-core/src/transforms/cacheStatic.ts
index 67bdaa887bf5f1d7c06af7a411f5b633b2b04d2f..6b425b6931523658b7804321c10cbecb7a5aa18b 100644 (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),
-    )
+  if (!cachedAsArray) {
+    for (const child of toCache) {
+      child.codegenNode = context.cache(child.codegenNode!)
+    }
+  }
+
+  function getCacheExpression(value: JSChildNode): CacheExpression {
+    const exp = context.cache(value)
     // #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}]`
+    // 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
     }
-    node.codegenNode.children = hoisted
+  }
+
+  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
index b1be06db7030227f6deba53c7e696b2a10e9872e..3f94838e5027b1bc427f9baa3ae2d864db69e141 100644 (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,
index 35aa9a373a4298f5a5f4bbd9a5e6b396b3c3c271..6f064d93759aad21dcbea964ed074d9496b37ef0 100644 (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
index bdfb37ccb07e01c547834f16c64bb2c830a3ae11..6ae97d90394fa3061b2d111d67d12d0a8fe6373a 100644 (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.
index 5d423ee2429e96ffd353ed1d265536a5b61a1bb5..890dd5cae91d565599c516b23fafc5549e46474c 100644 (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(
index a6437f89c639bf32b7d973721512e6ab6f534b5a..7f375c3a0aba8a0b153bcd48ca05abbc3161f562 100644 (file)
@@ -248,7 +248,7 @@ function createChildrenCodegenNode(
       `${keyIndex}`,
       false,
       locStub,
-      ConstantTypes.CAN_HOIST,
+      ConstantTypes.CAN_CACHE,
     ),
   )
   const { children } = branch
index 7c728f4caadbfa429574bfb932394ac32c059437..83295a45c8adabe2172919a0370b53303f333590 100644 (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)
       }
     }
   }
index 863b3a7d729b331212ce0eeec0cfd973b16824d5..88be6238a4dbed32b1eaaaf98b3c625f1fa884f5 100644 (file)
@@ -148,7 +148,7 @@ export const transformModel: DirectiveTransform = (dir, node, context) => {
           `{ ${modifiers} }`,
           false,
           dir.loc,
-          ConstantTypes.CAN_HOIST,
+          ConstantTypes.CAN_CACHE,
         ),
       ),
     )
index 99a2c5b61a89974b730a90922483b3f382d608f6..50b2dddc2f41fce70cb9a9c0948f54200ed72899 100644 (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__) {
index 57d880a03f6e13ebe1e4949fd5186c5aa3667297..ef0c54d34ef9aec7de952d9772d7adc28d7a607c 100644 (file)
 // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
 
-exports[`stringify static html > should bail for <option> elements with number values 1`] = `
-"const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
+exports[`stringify static html > escape 1`] = `
+"const { toDisplayString: _toDisplayString, normalizeClass: _normalizeClass, createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, 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, _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, _hoisted_2))
+  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 on bindings that are hoisted but not stringifiable 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("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, _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 cached but not stringifiable 1`] = `
+"const { createElementVNode: _createElementVNode, 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] = [
+    _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 with bindings that are non-static but stringifiable 1`] = `
+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
 
-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, _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
 
 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))
 }"
 `;
index 67267c13deea0ed4c1eb77fa703a5acfde1d0ab3..fbf5718e65ecd4507cf023c372591c5633289c94 100644 (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()
   })
 })
index 2e80729119d16c4985d7edfd9fc5ca451259d4ee..bc88e5f1e57343e6b858edc1991b84f343f1dbc8 100644 (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(
index 7d740798ebbef7f4f3b6dc7b8ac5c471de7635e4..4bce0f990399a65c085acaca04ff75d2a10b2b68 100644 (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
index b557919cb9696d244fbb6f0f10144cbd48cd485e..c746a21ffaee77be0ce1396edf9649c17e5410ae 100644 (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 */))
 }
 }
index 6af4115efb06ee5e266dd1210a3524711773de85..18ec05da2b574ad66d9c47a1cfdd02331f67dc69 100644 (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)
+  ])))
 }"
 `;
 
index 8aaba811404f01e1f2cb7d1833e4836f67865f7f..0469ffaba88655cd95429cc95d7b27883eed6fd4 100644 (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)
+  ])))
 }"
 `;
index 933542ab98db9a7952a61e423ea68fe82c60ba92..241d7b39456443d448e5aefba6367cf9463b8781 100644 (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)
index 3be8837ce4b85751d891bbcfc4f70c217ee534b5..b7e320e2d2ec423f7ec9641aad0b7ffeabfa335f 100644 (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')
       }
index 0e2a4bafcc5ae6d69cc7978a58dc10f30b69c15c..e886095ebe73174c91a226974bd9e103edf84250 100644 (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)
index 009b714737feb0ebfbc9fb87c8479f5420855fed..fc22f10de40357e1cb6b9df71ef22e597a975eca 100644 (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`,
 }