Przeglądaj źródła

fix(compiler-core): handle template root and template v-if as stable fragments

Evan You 6 lat temu
rodzic
commit
8ffd79c754

+ 1 - 0
jest.config.js

@@ -5,6 +5,7 @@ module.exports = {
     __TEST__: true,
     __VERSION__: require('./package.json').version,
     __BROWSER__: false,
+    __BUNDLER__: false,
     __RUNTIME_COMPILE__: true,
     __FEATURE_OPTIONS__: true,
     __FEATURE_SUSPENSE__: true

+ 3 - 3
packages/compiler-core/__tests__/__snapshots__/compile.spec.ts.snap

@@ -19,7 +19,7 @@ return function render() {
         return (_openBlock(), _createBlock(\\"div\\", null, [
           _createVNode(\\"span\\", null, _toString(value + index), 1 /* TEXT */)
         ]))
-      }), 128 /* UNKEYED_FRAGMENT */))
+      }), 256 /* UNKEYED_FRAGMENT */))
     ], 2 /* CLASS */))
   }
 }"
@@ -42,7 +42,7 @@ return function render() {
       return (openBlock(), createBlock(\\"div\\", null, [
         createVNode(\\"span\\", null, toString(value + index), 1 /* TEXT */)
       ]))
-    }), 128 /* UNKEYED_FRAGMENT */))
+    }), 256 /* UNKEYED_FRAGMENT */))
   ], 2 /* CLASS */))
 }"
 `;
@@ -64,7 +64,7 @@ export default function render() {
       return (openBlock(), createBlock(\\"div\\", null, [
         createVNode(\\"span\\", null, toString(value + index), 1 /* TEXT */)
       ]))
-    }), 128 /* UNKEYED_FRAGMENT */))
+    }), 256 /* UNKEYED_FRAGMENT */))
   ], 2 /* CLASS */))
 }"
 `;

+ 4 - 1
packages/compiler-core/__tests__/transform.spec.ts

@@ -21,6 +21,8 @@ import { transformFor } from '../src/transforms/vFor'
 import { transformElement } from '../src/transforms/transformElement'
 import { transformSlotOutlet } from '../src/transforms/transformSlotOutlet'
 import { transformText } from '../src/transforms/transformText'
+import { genFlagText } from './testUtils'
+import { PatchFlags } from '@vue/shared'
 
 describe('compiler: transform', () => {
   test('context state', () => {
@@ -352,7 +354,8 @@ describe('compiler: transform', () => {
           [
             { type: NodeTypes.ELEMENT, tag: `div` },
             { type: NodeTypes.ELEMENT, tag: `div` }
-          ]
+          ],
+          genFlagText(PatchFlags.STABLE_FRAGMENT)
         ])
       )
     })

+ 3 - 3
packages/compiler-core/__tests__/transforms/__snapshots__/hoistStatic.spec.ts.snap

@@ -235,7 +235,7 @@ return function render() {
         return (_openBlock(), _createBlock(\\"p\\", null, [
           _createVNode(\\"span\\", null, _toString(o + 'foo'), 1 /* TEXT */)
         ]))
-      }), 128 /* UNKEYED_FRAGMENT */))
+      }), 256 /* UNKEYED_FRAGMENT */))
     ]))
   }
 }"
@@ -270,7 +270,7 @@ return function render() {
         return (_openBlock(), _createBlock(\\"p\\", null, [
           _createVNode(\\"span\\", null, _toString(o), 1 /* TEXT */)
         ]))
-      }), 128 /* UNKEYED_FRAGMENT */))
+      }), 256 /* UNKEYED_FRAGMENT */))
     ]))
   }
 }"
@@ -362,7 +362,7 @@ return function render() {
         return (_openBlock(), _createBlock(\\"div\\", _hoisted_1, [
           _hoisted_2
         ]))
-      }), 128 /* UNKEYED_FRAGMENT */))
+      }), 256 /* UNKEYED_FRAGMENT */))
     ]))
   }
 }"

+ 3 - 3
packages/compiler-core/__tests__/transforms/__snapshots__/transformText.spec.ts.snap

