From c95db5de50cd3de40619112bfc1977dec8e7c379 Mon Sep 17 00:00:00 2001 From: Rairn <958414905@qq.com> Date: Mon, 19 Sep 2022 22:43:17 +0800 Subject: [PATCH 1/3] test(runtime-core): the result of client render should be the same as server render --- packages/compiler-dom/src/index.ts | 7 +++++-- .../runtime-core/__tests__/hydration.spec.ts | 21 +++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/packages/compiler-dom/src/index.ts b/packages/compiler-dom/src/index.ts index 2c6f71cefbb..89c005d8e50 100644 --- a/packages/compiler-dom/src/index.ts +++ b/packages/compiler-dom/src/index.ts @@ -17,11 +17,14 @@ import { transformModel } from './transforms/vModel' import { transformOn } from './transforms/vOn' import { transformShow } from './transforms/vShow' import { transformTransition } from './transforms/Transition' -import { stringifyStatic } from './transforms/stringifyStatic' +import { + stringifyStatic, + StringifyThresholds +} from './transforms/stringifyStatic' import { ignoreSideEffectTags } from './transforms/ignoreSideEffectTags' import { extend } from '@vue/shared' -export { parserOptions } +export { parserOptions, StringifyThresholds } export const DOMNodeTransforms: NodeTransform[] = [ transformStyle, diff --git a/packages/runtime-core/__tests__/hydration.spec.ts b/packages/runtime-core/__tests__/hydration.spec.ts index 17ffbb40092..551f5e6925a 100644 --- a/packages/runtime-core/__tests__/hydration.spec.ts +++ b/packages/runtime-core/__tests__/hydration.spec.ts @@ -17,6 +17,7 @@ import { renderSlot } from '@vue/runtime-dom' import { renderToString, SSRContext } from '@vue/server-renderer' +import { StringifyThresholds } from '@vue/compiler-dom' import { PatchFlags } from '../../shared/src' function mountWithHydration(html: string, render: () => any) { @@ -545,6 +546,26 @@ describe('SSR hydration', () => { expect(text.textContent).toBe('bye') }) + // #6637 + test('the result of client render should be the same as server render', async () => { + const App = { + // the quantity must reach the threshold to be reproduce + template: `
`.repeat(StringifyThresholds.NODE_COUNT) + } + const container = document.createElement('div') + + // server render + const serverSide = await renderToString(h(App)) + container.innerHTML = serverSide + + // client render + createSSRApp(App).mount(container) + const clientSide = container.innerHTML + + expect(`Hydration node mismatch`).not.toHaveBeenWarned() + expect(serverSide).toBe(clientSide) + }) + test('handle click error in ssr mode', async () => { const App = { setup() { From 5790adf7b2f5ebe9bbad9bb96b9f59d468be7f0d Mon Sep 17 00:00:00 2001 From: Rairn <958414905@qq.com> Date: Tue, 20 Sep 2022 04:44:16 +0800 Subject: [PATCH 2/3] fix(compiler-core): avoid discrepancies with server-side rendering --- packages/compiler-core/src/transform.ts | 59 +++++++++++-------------- packages/compiler-core/src/utils.ts | 36 +++++++++++++-- 2 files changed, 60 insertions(+), 35 deletions(-) diff --git a/packages/compiler-core/src/transform.ts b/packages/compiler-core/src/transform.ts index 6397df92f3c..446fde609d4 100644 --- a/packages/compiler-core/src/transform.ts +++ b/packages/compiler-core/src/transform.ts @@ -15,7 +15,6 @@ import { CacheExpression, createCacheExpression, TemplateLiteral, - createVNodeCall, ConstantTypes, ArrayExpression } from './ast' @@ -23,8 +22,6 @@ import { isString, isArray, NOOP, - PatchFlags, - PatchFlagNames, EMPTY_OBJ, capitalize, camelize @@ -32,11 +29,11 @@ import { import { defaultOnError, defaultOnWarn } from './errors' import { TO_DISPLAY_STRING, - FRAGMENT, helperNameMap, - CREATE_COMMENT + CREATE_COMMENT, + CREATE_STATIC } from './runtimeHelpers' -import { isVSlot, makeBlock } from './utils' +import { isVSlot, makeBlock, makeFragmentBlock } from './utils' import { hoistStatic, isSingleElementRoot } from './transforms/hoistStatic' import { CompilerCompatOptions } from './compat/compatConfig' @@ -122,6 +119,20 @@ export interface TransformContext filters?: Set } +const prefix = '_hoisted_' +const isSingleHoistStaticRoot = ( + root: RootNode, + context: TransformContext +): boolean => { + const { children } = root + const { hoists } = context + return ( + children.length === 1 && + (children[0] as any).codegenNode?.content?.startsWith(prefix) && + (hoists[0] as any)?.callee === CREATE_STATIC + ) +} + export function createTransformContext( root: RootNode, { @@ -282,7 +293,7 @@ export function createTransformContext( if (isString(exp)) exp = createSimpleExpression(exp) context.hoists.push(exp) const identifier = createSimpleExpression( - `_hoisted_${context.hoists.length}`, + `${prefix}${context.hoists.length}`, false, exp.loc, ConstantTypes.CAN_HOIST @@ -338,12 +349,18 @@ export function transform(root: RootNode, options: TransformOptions) { } function createRootCodegen(root: RootNode, context: TransformContext) { - const { helper } = context const { children } = root if (children.length === 1) { const child = children[0] + // #6637 + if (isSingleHoistStaticRoot(root, context)) { + // when the root is only one child and it is a static node, + // we need to return a fragment block to keep in line with + // the server-side rendering behavior to prevent warning when hydrating + makeFragmentBlock(root, context) + } // if the single child is an element, turn it into a block. - if (isSingleElementRoot(root, child) && child.codegenNode) { + else if (isSingleElementRoot(root, child) && child.codegenNode) { // single element root is never hoisted so codegenNode will never be // SimpleExpressionNode const codegenNode = child.codegenNode @@ -359,29 +376,7 @@ function createRootCodegen(root: RootNode, context: TransformContext) { } } else if (children.length > 1) { // root has multiple nodes - return a fragment block. - let patchFlag = PatchFlags.STABLE_FRAGMENT - let patchFlagText = PatchFlagNames[PatchFlags.STABLE_FRAGMENT] - // check if the fragment actually contains a single valid child with - // the rest being comments - if ( - __DEV__ && - children.filter(c => c.type !== NodeTypes.COMMENT).length === 1 - ) { - patchFlag |= PatchFlags.DEV_ROOT_FRAGMENT - patchFlagText += `, ${PatchFlagNames[PatchFlags.DEV_ROOT_FRAGMENT]}` - } - root.codegenNode = createVNodeCall( - context, - helper(FRAGMENT), - undefined, - root.children, - patchFlag + (__DEV__ ? ` /* ${patchFlagText} */` : ``), - undefined, - undefined, - true, - undefined, - false /* isComponent */ - ) + makeFragmentBlock(root, context) } else { // no children = noop. codegen will return null. } diff --git a/packages/compiler-core/src/utils.ts b/packages/compiler-core/src/utils.ts index c9e310fe089..1fd6069640b 100644 --- a/packages/compiler-core/src/utils.ts +++ b/packages/compiler-core/src/utils.ts @@ -23,7 +23,8 @@ import { VNodeCall, SimpleExpressionNode, BlockCodegenNode, - MemoExpression + MemoExpression, + createVNodeCall } from './ast' import { TransformContext } from './transform' import { @@ -40,9 +41,10 @@ import { CREATE_VNODE, CREATE_ELEMENT_VNODE, WITH_MEMO, - OPEN_BLOCK + OPEN_BLOCK, + FRAGMENT } from './runtimeHelpers' -import { isString, isObject, hyphenate, extend, NOOP } from '@vue/shared' +import { isString, isObject, hyphenate, extend, NOOP, PatchFlags, PatchFlagNames } from '@vue/shared' import { PropsExpression } from './transforms/transformElement' import { parseExpression } from '@babel/parser' import { Expression } from '@babel/types' @@ -537,3 +539,31 @@ export function makeBlock( helper(getVNodeBlockHelper(inSSR, node.isComponent)) } } + +export function makeFragmentBlock(root: RootNode, context: TransformContext) { + const { helper } = context + const { children } = root + let patchFlag = PatchFlags.STABLE_FRAGMENT + let patchFlagText = PatchFlagNames[PatchFlags.STABLE_FRAGMENT] + // check if the fragment actually contains a single valid child with + // the rest being comments + if ( + __DEV__ && + children.filter(c => c.type !== NodeTypes.COMMENT).length === 1 + ) { + patchFlag |= PatchFlags.DEV_ROOT_FRAGMENT + patchFlagText += `, ${PatchFlagNames[PatchFlags.DEV_ROOT_FRAGMENT]}` + } + root.codegenNode = createVNodeCall( + context, + helper(FRAGMENT), + undefined, + root.children, + patchFlag + (__DEV__ ? ` /* ${patchFlagText} */` : ``), + undefined, + undefined, + true, + undefined, + false + ) +} From 3586d7b81701ba706f6a4a3a601d4f014f85593f Mon Sep 17 00:00:00 2001 From: Rairn <958414905@qq.com> Date: Tue, 20 Sep 2022 04:55:04 +0800 Subject: [PATCH 3/3] test(compiler-dom): update snap --- .../__snapshots__/stringifyStatic.spec.ts.snap | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/compiler-dom/__tests__/transforms/__snapshots__/stringifyStatic.spec.ts.snap b/packages/compiler-dom/__tests__/transforms/__snapshots__/stringifyStatic.spec.ts.snap index 8427b38fcec..8004cfbe5a9 100644 --- a/packages/compiler-dom/__tests__/transforms/__snapshots__/stringifyStatic.spec.ts.snap +++ b/packages/compiler-dom/__tests__/transforms/__snapshots__/stringifyStatic.spec.ts.snap @@ -34,21 +34,25 @@ return function render(_ctx, _cache) { `; exports[`stringify static html stringify v-html 1`] = ` -"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode } = Vue +"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue const _hoisted_1 = /*#__PURE__*/_createStaticVNode(\\"
show-it 
12
\\", 2) return function render(_ctx, _cache) { - return _hoisted_1 + return (_openBlock(), _createElementBlock(_Fragment, null, [ + _hoisted_1 + ], 2112 /* STABLE_FRAGMENT, DEV_ROOT_FRAGMENT */)) }" `; exports[`stringify static html stringify v-text 1`] = ` -"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode } = Vue +"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue const _hoisted_1 = /*#__PURE__*/_createStaticVNode(\\"
<span>show-it </span>
12
\\", 2) return function render(_ctx, _cache) { - return _hoisted_1 + return (_openBlock(), _createElementBlock(_Fragment, null, [ + _hoisted_1 + ], 2112 /* STABLE_FRAGMENT, DEV_ROOT_FRAGMENT */)) }" `;