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 = (
|
export type SlotFnBuilder = (
|
||||||
slotProps: ExpressionNode | undefined,
|
slotProps: ExpressionNode | undefined,
|
||||||
|
vForExp: ExpressionNode | undefined,
|
||||||
slotChildren: TemplateChildNode[],
|
slotChildren: TemplateChildNode[],
|
||||||
loc: SourceLocation
|
loc: SourceLocation
|
||||||
) => FunctionExpression
|
) => FunctionExpression
|
||||||
|
|
||||||
const buildClientSlotFn: SlotFnBuilder = (props, children, loc) =>
|
const buildClientSlotFn: SlotFnBuilder = (props, _vForExp, children, loc) =>
|
||||||
createFunctionExpression(
|
createFunctionExpression(
|
||||||
props,
|
props,
|
||||||
children,
|
children,
|
||||||
|
@ -149,7 +150,7 @@ export function buildSlots(
|
||||||
slotsProperties.push(
|
slotsProperties.push(
|
||||||
createObjectProperty(
|
createObjectProperty(
|
||||||
arg || createSimpleExpression('default', true),
|
arg || createSimpleExpression('default', true),
|
||||||
buildSlotFn(exp, children, loc)
|
buildSlotFn(exp, undefined, children, loc)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -201,11 +202,17 @@ export function buildSlots(
|
||||||
hasDynamicSlots = true
|
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)
|
// check if this slot is conditional (v-if/v-for)
|
||||||
let vIf: DirectiveNode | undefined
|
let vIf: DirectiveNode | undefined
|
||||||
let vElse: DirectiveNode | undefined
|
let vElse: DirectiveNode | undefined
|
||||||
let vFor: DirectiveNode | undefined
|
|
||||||
if ((vIf = findDir(slotElement, 'if'))) {
|
if ((vIf = findDir(slotElement, 'if'))) {
|
||||||
hasDynamicSlots = true
|
hasDynamicSlots = true
|
||||||
dynamicSlots.push(
|
dynamicSlots.push(
|
||||||
|
@ -257,7 +264,7 @@ export function buildSlots(
|
||||||
createCompilerError(ErrorCodes.X_V_ELSE_NO_ADJACENT_IF, vElse.loc)
|
createCompilerError(ErrorCodes.X_V_ELSE_NO_ADJACENT_IF, vElse.loc)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else if ((vFor = findDir(slotElement, 'for'))) {
|
} else if (vFor) {
|
||||||
hasDynamicSlots = true
|
hasDynamicSlots = true
|
||||||
const parseResult =
|
const parseResult =
|
||||||
vFor.parseResult ||
|
vFor.parseResult ||
|
||||||
|
@ -306,7 +313,7 @@ export function buildSlots(
|
||||||
props: ExpressionNode | undefined,
|
props: ExpressionNode | undefined,
|
||||||
children: TemplateChildNode[]
|
children: TemplateChildNode[]
|
||||||
) => {
|
) => {
|
||||||
const fn = buildSlotFn(props, children, loc)
|
const fn = buildSlotFn(props, undefined, children, loc)
|
||||||
if (__COMPAT__ && context.compatConfig) {
|
if (__COMPAT__ && context.compatConfig) {
|
||||||
fn.isNonScopedSlot = true
|
fn.isNonScopedSlot = true
|
||||||
}
|
}
|
||||||
|
|
|
@ -181,11 +181,14 @@ describe('ssr: components', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
test('v-for slot', () => {
|
test('v-for slot', () => {
|
||||||
expect(
|
const { code } = compile(`<foo>
|
||||||
compile(`<foo>
|
<template v-for="(key, index) in names" v-slot:[key]="{ msg }">{{ msg + key + index + bar }}</template>
|
||||||
<template v-for="key in names" v-slot:[key]="{ msg }">{{ msg + key + bar }}</template>
|
</foo>`)
|
||||||
</foo>`).code
|
expect(code).not.toMatch(`_ctx.msg`)
|
||||||
).toMatchInlineSnapshot(`
|
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 { resolveComponent: _resolveComponent, withCtx: _withCtx, toDisplayString: _toDisplayString, createTextVNode: _createTextVNode, renderList: _renderList, createSlots: _createSlots } = require(\\"vue\\")
|
||||||
const { ssrRenderComponent: _ssrRenderComponent, ssrInterpolate: _ssrInterpolate } = require(\\"vue/server-renderer\\")
|
const { ssrRenderComponent: _ssrRenderComponent, ssrInterpolate: _ssrInterpolate } = require(\\"vue/server-renderer\\")
|
||||||
|
|
||||||
|
@ -193,15 +196,15 @@ describe('ssr: components', () => {
|
||||||
const _component_foo = _resolveComponent(\\"foo\\")
|
const _component_foo = _resolveComponent(\\"foo\\")
|
||||||
|
|
||||||
_push(_ssrRenderComponent(_component_foo, _attrs, _createSlots({ _: 2 /* DYNAMIC */ }, [
|
_push(_ssrRenderComponent(_component_foo, _attrs, _createSlots({ _: 2 /* DYNAMIC */ }, [
|
||||||
_renderList(_ctx.names, (key) => {
|
_renderList(_ctx.names, (key, index) => {
|
||||||
return {
|
return {
|
||||||
name: key,
|
name: key,
|
||||||
fn: _withCtx(({ msg }, _push, _parent, _scopeId) => {
|
fn: _withCtx(({ msg }, _push, _parent, _scopeId) => {
|
||||||
if (_push) {
|
if (_push) {
|
||||||
_push(\`\${_ssrInterpolate(msg + key + _ctx.bar)}\`)
|
_push(\`\${_ssrInterpolate(msg + key + index + _ctx.bar)}\`)
|
||||||
} else {
|
} else {
|
||||||
return [
|
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
|
// fallback in case the child is render-fn based). Store them in an array
|
||||||
// for later use.
|
// for later use.
|
||||||
if (clonedNode.children.length) {
|
if (clonedNode.children.length) {
|
||||||
buildSlots(clonedNode, context, (props, children) => {
|
buildSlots(clonedNode, context, (props, vFor, children) => {
|
||||||
vnodeBranches.push(createVNodeSlotBranch(props, children, context))
|
vnodeBranches.push(
|
||||||
|
createVNodeSlotBranch(props, vFor, children, context)
|
||||||
|
)
|
||||||
return createFunctionExpression(undefined)
|
return createFunctionExpression(undefined)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -150,7 +152,7 @@ export const ssrTransformComponent: NodeTransform = (node, context) => {
|
||||||
const wipEntries: WIPSlotEntry[] = []
|
const wipEntries: WIPSlotEntry[] = []
|
||||||
wipMap.set(node, wipEntries)
|
wipMap.set(node, wipEntries)
|
||||||
|
|
||||||
const buildSSRSlotFn: SlotFnBuilder = (props, children, loc) => {
|
const buildSSRSlotFn: SlotFnBuilder = (props, _vForExp, children, loc) => {
|
||||||
const param0 = (props && stringifyExpression(props)) || `_`
|
const param0 = (props && stringifyExpression(props)) || `_`
|
||||||
const fn = createFunctionExpression(
|
const fn = createFunctionExpression(
|
||||||
[param0, `_push`, `_parent`, `_scopeId`],
|
[param0, `_push`, `_parent`, `_scopeId`],
|
||||||
|
@ -277,6 +279,7 @@ const vnodeDirectiveTransforms = {
|
||||||
|
|
||||||
function createVNodeSlotBranch(
|
function createVNodeSlotBranch(
|
||||||
props: ExpressionNode | undefined,
|
props: ExpressionNode | undefined,
|
||||||
|
vForExp: ExpressionNode | undefined,
|
||||||
children: TemplateChildNode[],
|
children: TemplateChildNode[],
|
||||||
parentContext: TransformContext
|
parentContext: TransformContext
|
||||||
): ReturnStatement {
|
): ReturnStatement {
|
||||||
|
@ -303,8 +306,8 @@ function createVNodeSlotBranch(
|
||||||
tag: 'template',
|
tag: 'template',
|
||||||
tagType: ElementTypes.TEMPLATE,
|
tagType: ElementTypes.TEMPLATE,
|
||||||
isSelfClosing: false,
|
isSelfClosing: false,
|
||||||
// important: provide v-slot="props" on the wrapper for proper
|
// important: provide v-slot="props" and v-for="exp" on the wrapper for
|
||||||
// scope analysis
|
// proper scope analysis
|
||||||
props: [
|
props: [
|
||||||
{
|
{
|
||||||
type: NodeTypes.DIRECTIVE,
|
type: NodeTypes.DIRECTIVE,
|
||||||
|
@ -313,6 +316,14 @@ function createVNodeSlotBranch(
|
||||||
arg: undefined,
|
arg: undefined,
|
||||||
modifiers: [],
|
modifiers: [],
|
||||||
loc: locStub
|
loc: locStub
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: NodeTypes.DIRECTIVE,
|
||||||
|
name: 'for',
|
||||||
|
exp: vForExp,
|
||||||
|
arg: undefined,
|
||||||
|
modifiers: [],
|
||||||
|
loc: locStub
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
children,
|
children,
|
||||||
|
|
|
@ -36,20 +36,24 @@ export function ssrTransformSuspense(
|
||||||
wipSlots: []
|
wipSlots: []
|
||||||
}
|
}
|
||||||
wipMap.set(node, wipEntry)
|
wipMap.set(node, wipEntry)
|
||||||
wipEntry.slotsExp = buildSlots(node, context, (_props, children, loc) => {
|
wipEntry.slotsExp = buildSlots(
|
||||||
const fn = createFunctionExpression(
|
node,
|
||||||
[],
|
context,
|
||||||
undefined, // no return, assign body later
|
(_props, _vForExp, children, loc) => {
|
||||||
true, // newline
|
const fn = createFunctionExpression(
|
||||||
false, // suspense slots are not treated as normal slots
|
[],
|
||||||
loc
|
undefined, // no return, assign body later
|
||||||
)
|
true, // newline
|
||||||
wipEntry.wipSlots.push({
|
false, // suspense slots are not treated as normal slots
|
||||||
fn,
|
loc
|
||||||
children
|
)
|
||||||
})
|
wipEntry.wipSlots.push({
|
||||||
return fn
|
fn,
|
||||||
}).slots
|
children
|
||||||
|
})
|
||||||
|
return fn
|
||||||
|
}
|
||||||
|
).slots
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue