mirror of https://github.com/vuejs/core.git
fix(compiler-ssr): proper scope analysis for ssr vnode slot fallback (#7184)
close #7095
This commit is contained in:
parent
7374e93f02
commit
e09c26bc9b
|
@ -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
|
||||
}
|
||||
|
|
|
@ -181,11 +181,14 @@ 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\\")
|
||||
|
||||
|
@ -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 */)
|
||||
]
|
||||
}
|
||||
})
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue