mirror of https://github.com/vuejs/core.git
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)
|
expect(compile(`<keep-alive><foo/></keep-alive>`).code)
|
||||||
.toMatchInlineSnapshot(`
|
.toMatchInlineSnapshot(`
|
||||||
"const { resolveComponent: _resolveComponent } = require(\\"vue\\")
|
"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(
|
export function processChildren(
|
||||||
children: TemplateChildNode[],
|
children: TemplateChildNode[],
|
||||||
context: SSRTransformContext,
|
context: SSRTransformContext,
|
||||||
asFragment = false
|
asFragment = false,
|
||||||
|
disableNestedFragments = false
|
||||||
) {
|
) {
|
||||||
if (asFragment) {
|
if (asFragment) {
|
||||||
context.pushStringPart(`<!--[-->`)
|
context.pushStringPart(`<!--[-->`)
|
||||||
|
@ -176,10 +177,10 @@ export function processChildren(
|
||||||
)
|
)
|
||||||
break
|
break
|
||||||
case NodeTypes.IF:
|
case NodeTypes.IF:
|
||||||
ssrProcessIf(child, context)
|
ssrProcessIf(child, context, disableNestedFragments)
|
||||||
break
|
break
|
||||||
case NodeTypes.FOR:
|
case NodeTypes.FOR:
|
||||||
ssrProcessFor(child, context)
|
ssrProcessFor(child, context, disableNestedFragments)
|
||||||
break
|
break
|
||||||
case NodeTypes.IF_BRANCH:
|
case NodeTypes.IF_BRANCH:
|
||||||
// no-op - handled by ssrProcessIf
|
// no-op - handled by ssrProcessIf
|
||||||
|
|
|
@ -46,6 +46,7 @@ import {
|
||||||
ssrProcessSuspense,
|
ssrProcessSuspense,
|
||||||
ssrTransformSuspense
|
ssrTransformSuspense
|
||||||
} from './ssrTransformSuspense'
|
} from './ssrTransformSuspense'
|
||||||
|
import { ssrProcessTransitionGroup } from './ssrTransformTransitionGroup'
|
||||||
import { isSymbol, isObject, isArray } from '@vue/shared'
|
import { isSymbol, isObject, isArray } from '@vue/shared'
|
||||||
|
|
||||||
// We need to construct the slot functions in the 1st pass to ensure proper
|
// 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)
|
return ssrProcessTeleport(node, context)
|
||||||
} else if (component === SUSPENSE) {
|
} else if (component === SUSPENSE) {
|
||||||
return ssrProcessSuspense(node, context)
|
return ssrProcessSuspense(node, context)
|
||||||
|
} else if (component === TRANSITION_GROUP) {
|
||||||
|
return ssrProcessTransitionGroup(node, context)
|
||||||
} else {
|
} else {
|
||||||
// real fall-through (e.g. KeepAlive): just render its children.
|
// real fall-through (e.g. KeepAlive): just render its children.
|
||||||
processChildren(node.children, context, component === TRANSITION_GROUP)
|
processChildren(node.children, context)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// finish up slot function expressions from the 1st pass.
|
// 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
|
// This is called during the 2nd transform pass to construct the SSR-specific
|
||||||
// codegen nodes.
|
// codegen nodes.
|
||||||
export function ssrProcessFor(node: ForNode, context: SSRTransformContext) {
|
export function ssrProcessFor(
|
||||||
|
node: ForNode,
|
||||||
|
context: SSRTransformContext,
|
||||||
|
disableNestedFragments = false
|
||||||
|
) {
|
||||||
const needFragmentWrapper =
|
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(
|
const renderLoop = createFunctionExpression(
|
||||||
createForLoopParams(node.parseResult)
|
createForLoopParams(node.parseResult)
|
||||||
)
|
)
|
||||||
|
@ -32,13 +37,17 @@ export function ssrProcessFor(node: ForNode, context: SSRTransformContext) {
|
||||||
context,
|
context,
|
||||||
needFragmentWrapper
|
needFragmentWrapper
|
||||||
)
|
)
|
||||||
// v-for always renders a fragment
|
// v-for always renders a fragment unless explicitly disabled
|
||||||
context.pushStringPart(`<!--[-->`)
|
if (!disableNestedFragments) {
|
||||||
|
context.pushStringPart(`<!--[-->`)
|
||||||
|
}
|
||||||
context.pushStatement(
|
context.pushStatement(
|
||||||
createCallExpression(context.helper(SSR_RENDER_LIST), [
|
createCallExpression(context.helper(SSR_RENDER_LIST), [
|
||||||
node.source,
|
node.source,
|
||||||
renderLoop
|
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
|
// This is called during the 2nd transform pass to construct the SSR-specific
|
||||||
// codegen nodes.
|
// codegen nodes.
|
||||||
export function ssrProcessIf(node: IfNode, context: SSRTransformContext) {
|
export function ssrProcessIf(
|
||||||
|
node: IfNode,
|
||||||
|
context: SSRTransformContext,
|
||||||
|
disableNestedFragments = false
|
||||||
|
) {
|
||||||
const [rootBranch] = node.branches
|
const [rootBranch] = node.branches
|
||||||
const ifStatement = createIfStatement(
|
const ifStatement = createIfStatement(
|
||||||
rootBranch.condition!,
|
rootBranch.condition!,
|
||||||
processIfBranch(rootBranch, context)
|
processIfBranch(rootBranch, context, disableNestedFragments)
|
||||||
)
|
)
|
||||||
context.pushStatement(ifStatement)
|
context.pushStatement(ifStatement)
|
||||||
|
|
||||||
let currentIf = ifStatement
|
let currentIf = ifStatement
|
||||||
for (let i = 1; i < node.branches.length; i++) {
|
for (let i = 1; i < node.branches.length; i++) {
|
||||||
const branch = node.branches[i]
|
const branch = node.branches[i]
|
||||||
const branchBlockStatement = processIfBranch(branch, context)
|
const branchBlockStatement = processIfBranch(
|
||||||
|
branch,
|
||||||
|
context,
|
||||||
|
disableNestedFragments
|
||||||
|
)
|
||||||
if (branch.condition) {
|
if (branch.condition) {
|
||||||
// else-if
|
// else-if
|
||||||
currentIf = currentIf.alternate = createIfStatement(
|
currentIf = currentIf.alternate = createIfStatement(
|
||||||
|
@ -55,10 +63,12 @@ export function ssrProcessIf(node: IfNode, context: SSRTransformContext) {
|
||||||
|
|
||||||
function processIfBranch(
|
function processIfBranch(
|
||||||
branch: IfBranchNode,
|
branch: IfBranchNode,
|
||||||
context: SSRTransformContext
|
context: SSRTransformContext,
|
||||||
|
disableNestedFragments = false
|
||||||
): BlockStatement {
|
): BlockStatement {
|
||||||
const { children } = branch
|
const { children } = branch
|
||||||
const needFragmentWrapper =
|
const needFragmentWrapper =
|
||||||
|
!disableNestedFragments &&
|
||||||
(children.length !== 1 || children[0].type !== NodeTypes.ELEMENT) &&
|
(children.length !== 1 || children[0].type !== NodeTypes.ELEMENT) &&
|
||||||
// optimize away nested fragments when the only child is a ForNode
|
// optimize away nested fragments when the only child is a ForNode
|
||||||
!(children.length === 1 && children[0].type === NodeTypes.FOR)
|
!(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
|
// #1126 if a transition children list contains multiple sub fragments, these
|
||||||
// fragments will be merged into a flat children array. Since each v-for
|
// 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.
|
// these children to force full diffs to ensure correct behavior.
|
||||||
if (keyedFragmentCount > 1) {
|
if (keyedFragmentCount > 1) {
|
||||||
for (let i = 0; i < ret.length; i++) {
|
for (let i = 0; i < ret.length; i++) {
|
||||||
|
|
Loading…
Reference in New Issue