@@ -23,7 +23,7 @@ return function render() {
       _createVNode(\\"div\\"),
       _createTextVNode(_toString(foo) + \\" bar \\" + _toString(baz), 1 /* TEXT */),
       _createVNode(\\"div\\")
-    ]))
+    ], 64 /* STABLE_FRAGMENT */))
   }
 }"
 `;
@@ -41,7 +41,7 @@ return function render() {
       _createVNode(\\"div\\"),
       _createTextVNode(\\"hello\\"),
       _createVNode(\\"div\\")
-    ]))
+    ], 64 /* STABLE_FRAGMENT */))
   }
 }"
 `;
@@ -69,7 +69,7 @@ return function render() {
       _createVNode(\\"div\\"),
       _createTextVNode(\\"hello\\"),
       _createVNode(\\"div\\")
-    ]))
+    ], 64 /* STABLE_FRAGMENT */))
   }
 }"
 `;

+ 14 - 14
packages/compiler-core/__tests__/transforms/__snapshots__/vFor.spec.ts.snap

@@ -9,7 +9,7 @@ return function render() {
     
     return (_openBlock(false), _createBlock(_Fragment, null, _renderList(items, (item) => {
       return (_openBlock(), _createBlock(\\"span\\"))
-    }), 128 /* UNKEYED_FRAGMENT */))
+    }), 256 /* UNKEYED_FRAGMENT */))
   }
 }"
 `;
@@ -25,8 +25,8 @@ return function render() {
       return (_openBlock(), _createBlock(_Fragment, { key: item }, [
         \\"hello\\",
         _createVNode(\\"span\\")
-      ]))
-    }), 64 /* KEYED_FRAGMENT */))
+      ], 64 /* STABLE_FRAGMENT */))
+    }), 128 /* KEYED_FRAGMENT */))
   }
 }"
 `;
@@ -40,7 +40,7 @@ return function render() {
     
     return (_openBlock(false), _createBlock(_Fragment, null, _renderList(items, (item) => {
       return (_openBlock(), _createBlock(\\"span\\", { key: item }))
-    }), 64 /* KEYED_FRAGMENT */))
+    }), 128 /* KEYED_FRAGMENT */))
   }
 }"
 `;
@@ -54,7 +54,7 @@ return function render() {
     
     return (_openBlock(false), _createBlock(_Fragment, null, _renderList(items, (item, __, index) => {
       return (_openBlock(), _createBlock(\\"span\\"))
-    }), 128 /* UNKEYED_FRAGMENT */))
+    }), 256 /* UNKEYED_FRAGMENT */))
   }
 }"
 `;
@@ -68,7 +68,7 @@ return function render() {
     
     return (_openBlock(false), _createBlock(_Fragment, null, _renderList(items, (_, __, index) => {
       return (_openBlock(), _createBlock(\\"span\\"))
-    }), 128 /* UNKEYED_FRAGMENT */))
+    }), 256 /* UNKEYED_FRAGMENT */))
   }
 }"
 `;
@@ -82,7 +82,7 @@ return function render() {
     
     return (_openBlock(false), _createBlock(_Fragment, null, _renderList(items, (_, key, index) => {
       return (_openBlock(), _createBlock(\\"span\\"))
-    }), 128 /* UNKEYED_FRAGMENT */))
+    }), 256 /* UNKEYED_FRAGMENT */))
   }
 }"
 `;
@@ -98,8 +98,8 @@ return function render() {
       return (_openBlock(), _createBlock(_Fragment, null, [
         \\"hello\\",
         _createVNode(\\"span\\")
-      ]))
-    }), 128 /* UNKEYED_FRAGMENT */))
+      ], 64 /* STABLE_FRAGMENT */))
+    }), 256 /* UNKEYED_FRAGMENT */))
   }
 }"
 `;
@@ -113,7 +113,7 @@ return function render() {
     
     return (_openBlock(false), _createBlock(_Fragment, null, _renderList(items, (item) => {
       return _renderSlot($slots, \\"default\\")
-    }), 128 /* UNKEYED_FRAGMENT */))
+    }), 256 /* UNKEYED_FRAGMENT */))
   }
 }"
 `;
@@ -127,7 +127,7 @@ return function render() {
     
     return (_openBlock(false), _createBlock(_Fragment, null, _renderList(items, (item) => {
       return _renderSlot($slots, \\"default\\")
-    }), 128 /* UNKEYED_FRAGMENT */))
+    }), 256 /* UNKEYED_FRAGMENT */))
   }
 }"
 `;
