]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(hydration): force hydration for v-bind with .prop modifier
authorEvan You <yyx990803@gmail.com>
Fri, 10 Nov 2023 05:13:07 +0000 (13:13 +0800)
committerEvan You <yyx990803@gmail.com>
Fri, 10 Nov 2023 05:13:07 +0000 (13:13 +0800)
ref #7490

packages/compiler-core/__tests__/transforms/__snapshots__/vModel.spec.ts.snap
packages/compiler-core/__tests__/transforms/transformElement.spec.ts
packages/compiler-core/src/transforms/transformElement.ts
packages/compiler-dom/__tests__/transforms/vOn.spec.ts
packages/runtime-core/__tests__/hydration.spec.ts
packages/runtime-core/__tests__/vnode.spec.ts
packages/runtime-core/src/hydration.ts
packages/runtime-core/src/renderer.ts
packages/runtime-core/src/vnode.ts
packages/shared/src/patchFlags.ts

index e59df2d545809914fe3fea1858d21003a28afe58..983fe1223aa8fa02c0cabc560b54cef86d6125d7 100644 (file)
@@ -85,7 +85,7 @@ return function render(_ctx, _cache) {
     return (_openBlock(), _createElementBlock(\\"input\\", {
       \\"foo-value\\": model,
       \\"onUpdate:fooValue\\": $event => ((model) = $event)
-    }, null, 40 /* PROPS, HYDRATE_EVENTS */, [\\"foo-value\\", \\"onUpdate:fooValue\\"]))
+    }, null, 40 /* PROPS, NEED_HYDRATION */, [\\"foo-value\\", \\"onUpdate:fooValue\\"]))
   }
 }"
 `;
index a1ae013a8306b74872f776940ab516ff8a6b4258..97559369d8a7307df2e4bd7d0ac5c56528d6a039 100644 (file)
@@ -1089,7 +1089,7 @@ describe('compiler: element transform', () => {
       })
     })
 
-    test('HYDRATE_EVENTS', () => {
+    test('NEED_HYDRATION for v-on', () => {
       // ignore click events (has dedicated fast path)
       const { node } = parseWithElementTransform(`<div @click="foo" />`, {
         directiveTransforms: {
@@ -1108,12 +1108,24 @@ describe('compiler: element transform', () => {
         }
       )
       expect(node2.patchFlag).toBe(
-        genFlagText([PatchFlags.PROPS, PatchFlags.HYDRATE_EVENTS])
+        genFlagText([PatchFlags.PROPS, PatchFlags.NEED_HYDRATION])
+      )
+    })
+
+    test('NEED_HYDRATION for v-bind.prop', () => {
+      const { node } = parseWithBind(`<div v-bind:id.prop="id" />`)
+      expect(node.patchFlag).toBe(
+        genFlagText([PatchFlags.PROPS, PatchFlags.NEED_HYDRATION])
+      )
+
+      const { node: node2 } = parseWithBind(`<div .id="id" />`)
+      expect(node2.patchFlag).toBe(
+        genFlagText([PatchFlags.PROPS, PatchFlags.NEED_HYDRATION])
       )
     })
 
     // #5870
-    test('HYDRATE_EVENTS on dynamic component', () => {
+    test('NEED_HYDRATION on dynamic component', () => {
       const { node } = parseWithElementTransform(
         `<component :is="foo" @input="foo" />`,
         {
@@ -1123,7 +1135,7 @@ describe('compiler: element transform', () => {
         }
       )
       expect(node.patchFlag).toBe(
-        genFlagText([PatchFlags.PROPS, PatchFlags.HYDRATE_EVENTS])
+        genFlagText([PatchFlags.PROPS, PatchFlags.NEED_HYDRATION])
       )
     })
   })
index 253b6be5efa1948cd0150d141b887ae3b78b3f6d..fd61f011051e82539552f151539b5ab3471166a5 100644 (file)
@@ -550,7 +550,7 @@ export function buildProps(
       )
     } else {
       // directives
-      const { name, arg, exp, loc } = prop
+      const { name, arg, exp, loc, modifiers } = prop
       const isVBind = name === 'bind'
       const isVOn = name === 'on'
 
@@ -678,6 +678,11 @@ export function buildProps(
         continue
       }
 
+      // force hydration for v-bind with .prop modifier
+      if (isVBind && modifiers.includes('prop')) {
+        patchFlag |= PatchFlags.NEED_HYDRATION
+      }
+
       const directiveTransform = context.directiveTransforms[name]
       if (directiveTransform) {
         // has built-in directive transform.
@@ -743,12 +748,12 @@ export function buildProps(
       patchFlag |= PatchFlags.PROPS
     }
     if (hasHydrationEventBinding) {
-      patchFlag |= PatchFlags.HYDRATE_EVENTS
+      patchFlag |= PatchFlags.NEED_HYDRATION
     }
   }
   if (
     !shouldUseBlock &&
-    (patchFlag === 0 || patchFlag === PatchFlags.HYDRATE_EVENTS) &&
+    (patchFlag === 0 || patchFlag === PatchFlags.NEED_HYDRATION) &&
     (hasRef || hasVnodeHook || runtimeDirectives.length > 0)
   ) {
     patchFlag |= PatchFlags.NEED_PATCH
index efc7fee374f7f321c48b4d81b565c74e556f5721..79ffcdef03c94fbb1e79c8f3955882884e0fdcc1 100644 (file)
@@ -272,7 +272,7 @@ describe('compiler-dom: transform v-on', () => {
     // 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(
-      genFlagText(PatchFlags.HYDRATE_EVENTS)
+      genFlagText(PatchFlags.NEED_HYDRATION)
     )
     expect(prop).toMatchObject({
       key: {
index e54960222c4ef61c9012870da08a8db9b2d13c82..7ea607d338080c1ead7e68b9e35ecb7ff6fae1f8 100644 (file)
@@ -935,6 +935,18 @@ describe('SSR hydration', () => {
     )
   })
 
+  test('force hydrate prop with `.prop` modifier', () => {
+    const { container } = mountWithHydration(
+      '<input type="checkbox" :indeterminate.prop="true">',
+      () =>
+        h('input', {
+          type: 'checkbox',
+          '.indeterminate': true
+        })
+    )
+    expect((container.firstChild! as any).indeterminate).toBe(true)
+  })
+
   test('force hydrate input v-model with non-string value bindings', () => {
     const { container } = mountWithHydration(
       '<input type="checkbox" value="true">',
index 02774aafee4b84b1df539edd6cac3020051e5180..11b2044a9e3650f9987dd8cd80e2e1438ae1ac28 100644 (file)
@@ -477,13 +477,13 @@ describe('vnode', () => {
       expect(vnode.dynamicChildren).toStrictEqual([vnode1])
     })
 
-    test('should not track vnodes with only HYDRATE_EVENTS flag', () => {
+    test('should not track vnodes with only NEED_HYDRATION flag', () => {
       const hoist = createVNode('div')
       const vnode =
         (openBlock(),
         createBlock('div', null, [
           hoist,
-          createVNode('div', null, 'text', PatchFlags.HYDRATE_EVENTS)
+          createVNode('div', null, 'text', PatchFlags.NEED_HYDRATION)
         ]))
       expect(vnode.dynamicChildren).toStrictEqual([])
     })
index 0e94495878ed70cacd83eab54a4bf17ea158efec..516823c70c56ee29528ae24789a8f9228dd488b2 100644 (file)
@@ -334,13 +334,15 @@ export function createHydrationFunctions(
         if (
           forcePatch ||
           !optimized ||
-          patchFlag & (PatchFlags.FULL_PROPS | PatchFlags.HYDRATE_EVENTS)
+          patchFlag & (PatchFlags.FULL_PROPS | PatchFlags.NEED_HYDRATION)
         ) {
           for (const key in props) {
             if (
               (forcePatch &&
                 (key.endsWith('value') || key === 'indeterminate')) ||
-              (isOn(key) && !isReservedProp(key))
+              (isOn(key) && !isReservedProp(key)) ||
+              // force hydrate v-bind with .prop modifiers
+              key[0] === '.'
             ) {
               patchProp(
                 el,
index 8799ecd473ca8ddc167970ed7acee7347c10ffce..8a3b4ffa3af53882a221cb4a502a239aea117c4d 100644 (file)
@@ -2395,7 +2395,7 @@ export function traverseStaticChildren(n1: VNode, n2: VNode, shallow = false) {
       const c1 = ch1[i] as VNode
       let c2 = ch2[i] as VNode
       if (c2.shapeFlag & ShapeFlags.ELEMENT && !c2.dynamicChildren) {
-        if (c2.patchFlag <= 0 || c2.patchFlag === PatchFlags.HYDRATE_EVENTS) {
+        if (c2.patchFlag <= 0 || c2.patchFlag === PatchFlags.NEED_HYDRATION) {
           c2 = ch2[i] = cloneIfMounted(ch2[i] as VNode)
           c2.el = c1.el
         }
index 10ee03c29c6c230225a3ee476915b0c1e6d780f2..04f1150e346ae43a02654a94e18a006f0d357c36 100644 (file)
@@ -489,7 +489,7 @@ function createBaseVNode(
     (vnode.patchFlag > 0 || shapeFlag & ShapeFlags.COMPONENT) &&
     // the EVENTS flag is only for hydration and if it is the only flag, the
     // vnode should not be considered dynamic due to handler caching.
-    vnode.patchFlag !== PatchFlags.HYDRATE_EVENTS
+    vnode.patchFlag !== PatchFlags.NEED_HYDRATION
   ) {
     currentBlock.push(vnode)
   }
index 58e8935aeb8b496c50eb61afef5ebcdc897c63b2..af5ee7b49e2e698d33c6967d5e3c8cee313bce40 100644 (file)
@@ -57,10 +57,11 @@ export const enum PatchFlags {
   FULL_PROPS = 1 << 4,
 
   /**
-   * Indicates an element with event listeners (which need to be attached
-   * during hydration)
+   * Indicates an element that requires props hydration
+   * (but not necessarily patching)
+   * e.g. event listeners & v-bind with prop modifier
    */
-  HYDRATE_EVENTS = 1 << 5,
+  NEED_HYDRATION = 1 << 5,
 
   /**
    * Indicates a fragment whose children order doesn't change.
@@ -131,7 +132,7 @@ export const PatchFlagNames: Record<PatchFlags, string> = {
   [PatchFlags.STYLE]: `STYLE`,
   [PatchFlags.PROPS]: `PROPS`,
   [PatchFlags.FULL_PROPS]: `FULL_PROPS`,
-  [PatchFlags.HYDRATE_EVENTS]: `HYDRATE_EVENTS`,
+  [PatchFlags.NEED_HYDRATION]: `NEED_HYDRATION`,
   [PatchFlags.STABLE_FRAGMENT]: `STABLE_FRAGMENT`,
   [PatchFlags.KEYED_FRAGMENT]: `KEYED_FRAGMENT`,
   [PatchFlags.UNKEYED_FRAGMENT]: `UNKEYED_FRAGMENT`,