From e09c26bc9bc4394c2c2d928806d382515c2676f3 Mon Sep 17 00:00:00 2001 From: edison Date: Wed, 25 Oct 2023 01:01:29 +0800 Subject: [PATCH] fix(compiler-ssr): proper scope analysis for ssr vnode slot fallback (#7184) close #7095 --- .../compiler-core/src/transforms/vSlot.ts | 19 +++++++---- .../__tests__/ssrComponent.spec.ts | 19 ++++++----- .../src/transforms/ssrTransformComponent.ts | 21 +++++++++--- .../src/transforms/ssrTransformSuspense.ts | 32 +++++++++++-------- 4 files changed, 58 insertions(+), 33 deletions(-) diff --git a/packages/compiler-core/src/transforms/vSlot.ts b/packages/compiler-core/src/transforms/vSlot.ts index c4416dd45f7..ffa90ea1171 100644 --- a/packages/compiler-core/src/transforms/vSlot.ts +++ b/packages/compiler-core/src/transforms/vSlot.ts @@ -100,11 +100,12 @@ export const trackVForSlotScopes: NodeTransform = (node, context) => { export type SlotFnBuilder = ( slotProps: ExpressionNode | undefined, + vForExp: ExpressionNode | undefined, slotChildren: TemplateChildNode[], loc: SourceLocation ) => FunctionExpression -const buildClientSlotFn: SlotFnBuilder = (props, children, loc) => +const buildClientSlotFn: SlotFnBuilder = (props, _vForExp, children, loc) => createFunctionExpression( props, children, @@ -149,7 +150,7 @@ export function buildSlots( slotsProperties.push( createObjectProperty( arg || createSimpleExpression('default', true), - buildSlotFn(exp, children, loc) + buildSlotFn(exp, undefined, children, loc) ) ) } @@ -201,11 +202,17 @@ export function buildSlots( hasDynamicSlots = true } - const slotFunction = buildSlotFn(slotProps, slotChildren, slotLoc) + const vFor = findDir(slotElement, 'for') + const slotFunction = buildSlotFn( + slotProps, + vFor?.exp, + slotChildren, + slotLoc + ) + // check if this slot is conditional (v-if/v-for) let vIf: DirectiveNode | undefined let vElse: DirectiveNode | undefined - let vFor: DirectiveNode | undefined if ((vIf = findDir(slotElement, 'if'))) { hasDynamicSlots = true dynamicSlots.push( @@ -257,7 +264,7 @@ export function buildSlots( createCompilerError(ErrorCodes.X_V_ELSE_NO_ADJACENT_IF, vElse.loc) ) } - } else if ((vFor = findDir(slotElement, 'for'))) { + } else if (vFor) { hasDynamicSlots = true const parseResult = vFor.parseResult || @@ -306,7 +313,7 @@ export function buildSlots( props: ExpressionNode | undefined, children: TemplateChildNode[] ) => { - const fn = buildSlotFn(props, children, loc) + const fn = buildSlotFn(props, undefined, children, loc) if (__COMPAT__ && context.compatConfig) { fn.isNonScopedSlot = true } diff --git a/packages/compiler-ssr/__tests__/ssrComponent.spec.ts b/packages/compiler-ssr/__tests__/ssrComponent.spec.ts index 9391c01e37e..a8ea08a5349 100644 --- a/packages/compiler-ssr/__tests__/ssrComponent.spec.ts +++ b/packages/compiler-ssr/__tests__/ssrComponent.spec.ts @@ -181,11 +181,14 @@ describe('ssr: components', () => { }) test('v-for slot', () => { - expect( - compile(` - - `).code - ).toMatchInlineSnapshot(` + const { code } = compile(` + + `) + expect(code).not.toMatch(`_ctx.msg`) + expect(code).not.toMatch(`_ctx.key`) + expect(code).not.toMatch(`_ctx.index`) + expect(code).toMatch(`_ctx.bar`) + expect(code).toMatchInlineSnapshot(` "const { resolveComponent: _resolveComponent, withCtx: _withCtx, toDisplayString: _toDisplayString, createTextVNode: _createTextVNode, renderList: _renderList, createSlots: _createSlots } = require(\\"vue\\") const { ssrRenderComponent: _ssrRenderComponent, ssrInterpolate: _ssrInterpolate } = require(\\"vue/server-renderer\\") @@ -193,15 +196,15 @@ describe('ssr: components', () => { const _component_foo = _resolveComponent(\\"foo\\") _push(_ssrRenderComponent(_component_foo, _attrs, _createSlots({ _: 2 /* DYNAMIC */ }, [ - _renderList(_ctx.names, (key) => { + _renderList(_ctx.names, (key, index) => { return { name: key, fn: _withCtx(({ msg }, _push, _parent, _scopeId) => { if (_push) { - _push(\`\${_ssrInterpolate(msg + key + _ctx.bar)}\`) + _push(\`\${_ssrInterpolate(msg + key + index + _ctx.bar)}\`) } else { return [ - _createTextVNode(_toDisplayString(msg + _ctx.key + _ctx.bar), 1 /* TEXT */) + _createTextVNode(_toDisplayString(msg + key + index + _ctx.bar), 1 /* TEXT */) ] } }) diff --git a/packages/compiler-ssr/src/transforms/ssrTransformComponent.ts b/packages/compiler-ssr/src/transforms/ssrTransformComponent.ts index 93cae7db3c2..7a12cb29009 100644 --- a/packages/compiler-ssr/src/transforms/ssrTransformComponent.ts +++ b/packages/compiler-ssr/src/transforms/ssrTransformComponent.ts @@ -125,8 +125,10 @@ export const ssrTransformComponent: NodeTransform = (node, context) => { // fallback in case the child is render-fn based). Store them in an array // for later use. if (clonedNode.children.length) { - buildSlots(clonedNode, context, (props, children) => { - vnodeBranches.push(createVNodeSlotBranch(props, children, context)) + buildSlots(clonedNode, context, (props, vFor, children) => { + vnodeBranches.push( + createVNodeSlotBranch(props, vFor, children, context) + ) return createFunctionExpression(undefined) }) } @@ -150,7 +152,7 @@ export const ssrTransformComponent: NodeTransform = (node, context) => { const wipEntries: WIPSlotEntry[] = [] wipMap.set(node, wipEntries) - const buildSSRSlotFn: SlotFnBuilder = (props, children, loc) => { + const buildSSRSlotFn: SlotFnBuilder = (props, _vForExp, children, loc) => { const param0 = (props && stringifyExpression(props)) || `_` const fn = createFunctionExpression( [param0, `_push`, `_parent`, `_scopeId`], @@ -277,6 +279,7 @@ const vnodeDirectiveTransforms = { function createVNodeSlotBranch( props: ExpressionNode | undefined, + vForExp: ExpressionNode | undefined, children: TemplateChildNode[], parentContext: TransformContext ): ReturnStatement { @@ -303,8 +306,8 @@ function createVNodeSlotBranch( tag: 'template', tagType: ElementTypes.TEMPLATE, isSelfClosing: false, - // important: provide v-slot="props" on the wrapper for proper - // scope analysis + // important: provide v-slot="props" and v-for="exp" on the wrapper for + // proper scope analysis props: [ { type: NodeTypes.DIRECTIVE, @@ -313,6 +316,14 @@ function createVNodeSlotBranch( arg: undefined, modifiers: [], loc: locStub + }, + { + type: NodeTypes.DIRECTIVE, + name: 'for', + exp: vForExp, + arg: undefined, + modifiers: [], + loc: locStub } ], children, diff --git a/packages/compiler-ssr/src/transforms/ssrTransformSuspense.ts b/packages/compiler-ssr/src/transforms/ssrTransformSuspense.ts index 207e9348eef..e7efbe1fb73 100644 --- a/packages/compiler-ssr/src/transforms/ssrTransformSuspense.ts +++ b/packages/compiler-ssr/src/transforms/ssrTransformSuspense.ts @@ -36,20 +36,24 @@ export function ssrTransformSuspense( wipSlots: [] } wipMap.set(node, wipEntry) - wipEntry.slotsExp = buildSlots(node, context, (_props, children, loc) => { - const fn = createFunctionExpression( - [], - undefined, // no return, assign body later - true, // newline - false, // suspense slots are not treated as normal slots - loc - ) - wipEntry.wipSlots.push({ - fn, - children - }) - return fn - }).slots + wipEntry.slotsExp = buildSlots( + node, + context, + (_props, _vForExp, children, loc) => { + const fn = createFunctionExpression( + [], + undefined, // no return, assign body later + true, // newline + false, // suspense slots are not treated as normal slots + loc + ) + wipEntry.wipSlots.push({ + fn, + children + }) + return fn + } + ).slots } } }