Skip to content

Commit

Permalink
fix(compiler-ssr): proper scope analysis for ssr vnode slot fallback (#…
Browse files Browse the repository at this point in the history
…7184)

close #7095
  • Loading branch information
edison1105 committed Oct 24, 2023
1 parent 7374e93 commit e09c26b
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 33 deletions.
19 changes: 13 additions & 6 deletions packages/compiler-core/src/transforms/vSlot.ts
Expand Up @@ -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,
Expand Down Expand Up @@ -149,7 +150,7 @@ export function buildSlots(
slotsProperties.push(
createObjectProperty(
arg || createSimpleExpression('default', true),
buildSlotFn(exp, children, loc)
buildSlotFn(exp, undefined, children, loc)
)
)
}
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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 ||
Expand Down Expand Up @@ -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
}
Expand Down
19 changes: 11 additions & 8 deletions packages/compiler-ssr/__tests__/ssrComponent.spec.ts
Expand Up @@ -181,27 +181,30 @@ describe('ssr: components', () => {
})

test('v-for slot', () => {
expect(
compile(`<foo>
<template v-for="key in names" v-slot:[key]="{ msg }">{{ msg + key + bar }}</template>
</foo>`).code
).toMatchInlineSnapshot(`
const { code } = compile(`<foo>
<template v-for="(key, index) in names" v-slot:[key]="{ msg }">{{ msg + key + index + bar }}</template>
</foo>`)
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\\")
return function ssrRender(_ctx, _push, _parent, _attrs) {
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 */)
]
}
})
Expand Down
21 changes: 16 additions & 5 deletions packages/compiler-ssr/src/transforms/ssrTransformComponent.ts
Expand Up @@ -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)
})
}
Expand All @@ -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`],
Expand Down Expand Up @@ -277,6 +279,7 @@ const vnodeDirectiveTransforms = {

function createVNodeSlotBranch(
props: ExpressionNode | undefined,
vForExp: ExpressionNode | undefined,
children: TemplateChildNode[],
parentContext: TransformContext
): ReturnStatement {
Expand All @@ -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,
Expand All @@ -313,6 +316,14 @@ function createVNodeSlotBranch(
arg: undefined,
modifiers: [],
loc: locStub
},
{
type: NodeTypes.DIRECTIVE,
name: 'for',
exp: vForExp,
arg: undefined,
modifiers: [],
loc: locStub
}
],
children,
Expand Down
32 changes: 18 additions & 14 deletions packages/compiler-ssr/src/transforms/ssrTransformSuspense.ts
Expand Up @@ -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
}
}
}
Expand Down

0 comments on commit e09c26b

Please sign in to comment.