@@ -145,7 +145,7 @@ return function render() {
       return (_openBlock(), _withDirectives(_createBlock(\\"div\\", null, null, 32 /* NEED_PATCH */), [
         [_directive_foo]
       ]))
-    }), 128 /* UNKEYED_FRAGMENT */))
+    }), 256 /* UNKEYED_FRAGMENT */))
   }
 }"
 `;
@@ -160,7 +160,7 @@ return function render() {
     return (_openBlock(), ok
       ? _createBlock(_Fragment, { key: 0 }, _renderList(list, (i) => {
           return (_openBlock(), _createBlock(\\"div\\"))
-        }), 128 /* UNKEYED_FRAGMENT */)
+        }), 256 /* UNKEYED_FRAGMENT */)
       : _createCommentVNode(\\"v-if\\", true))
   }
 }"
@@ -175,7 +175,7 @@ return function render() {
     
     return (_openBlock(false), _createBlock(_Fragment, null, _renderList(items, (item, key, index) => {
       return (_openBlock(), _createBlock(\\"span\\"))
-    }), 128 /* UNKEYED_FRAGMENT */))
+    }), 256 /* UNKEYED_FRAGMENT */))
   }
 }"
 `;

+ 6 - 6
packages/compiler-core/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap

@@ -11,7 +11,7 @@ return function render() {
     [_ctx.one]: ({ foo }) => [toString(foo), toString(_ctx.bar)],
     [_ctx.two]: ({ bar }) => [toString(_ctx.foo), toString(bar)],
     _compiled: true
-  }, 256 /* DYNAMIC_SLOTS */))
+  }, 512 /* DYNAMIC_SLOTS */))
 }"
 `;
 
@@ -59,7 +59,7 @@ return function render() {
         fn: () => [toString(name)]
       }
     })
-  ]), 256 /* DYNAMIC_SLOTS */))
+  ]), 512 /* DYNAMIC_SLOTS */))
 }"
 `;
 
@@ -77,7 +77,7 @@ return function render() {
           fn: (props) => [toString(props)]
         }
       : undefined
-  ]), 256 /* DYNAMIC_SLOTS */))
+  ]), 512 /* DYNAMIC_SLOTS */))
 }"
 `;
 
@@ -105,7 +105,7 @@ return function render() {
               name: \\"one\\",
               fn: () => [\\"baz\\"]
             }
-    ]), 256 /* DYNAMIC_SLOTS */))
+    ]), 512 /* DYNAMIC_SLOTS */))
   }
 }"
 `;
@@ -126,7 +126,7 @@ return function render() {
             fn: () => [\\"hello\\"]
           }
         : undefined
-    ]), 256 /* DYNAMIC_SLOTS */))
+    ]), 512 /* DYNAMIC_SLOTS */))
   }
 }"
 `;
@@ -159,7 +159,7 @@ return function render() {
       createVNode(_component_Inner, null, {
         default: ({ bar }) => [toString(foo), toString(bar), toString(_ctx.baz)],
         _compiled: true
-      }, 256 /* DYNAMIC_SLOTS */),
+      }, 512 /* DYNAMIC_SLOTS */),
       \\" \\",
       toString(foo),
       toString(_ctx.bar),

+ 5 - 3
packages/compiler-core/__tests__/transforms/vFor.spec.ts

@@ -25,7 +25,7 @@ import {
   RENDER_SLOT,
   WITH_DIRECTIVES
 } from '../../src/runtimeHelpers'
-import { PatchFlags } from '@vue/runtime-dom'
+import { PatchFlags } from '@vue/shared'
 import { createObjectMatcher, genFlagText } from '../testUtils'
 
 function parseWithForTransform(
@@ -704,7 +704,8 @@ describe('compiler: v-for', () => {
           [
             { type: NodeTypes.TEXT, content: `hello` },
             { type: NodeTypes.ELEMENT, tag: `span` }
-          ]
+          ],
+          genFlagText(PatchFlags.STABLE_FRAGMENT)
         ]
       })
       expect(generate(root).code).toMatchSnapshot()
@@ -784,7 +785,8 @@ describe('compiler: v-for', () => {
           [
             { type: NodeTypes.TEXT, content: `hello` },
             { type: NodeTypes.ELEMENT, tag: `span` }
-          ]
+          ],
+          genFlagText(PatchFlags.STABLE_FRAGMENT)
         ]
       })
       expect(generate(root).code).toMatchSnapshot()

+ 2 - 2
packages/compiler-core/src/transform.ts

@@ -290,8 +290,8 @@ function finalizeRoot(root: RootNode, context: TransformContext) {
         helper(FRAGMENT),
         `null`,
         root.children,
-        `${PatchFlags.UNKEYED_FRAGMENT} /* ${
-          PatchFlagNames[PatchFlags.UNKEYED_FRAGMENT]
+        `${PatchFlags.STABLE_FRAGMENT} /* ${
+          PatchFlagNames[PatchFlags.STABLE_FRAGMENT]
         } */`
       ]),
       context

+ 4 - 1
packages/compiler-core/src/transforms/vFor.ts

@@ -146,7 +146,10 @@ export const transformFor = createStructuralDirectiveTransform(
           createCallExpression(helper(CREATE_BLOCK), [
             helper(FRAGMENT),
             keyProperty ? createObjectExpression([keyProperty]) : `null`,
-            node.children
+            node.children,
+            `${PatchFlags.STABLE_FRAGMENT} /* ${
+              PatchFlagNames[PatchFlags.STABLE_FRAGMENT]
+            } */`
           ]),
           context
         )

+ 1 - 1
packages/compiler-dom/__tests__/__snapshots__/index.spec.ts.snap

@@ -16,7 +16,7 @@ return function render() {
       _createVNode(\\"div\\", null, \\"test\\"),
       _createVNode(\\"div\\", { style: _hoisted_1 }, \\"red\\"),
       _createVNode(\\"div\\", { style: {color: 'green'} }, null, 4 /* STYLE */)
-    ]))
+    ], 64 /* STABLE_FRAGMENT */))
   }
 }"
 `;

