]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(compiler-core): handle template root and template v-if as stable fragments
authorEvan You <yyx990803@gmail.com>
Fri, 13 Dec 2019 01:46:20 +0000 (20:46 -0500)
committerEvan You <yyx990803@gmail.com>
Fri, 13 Dec 2019 02:09:47 +0000 (21:09 -0500)
18 files changed:
jest.config.js
packages/compiler-core/__tests__/__snapshots__/compile.spec.ts.snap
packages/compiler-core/__tests__/transform.spec.ts
packages/compiler-core/__tests__/transforms/__snapshots__/hoistStatic.spec.ts.snap
packages/compiler-core/__tests__/transforms/__snapshots__/transformText.spec.ts.snap
packages/compiler-core/__tests__/transforms/__snapshots__/vFor.spec.ts.snap
packages/compiler-core/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap
packages/compiler-core/__tests__/transforms/vFor.spec.ts
packages/compiler-core/src/transform.ts
packages/compiler-core/src/transforms/vFor.ts
packages/compiler-dom/__tests__/__snapshots__/index.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__/rendererFragment.spec.ts
packages/runtime-core/src/index.ts
packages/runtime-core/src/renderer.ts
packages/shared/src/patchFlags.ts
packages/vue/examples/__tests__/e2eUtils.ts

index 2bec8c31377eb96fdd40598e5da3b1d711f8f6be..5d9067c8ecaa0b0912a7464953ec1c7eb4a208db 100644 (file)
@@ -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
index 3f7f1c595910f76b37205b594c443d360c16ffe6..e3e4c726c0ab024f11f14d37a5e3c3913d9444b2 100644 (file)
@@ -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 */))
 }"
 `;
index baaddd788fee36599e3799f4e6653684b884b282..fb67a5d1ff9d8aaebf3ceab7e705f0d11343b627 100644 (file)
@@ -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)
         ])
       )
     })
index 8d83391ba25795422ea64ce743a9143990eb3693..5173080d2881ef4f495c333abacfc24724244915 100644 (file)
@@ -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 */))
     ]))
   }
 }"
index e0327d8a477d12d46b0d4303571118fa8fd6afca..a40a6f44dc2aaabaa40263cc325c31f337471d6c 100644 (file)
@@ -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 */))
   }
 }"
 `;
index 372b077124ae2ba311b43b2fd9aaa620ce5acad3..edf9326f638fd43377c7d0549398ca103a4052a0 100644 (file)
@@ -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 */))
   }
 }"
 `;
index dd819d03a0eab8b4b423745bd5d43c2c7fb4c28e..c7796e246e602fc3fc92e6f64927a9d1473def3f 100644 (file)
@@ -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),
index 39b34c532d7867bb7563f1e34bad7484890ca265..93b6bc9031901c796abf671407e0ea5a4c4975a6 100644 (file)
@@ -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()
index dfa73edc3e607ac51be0790ca178aaf32ee04094..0e32d29884213849c5024034e0ba1bfee9040ad0 100644 (file)
@@ -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
index 418999cbc1fc9b5b99c31e6796e7a4e06af6dfb6..a31a47e8edf26600e10f6ea51d12b4f0569dba70 100644 (file)
@@ -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
         )
index 1b8d51ebb2f842a195c97f49e253186ca412cc24..7c9d04a2345c21158de6d965c2142e7e55712cd3 100644 (file)
@@ -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 */))
   }
 }"
 `;
index 4004ddbcf078a0fb7daacddc023d3d11df3d7314..580a70b4f7903df227bf978fe451123a67838c81 100644 (file)
@@ -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 */))
 }"
 `;
index 444b0ea4d366841d15954cc471ede2bef41d0599..2ea6edbd6024f9688b5d63aa1519cba50c94cc24 100644 (file)
@@ -44,6 +44,6 @@ export default function render() {
       src: \\"./logo.png\\",
       srcset: _hoisted_7
     })
-  ]))
+  ], 64 /* STABLE_FRAGMENT */))
 }"
 `;
index 8c6aef0167e355689684837de6658e58402c9cf5..7c4be2b3f5b7474a4495e283100ab50c3473a6b2 100644 (file)
@@ -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
index de44fdd013f9608901bdb2536875e6e74280c782..2629f0f379436361c0c4b48ed6db050733d39829 100644 (file)
@@ -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
index 3a27390e106d94f3fe3d370b58e60c05dcd039c7..b3dbc52d09832c73328c6d1af4b80c1b9b8da974 100644 (file)
@@ -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(
index 48fe939b0d18c7d96151369b59c9812382cb604e..de1647154abfef5283e67606be9e2b2646bece1f 100644 (file)
@@ -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`,
index b39f01ffae841b107f3ce1145274427d9a3b3d75..8d5ae479f9127a5c46402aa6d8e3be622d55fd85 100644 (file)
@@ -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
+        )
       }
     })
   })