From 364f319d214226770d97c98d8fcada80c9e8dde3 Mon Sep 17 00:00:00 2001 From: Evan You Date: Fri, 10 Nov 2023 13:13:07 +0800 Subject: [PATCH] fix(hydration): force hydration for v-bind with .prop modifier ref #7490 --- .../__snapshots__/vModel.spec.ts.snap | 2 +- .../transforms/transformElement.spec.ts | 20 +++++++++++++++---- .../src/transforms/transformElement.ts | 11 +++++++--- .../__tests__/transforms/vOn.spec.ts | 2 +- .../runtime-core/__tests__/hydration.spec.ts | 12 +++++++++++ packages/runtime-core/__tests__/vnode.spec.ts | 4 ++-- packages/runtime-core/src/hydration.ts | 6 ++++-- packages/runtime-core/src/renderer.ts | 2 +- packages/runtime-core/src/vnode.ts | 2 +- packages/shared/src/patchFlags.ts | 9 +++++---- 10 files changed, 51 insertions(+), 19 deletions(-) diff --git a/packages/compiler-core/__tests__/transforms/__snapshots__/vModel.spec.ts.snap b/packages/compiler-core/__tests__/transforms/__snapshots__/vModel.spec.ts.snap index e59df2d5458..983fe1223aa 100644 --- a/packages/compiler-core/__tests__/transforms/__snapshots__/vModel.spec.ts.snap +++ b/packages/compiler-core/__tests__/transforms/__snapshots__/vModel.spec.ts.snap @@ -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\\"])) } }" `; diff --git a/packages/compiler-core/__tests__/transforms/transformElement.spec.ts b/packages/compiler-core/__tests__/transforms/transformElement.spec.ts index a1ae013a830..97559369d8a 100644 --- a/packages/compiler-core/__tests__/transforms/transformElement.spec.ts +++ b/packages/compiler-core/__tests__/transforms/transformElement.spec.ts @@ -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(`
`, { 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(`
`) + expect(node.patchFlag).toBe( + genFlagText([PatchFlags.PROPS, PatchFlags.NEED_HYDRATION]) + ) + + const { node: node2 } = parseWithBind(`
`) + 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( ``, { @@ -1123,7 +1135,7 @@ describe('compiler: element transform', () => { } ) expect(node.patchFlag).toBe( - genFlagText([PatchFlags.PROPS, PatchFlags.HYDRATE_EVENTS]) + genFlagText([PatchFlags.PROPS, PatchFlags.NEED_HYDRATION]) ) }) }) diff --git a/packages/compiler-core/src/transforms/transformElement.ts b/packages/compiler-core/src/transforms/transformElement.ts index 253b6be5efa..fd61f011051 100644 --- a/packages/compiler-core/src/transforms/transformElement.ts +++ b/packages/compiler-core/src/transforms/transformElement.ts @@ -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 diff --git a/packages/compiler-dom/__tests__/transforms/vOn.spec.ts b/packages/compiler-dom/__tests__/transforms/vOn.spec.ts index efc7fee374f..79ffcdef03c 100644 --- a/packages/compiler-dom/__tests__/transforms/vOn.spec.ts +++ b/packages/compiler-dom/__tests__/transforms/vOn.spec.ts @@ -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: { diff --git a/packages/runtime-core/__tests__/hydration.spec.ts b/packages/runtime-core/__tests__/hydration.spec.ts index e54960222c4..7ea607d3380 100644 --- a/packages/runtime-core/__tests__/hydration.spec.ts +++ b/packages/runtime-core/__tests__/hydration.spec.ts @@ -935,6 +935,18 @@ describe('SSR hydration', () => { ) }) + test('force hydrate prop with `.prop` modifier', () => { + const { container } = mountWithHydration( + '', + () => + 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( '', diff --git a/packages/runtime-core/__tests__/vnode.spec.ts b/packages/runtime-core/__tests__/vnode.spec.ts index 02774aafee4..11b2044a9e3 100644 --- a/packages/runtime-core/__tests__/vnode.spec.ts +++ b/packages/runtime-core/__tests__/vnode.spec.ts @@ -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([]) }) diff --git a/packages/runtime-core/src/hydration.ts b/packages/runtime-core/src/hydration.ts index 0e94495878e..516823c70c5 100644 --- a/packages/runtime-core/src/hydration.ts +++ b/packages/runtime-core/src/hydration.ts @@ -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, diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts index 8799ecd473c..8a3b4ffa3af 100644 --- a/packages/runtime-core/src/renderer.ts +++ b/packages/runtime-core/src/renderer.ts @@ -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 } diff --git a/packages/runtime-core/src/vnode.ts b/packages/runtime-core/src/vnode.ts index 10ee03c29c6..04f1150e346 100644 --- a/packages/runtime-core/src/vnode.ts +++ b/packages/runtime-core/src/vnode.ts @@ -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) } diff --git a/packages/shared/src/patchFlags.ts b/packages/shared/src/patchFlags.ts index 58e8935aeb8..af5ee7b49e2 100644 --- a/packages/shared/src/patchFlags.ts +++ b/packages/shared/src/patchFlags.ts @@ -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.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`,