+ 1 - 1
packages/compiler-sfc/__tests__/__snapshots__/templateTransformAssetUrl.spec.ts.snap

@@ -34,6 +34,6 @@ export default function render() {
     createVNode(\\"img\\", { src: _imports_0 }),
     createVNode(\\"img\\", { src: _imports_1 }),
     createVNode(\\"img\\", { src: _imports_1 })
-  ]))
+  ], 64 /* STABLE_FRAGMENT */))
 }"
 `;

+ 1 - 1
packages/compiler-sfc/__tests__/__snapshots__/templateTransformSrcset.spec.ts.snap

@@ -44,6 +44,6 @@ export default function render() {
       src: \\"./logo.png\\",
       srcset: _hoisted_7
     })
-  ]))
+  ], 64 /* STABLE_FRAGMENT */))
 }"
 `;

+ 11 - 3
packages/runtime-core/__tests__/rendererFragment.spec.ts

@@ -10,7 +10,8 @@ import {
   resetOps,
   dumpOps,
   NodeOpTypes,
-  serializeInner
+  serializeInner,
+  createTextVNode
 } from '@vue/runtime-test'
 
 describe('renderer: fragment', () => {
@@ -110,7 +111,10 @@ describe('renderer: fragment', () => {
       createVNode(
         Fragment,
         null,
-        [h('div', 'one'), 'two'],
+        [
+          createVNode('div', null, 'one', PatchFlags.TEXT),
+          createTextVNode('two')
+        ],
         PatchFlags.UNKEYED_FRAGMENT
       ),
       root
@@ -121,7 +125,11 @@ describe('renderer: fragment', () => {
       createVNode(
         Fragment,
         null,
-        [h('div', 'foo'), 'bar', 'baz'],
+        [
+          createVNode('div', null, 'foo', PatchFlags.TEXT),
+          createTextVNode('bar'),
+          createTextVNode('baz')
+        ],
         PatchFlags.KEYED_FRAGMENT
       ),
       root

+ 1 - 0
packages/runtime-core/src/index.ts

@@ -44,6 +44,7 @@ export const PatchFlags = PublicPatchFlags as {
   PROPS: number
   NEED_PATCH: number
   FULL_PROPS: number
+  STABLE_FRAGMENT: number
   KEYED_FRAGMENT: number
   UNKEYED_FRAGMENT: number
   DYNAMIC_SLOTS: number

+ 34 - 14
packages/runtime-core/src/renderer.ts

@@ -654,10 +654,14 @@ export function createRenderer<
     const fragmentEndAnchor = (n2.anchor = n1
       ? n1.anchor
       : hostCreateComment(showID ? `fragment-${devFragmentID}-end` : ''))!
-    if (showID) {
-      devFragmentID++
+    const { patchFlag } = n2
+    if (patchFlag > 0) {
+      optimized = true
     }
     if (n1 == null) {
+      if (showID) {
+        devFragmentID++
+      }
       hostInsert(fragmentStartAnchor, container, anchor)
       hostInsert(fragmentEndAnchor, container, anchor)
       // a fragment can only have array children
@@ -673,16 +677,33 @@ export function createRenderer<
         optimized
       )
     } else {
-      patchChildren(
-        n1,
-        n2,
-        container,
-        fragmentEndAnchor,
-        parentComponent,
-        parentSuspense,
-        isSVG,
-        optimized
-      )
+      if (patchFlag & PatchFlags.STABLE_FRAGMENT && n2.dynamicChildren) {
+        // a stable fragment (template root or <template v-for>) doesn't need to
+        // patch children order, but it may contain dynamicChildren.
+        patchBlockChildren(
+          n1.dynamicChildren!,
+          n2.dynamicChildren,
+          container,
+          parentComponent,
+          parentSuspense,
+          isSVG
+        )
+      } else {
+        // keyed / unkeyed, or manual fragments.
+        // for keyed & unkeyed, since they are compiler generated from v-for,
+        // each child is guarunteed to be a block so the fragment will never
+        // have dynamicChildren.
+        patchChildren(
+          n1,
+          n2,
+          container,
+          fragmentEndAnchor,
+          parentComponent,
+          parentSuspense,
+          isSVG,
+          optimized
+        )
+      }
     }
   }
 
@@ -1033,7 +1054,6 @@ export function createRenderer<
     }
     // fast path
     if (patchFlag > 0) {
-      optimized = true
       if (patchFlag & PatchFlags.KEYED_FRAGMENT) {
         // this could be either fully-keyed or mixed (some keyed some not)
         // presence of patchFlag means children are guaranteed to be arrays
@@ -1215,7 +1235,7 @@ export function createRenderer<
     while (i <= e1 && i <= e2) {
       const n1 = c1[e1]
       const n2 = optimized
-        ? (c2[i] as HostVNode)
+        ? (c2[e2] as HostVNode)
         : (c2[e2] = normalizeVNode(c2[e2]))
       if (isSameVNodeType(n1, n2)) {
         patch(

+ 7 - 3
packages/shared/src/patchFlags.ts

@@ -47,16 +47,19 @@ export const enum PatchFlags {
   // value.
   NEED_PATCH = 1 << 5,
 
+  // Indicates a fragment whose children order doesn't change.
+  STABLE_FRAGMENT = 1 << 6,
+
   // Indicates a fragment with keyed or partially keyed children
-  KEYED_FRAGMENT = 1 << 6,
+  KEYED_FRAGMENT = 1 << 7,
 
   // Indicates a fragment with unkeyed children.
-  UNKEYED_FRAGMENT = 1 << 7,
+  UNKEYED_FRAGMENT = 1 << 8,
 
   // Indicates a component with dynamic slots (e.g. slot that references a v-for
   // iterated value, or dynamic slot names).
   // Components with this flag are always force updated.
-  DYNAMIC_SLOTS = 1 << 8,
+  DYNAMIC_SLOTS = 1 << 9,
 
   // A special flag that indicates that the diffing algorithm should bail out
   // of optimized mode. This is only on block fragments created by renderSlot()
@@ -87,6 +90,7 @@ export const PatchFlagNames = {
   [PatchFlags.PROPS]: `PROPS`,
   [PatchFlags.NEED_PATCH]: `NEED_PATCH`,
   [PatchFlags.FULL_PROPS]: `FULL_PROPS`,
+  [PatchFlags.STABLE_FRAGMENT]: `STABLE_FRAGMENT`,
   [PatchFlags.KEYED_FRAGMENT]: `KEYED_FRAGMENT`,
   [PatchFlags.UNKEYED_FRAGMENT]: `UNKEYED_FRAGMENT`,
   [PatchFlags.DYNAMIC_SLOTS]: `DYNAMIC_SLOTS`,

+ 5 - 1
packages/vue/examples/__tests__/e2eUtils.ts

@@ -14,7 +14,11 @@ export function setupPuppeteer() {
 
     page.on('console', e => {
       if (e.type() === 'error') {
-        console.error(`Error from Puppeteer-loaded page:`, e)
+        const err = e.args()[0] as any
+        console.error(
+          `Error from Puppeteer-loaded page:\n`,
+          err._remoteObject.description
+        )
       }
     })
   })