fix(compiler-ssr): generate correct children for transition-group
fix #2510
This commit is contained in:
parent
55d99d729e
commit
a5d6f8091e
@ -275,14 +275,6 @@ describe('ssr: components', () => {
|
||||
}"
|
||||
`)
|
||||
|
||||
expect(compile(`<transition-group><div/></transition-group>`).code)
|
||||
.toMatchInlineSnapshot(`
|
||||
"
|
||||
return function ssrRender(_ctx, _push, _parent, _attrs) {
|
||||
_push(\`<!--[--><div></div><!--]-->\`)
|
||||
}"
|
||||
`)
|
||||
|
||||
expect(compile(`<keep-alive><foo/></keep-alive>`).code)
|
||||
.toMatchInlineSnapshot(`
|
||||
"const { resolveComponent: _resolveComponent } = require(\\"vue\\")
|
||||
@ -295,5 +287,93 @@ describe('ssr: components', () => {
|
||||
}"
|
||||
`)
|
||||
})
|
||||
|
||||
// transition-group should flatten and concat its children fragments into
|
||||
// a single one
|
||||
describe('transition-group', () => {
|
||||
test('basic', () => {
|
||||
expect(
|
||||
compile(
|
||||
`<transition-group><div v-for="i in list"/></transition-group>`
|
||||
).code
|
||||
).toMatchInlineSnapshot(`
|
||||
"const { ssrRenderList: _ssrRenderList } = require(\\"@vue/server-renderer\\")
|
||||
|
||||
return function ssrRender(_ctx, _push, _parent, _attrs) {
|
||||
_push(\`<!--[-->\`)
|
||||
_ssrRenderList(_ctx.list, (i) => {
|
||||
_push(\`<div></div>\`)
|
||||
})
|
||||
_push(\`<!--]-->\`)
|
||||
}"
|
||||
`)
|
||||
})
|
||||
|
||||
test('with static tag', () => {
|
||||
expect(
|
||||
compile(
|
||||
`<transition-group tag="ul"><div v-for="i in list"/></transition-group>`
|
||||
).code
|
||||
).toMatchInlineSnapshot(`
|
||||
"const { ssrRenderList: _ssrRenderList } = require(\\"@vue/server-renderer\\")
|
||||
|
||||
return function ssrRender(_ctx, _push, _parent, _attrs) {
|
||||
_push(\`<ul>\`)
|
||||
_ssrRenderList(_ctx.list, (i) => {
|
||||
_push(\`<div></div>\`)
|
||||
})
|
||||
_push(\`</ul>\`)
|
||||
}"
|
||||
`)
|
||||
})
|
||||
|
||||
test('with dynamic tag', () => {
|
||||
expect(
|
||||
compile(
|
||||
`<transition-group :tag="someTag"><div v-for="i in list"/></transition-group>`
|
||||
).code
|
||||
).toMatchInlineSnapshot(`
|
||||
"const { ssrRenderList: _ssrRenderList } = require(\\"@vue/server-renderer\\")
|
||||
|
||||
return function ssrRender(_ctx, _push, _parent, _attrs) {
|
||||
_push(\`<\${_ctx.someTag}>\`)
|
||||
_ssrRenderList(_ctx.list, (i) => {
|
||||
_push(\`<div></div>\`)
|
||||
})
|
||||
_push(\`</\${_ctx.someTag}>\`)
|
||||
}"
|
||||
`)
|
||||
})
|
||||
|
||||
test('with multi fragments children', () => {
|
||||
expect(
|
||||
compile(
|
||||
`<transition-group>
|
||||
<div v-for="i in 10"/>
|
||||
<div v-for="i in 10"/>
|
||||
<template v-if="ok"><div>ok</div></template>
|
||||
</transition-group>`
|
||||
).code
|
||||
).toMatchInlineSnapshot(`
|
||||
"const { ssrRenderList: _ssrRenderList } = require(\\"@vue/server-renderer\\")
|
||||
|
||||
return function ssrRender(_ctx, _push, _parent, _attrs) {
|
||||
_push(\`<!--[-->\`)
|
||||
_ssrRenderList(10, (i) => {
|
||||
_push(\`<div></div>\`)
|
||||
})
|
||||
_ssrRenderList(10, (i) => {
|
||||
_push(\`<div></div>\`)
|
||||
})
|
||||
if (_ctx.ok) {
|
||||
_push(\`<div>ok</div>\`)
|
||||
} else {
|
||||
_push(\`<!---->\`)
|
||||
}
|
||||
_push(\`<!--]-->\`)
|
||||
}"
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -128,7 +128,8 @@ function createChildContext(
|
||||
export function processChildren(
|
||||
children: TemplateChildNode[],
|
||||
context: SSRTransformContext,
|
||||
asFragment = false
|
||||
asFragment = false,
|
||||
disableNestedFragments = false
|
||||
) {
|
||||
if (asFragment) {
|
||||
context.pushStringPart(`<!--[-->`)
|
||||
@ -176,10 +177,10 @@ export function processChildren(
|
||||
)
|
||||
break
|
||||
case NodeTypes.IF:
|
||||
ssrProcessIf(child, context)
|
||||
ssrProcessIf(child, context, disableNestedFragments)
|
||||
break
|
||||
case NodeTypes.FOR:
|
||||
ssrProcessFor(child, context)
|
||||
ssrProcessFor(child, context, disableNestedFragments)
|
||||
break
|
||||
case NodeTypes.IF_BRANCH:
|
||||
// no-op - handled by ssrProcessIf
|
||||
|
@ -46,6 +46,7 @@ import {
|
||||
ssrProcessSuspense,
|
||||
ssrTransformSuspense
|
||||
} from './ssrTransformSuspense'
|
||||
import { ssrProcessTransitionGroup } from './ssrTransformTransitionGroup'
|
||||
import { isSymbol, isObject, isArray } from '@vue/shared'
|
||||
|
||||
// We need to construct the slot functions in the 1st pass to ensure proper
|
||||
@ -176,9 +177,11 @@ export function ssrProcessComponent(
|
||||
return ssrProcessTeleport(node, context)
|
||||
} else if (component === SUSPENSE) {
|
||||
return ssrProcessSuspense(node, context)
|
||||
} else if (component === TRANSITION_GROUP) {
|
||||
return ssrProcessTransitionGroup(node, context)
|
||||
} else {
|
||||
// real fall-through (e.g. KeepAlive): just render its children.
|
||||
processChildren(node.children, context, component === TRANSITION_GROUP)
|
||||
processChildren(node.children, context)
|
||||
}
|
||||
} else {
|
||||
// finish up slot function expressions from the 1st pass.
|
||||
|
@ -0,0 +1,41 @@
|
||||
import { ComponentNode, findProp, NodeTypes } from '@vue/compiler-dom'
|
||||
import { processChildren, SSRTransformContext } from '../ssrCodegenTransform'
|
||||
|
||||
export function ssrProcessTransitionGroup(
|
||||
node: ComponentNode,
|
||||
context: SSRTransformContext
|
||||
) {
|
||||
const tag = findProp(node, 'tag')
|
||||
if (tag) {
|
||||
if (tag.type === NodeTypes.DIRECTIVE) {
|
||||
// dynamic :tag
|
||||
context.pushStringPart(`<`)
|
||||
context.pushStringPart(tag.exp!)
|
||||
context.pushStringPart(`>`)
|
||||
|
||||
processChildren(
|
||||
node.children,
|
||||
context,
|
||||
false,
|
||||
/**
|
||||
* TransitionGroup has the special runtime behavior of flattening and
|
||||
* concatenating all children into a single fragment (in order for them to
|
||||
* be pathced using the same key map) so we need to account for that here
|
||||
* by disabling nested fragment wrappers from being generated.
|
||||
*/
|
||||
true
|
||||
)
|
||||
context.pushStringPart(`</`)
|
||||
context.pushStringPart(tag.exp!)
|
||||
context.pushStringPart(`>`)
|
||||
} else {
|
||||
// static tag
|
||||
context.pushStringPart(`<${tag.value!.content}>`)
|
||||
processChildren(node.children, context, false, true)
|
||||
context.pushStringPart(`</${tag.value!.content}>`)
|
||||
}
|
||||
} else {
|
||||
// fragment
|
||||
processChildren(node.children, context, true, true)
|
||||
}
|
||||
}
|
@ -21,9 +21,14 @@ export const ssrTransformFor = createStructuralDirectiveTransform(
|
||||
|
||||
// This is called during the 2nd transform pass to construct the SSR-specific
|
||||
// codegen nodes.
|
||||
export function ssrProcessFor(node: ForNode, context: SSRTransformContext) {
|
||||
export function ssrProcessFor(
|
||||
node: ForNode,
|
||||
context: SSRTransformContext,
|
||||
disableNestedFragments = false
|
||||
) {
|
||||
const needFragmentWrapper =
|
||||
node.children.length !== 1 || node.children[0].type !== NodeTypes.ELEMENT
|
||||
!disableNestedFragments &&
|
||||
(node.children.length !== 1 || node.children[0].type !== NodeTypes.ELEMENT)
|
||||
const renderLoop = createFunctionExpression(
|
||||
createForLoopParams(node.parseResult)
|
||||
)
|
||||
@ -32,13 +37,17 @@ export function ssrProcessFor(node: ForNode, context: SSRTransformContext) {
|
||||
context,
|
||||
needFragmentWrapper
|
||||
)
|
||||
// v-for always renders a fragment
|
||||
context.pushStringPart(`<!--[-->`)
|
||||
// v-for always renders a fragment unless explicitly disabled
|
||||
if (!disableNestedFragments) {
|
||||
context.pushStringPart(`<!--[-->`)
|
||||
}
|
||||
context.pushStatement(
|
||||
createCallExpression(context.helper(SSR_RENDER_LIST), [
|
||||
node.source,
|
||||
renderLoop
|
||||
])
|
||||
)
|
||||
context.pushStringPart(`<!--]-->`)
|
||||
if (!disableNestedFragments) {
|
||||
context.pushStringPart(`<!--]-->`)
|
||||
}
|
||||
}
|
||||
|
@ -22,18 +22,26 @@ export const ssrTransformIf = createStructuralDirectiveTransform(
|
||||
|
||||
// This is called during the 2nd transform pass to construct the SSR-specific
|
||||
// codegen nodes.
|
||||
export function ssrProcessIf(node: IfNode, context: SSRTransformContext) {
|
||||
export function ssrProcessIf(
|
||||
node: IfNode,
|
||||
context: SSRTransformContext,
|
||||
disableNestedFragments = false
|
||||
) {
|
||||
const [rootBranch] = node.branches
|
||||
const ifStatement = createIfStatement(
|
||||
rootBranch.condition!,
|
||||
processIfBranch(rootBranch, context)
|
||||
processIfBranch(rootBranch, context, disableNestedFragments)
|
||||
)
|
||||
context.pushStatement(ifStatement)
|
||||
|
||||
let currentIf = ifStatement
|
||||
for (let i = 1; i < node.branches.length; i++) {
|
||||
const branch = node.branches[i]
|
||||
const branchBlockStatement = processIfBranch(branch, context)
|
||||
const branchBlockStatement = processIfBranch(
|
||||
branch,
|
||||
context,
|
||||
disableNestedFragments
|
||||
)
|
||||
if (branch.condition) {
|
||||
// else-if
|
||||
currentIf = currentIf.alternate = createIfStatement(
|
||||
@ -55,10 +63,12 @@ export function ssrProcessIf(node: IfNode, context: SSRTransformContext) {
|
||||
|
||||
function processIfBranch(
|
||||
branch: IfBranchNode,
|
||||
context: SSRTransformContext
|
||||
context: SSRTransformContext,
|
||||
disableNestedFragments = false
|
||||
): BlockStatement {
|
||||
const { children } = branch
|
||||
const needFragmentWrapper =
|
||||
!disableNestedFragments &&
|
||||
(children.length !== 1 || children[0].type !== NodeTypes.ELEMENT) &&
|
||||
// optimize away nested fragments when the only child is a ForNode
|
||||
!(children.length === 1 && children[0].type === NodeTypes.FOR)
|
||||
|
@ -471,7 +471,7 @@ export function getTransitionRawChildren(
|
||||
}
|
||||
// #1126 if a transition children list contains multiple sub fragments, these
|
||||
// fragments will be merged into a flat children array. Since each v-for
|
||||
// fragment may contain different static bindings inside, we need to de-top
|
||||
// fragment may contain different static bindings inside, we need to de-op
|
||||
// these children to force full diffs to ensure correct behavior.
|
||||
if (keyedFragmentCount > 1) {
|
||||
for (let i = 0; i < ret.length; i++) {
|
||||
|
Loading…
Reference in New Issue
Block a user