From c03459b9b6d3c18450235bc4074a603677996320 Mon Sep 17 00:00:00 2001 From: Roan Kattouw Date: Tue, 17 May 2022 02:52:44 -0700 Subject: [PATCH] fix(ssr): support client-compiled v-model with dynamic type during ssr (#5787) fix #5786 --- packages/runtime-dom/src/directives/vModel.ts | 56 +++++++---- .../__tests__/ssrDirectives.spec.ts | 95 +++++++++++++++++++ 2 files changed, 131 insertions(+), 20 deletions(-) diff --git a/packages/runtime-dom/src/directives/vModel.ts b/packages/runtime-dom/src/directives/vModel.ts index 1a14decd..722b4d9b 100644 --- a/packages/runtime-dom/src/directives/vModel.ts +++ b/packages/runtime-dom/src/directives/vModel.ts @@ -269,6 +269,24 @@ export const vModelDynamic: ObjectDirective< } } +function resolveDynamicModel(tagName: string, type: string | undefined) { + switch (tagName) { + case 'SELECT': + return vModelSelect + case 'TEXTAREA': + return vModelText + default: + switch (type) { + case 'checkbox': + return vModelCheckbox + case 'radio': + return vModelRadio + default: + return vModelText + } + } +} + function callModelHook( el: HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement, binding: DirectiveBinding, @@ -276,26 +294,10 @@ function callModelHook( prevVNode: VNode | null, hook: keyof ObjectDirective ) { - let modelToUse: ObjectDirective - switch (el.tagName) { - case 'SELECT': - modelToUse = vModelSelect - break - case 'TEXTAREA': - modelToUse = vModelText - break - default: - switch (vnode.props && vnode.props.type) { - case 'checkbox': - modelToUse = vModelCheckbox - break - case 'radio': - modelToUse = vModelRadio - break - default: - modelToUse = vModelText - } - } + const modelToUse = resolveDynamicModel( + el.tagName, + vnode.props && vnode.props.type + ) const fn = modelToUse[hook] as DirectiveHook fn && fn(el, binding, vnode, prevVNode) } @@ -324,4 +326,18 @@ export function initVModelForSSR() { return { checked: true } } } + + vModelDynamic.getSSRProps = (binding, vnode) => { + if (typeof vnode.type !== 'string') { + return + } + const modelToUse = resolveDynamicModel( + // resolveDynamicModel expects an uppercase tag name, but vnode.type is lowercase + vnode.type.toUpperCase(), + vnode.props && vnode.props.type + ) + if (modelToUse.getSSRProps) { + return modelToUse.getSSRProps(binding, vnode) + } + } } diff --git a/packages/server-renderer/__tests__/ssrDirectives.spec.ts b/packages/server-renderer/__tests__/ssrDirectives.spec.ts index 3e8bd2e0..74b01204 100644 --- a/packages/server-renderer/__tests__/ssrDirectives.spec.ts +++ b/packages/server-renderer/__tests__/ssrDirectives.spec.ts @@ -11,6 +11,7 @@ import { vModelText, vModelRadio, vModelCheckbox, + vModelDynamic, resolveDirective } from 'vue' import { ssrGetDirectiveProps, ssrRenderAttrs } from '../src' @@ -376,6 +377,100 @@ describe('ssr: directives', () => { }) }) + describe('vnode v-model dynamic', () => { + test('text', async () => { + expect( + await renderToString( + createApp({ + render() { + return withDirectives(h('input'), [[vModelDynamic, 'hello']]) + } + }) + ) + ).toBe(``) + }) + + test('radio', async () => { + expect( + await renderToString( + createApp({ + render() { + return withDirectives( + h('input', { type: 'radio', value: 'hello' }), + [[vModelDynamic, 'hello']] + ) + } + }) + ) + ).toBe(``) + + expect( + await renderToString( + createApp({ + render() { + return withDirectives( + h('input', { type: 'radio', value: 'hello' }), + [[vModelDynamic, 'foo']] + ) + } + }) + ) + ).toBe(``) + }) + + test('checkbox', async () => { + expect( + await renderToString( + createApp({ + render() { + return withDirectives(h('input', { type: 'checkbox' }), [ + [vModelDynamic, true] + ]) + } + }) + ) + ).toBe(``) + + expect( + await renderToString( + createApp({ + render() { + return withDirectives(h('input', { type: 'checkbox' }), [ + [vModelDynamic, false] + ]) + } + }) + ) + ).toBe(``) + + expect( + await renderToString( + createApp({ + render() { + return withDirectives( + h('input', { type: 'checkbox', value: 'foo' }), + [[vModelDynamic, ['foo']]] + ) + } + }) + ) + ).toBe(``) + + expect( + await renderToString( + createApp({ + render() { + return withDirectives( + h('input', { type: 'checkbox', value: 'foo' }), + [[vModelDynamic, []]] + ) + } + }) + ) + ).toBe(``) + }) + }) + test('custom directive w/ getSSRProps (vdom)', async () => { expect( await renderToString(