Skip to content

Commit

Permalink
fix(compiler-ssr): generate correct children for transition-group
Browse files Browse the repository at this point in the history
fix #2510
  • Loading branch information
yyx990803 committed Nov 27, 2020
1 parent 55d99d7 commit a5d6f80
Show file tree
Hide file tree
Showing 7 changed files with 166 additions and 22 deletions.
96 changes: 88 additions & 8 deletions packages/compiler-ssr/__tests__/ssrComponent.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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\\")
Expand All @@ -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(\`<!--]-->\`)
}"
`)
})
})
})
})
7 changes: 4 additions & 3 deletions packages/compiler-ssr/src/ssrCodegenTransform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,8 @@ function createChildContext(
export function processChildren(
children: TemplateChildNode[],
context: SSRTransformContext,
asFragment = false
asFragment = false,
disableNestedFragments = false
) {
if (asFragment) {
context.pushStringPart(`<!--[-->`)
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
@@ -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)
}
}
19 changes: 14 additions & 5 deletions packages/compiler-ssr/src/transforms/ssrVFor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
)
Expand All @@ -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(`<!--]-->`)
}
}
18 changes: 14 additions & 4 deletions packages/compiler-ssr/src/transforms/ssrVIf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion packages/runtime-core/src/components/BaseTransition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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++) {
Expand Down

0 comments on commit a5d6f80

Please sign in to comment.