SlotOutletNode,
ElementNode,
DirectiveNode,
- ForNode
+ ForNode,
+ PlainElementNode
} from '../ast'
import { createCompilerError, ErrorCodes } from '../errors'
import {
// finish the codegen now that all children have been traversed
let childBlock
const isTemplate = isTemplateNode(node)
+ const { children } = forNode
+ const needFragmentWrapper =
+ children.length > 1 || children[0].type !== NodeTypes.ELEMENT
const slotOutlet = isSlotOutlet(node)
? node
: isTemplate &&
// the props for renderSlot is passed as the 3rd argument.
injectProp(childBlock, keyProperty, context)
}
- } else if (isTemplate) {
- // <template v-for="...">
+ } else if (needFragmentWrapper) {
+ // <template v-for="..."> with text or multi-elements
// should generate a fragment block for each loop
childBlock = createBlockExpression(
createCallExpression(helper(CREATE_BLOCK), [
} else {
// Normal element v-for. Directly use the child's codegenNode
// arguments, but replace createVNode() with createBlock()
- let codegenNode = node.codegenNode as ElementCodegenNode
+ let codegenNode = (children[0] as PlainElementNode)
+ .codegenNode as ElementCodegenNode
if (codegenNode.callee === WITH_DIRECTIVES) {
codegenNode.arguments[0].callee = helper(CREATE_BLOCK)
} else {
--- /dev/null
+import { compile } from '../src'
+
+describe('ssr: v-for', () => {
+ test('basic', () => {
+ expect(compile(`<div v-for="i in list" />`).code).toMatchInlineSnapshot(`
+ "const { _renderList } = require(\\"@vue/server-renderer\\")
+
+ return function ssrRender(_ctx, _push, _parent) {
+ _push(\`<!---->\`)
+ _renderList(_ctx.list, (i) => {
+ _push(\`<div></div>\`)
+ })
+ _push(\`<!---->\`)
+ }"
+ `)
+ })
+
+ test('nested content', () => {
+ expect(compile(`<div v-for="i in list">foo<span>bar</span></div>`).code)
+ .toMatchInlineSnapshot(`
+ "const { _renderList } = require(\\"@vue/server-renderer\\")
+
+ return function ssrRender(_ctx, _push, _parent) {
+ _push(\`<!---->\`)
+ _renderList(_ctx.list, (i) => {
+ _push(\`<div>foo<span>bar</span></div>\`)
+ })
+ _push(\`<!---->\`)
+ }"
+ `)
+ })
+
+ test('nested v-for', () => {
+ expect(
+ compile(
+ `<div v-for="row, i in list">` +
+ `<div v-for="j in row">{{ i }},{{ j }}</div>` +
+ `</div>`
+ ).code
+ ).toMatchInlineSnapshot(`
+ "const { _interpolate, _renderList } = require(\\"@vue/server-renderer\\")
+
+ return function ssrRender(_ctx, _push, _parent) {
+ _push(\`<!---->\`)
+ _renderList(_ctx.list, (row, i) => {
+ _push(\`<div><!---->\`)
+ _renderList(row, (j) => {
+ _push(\`<div>\${_interpolate(i)},\${_interpolate(j)}</div>\`)
+ })
+ _push(\`<!----></div>\`)
+ })
+ _push(\`<!---->\`)
+ }"
+ `)
+ })
+
+ test('template v-for (text)', () => {
+ expect(compile(`<template v-for="i in list">{{ i }}</template>`).code)
+ .toMatchInlineSnapshot(`
+ "const { _interpolate, _renderList } = require(\\"@vue/server-renderer\\")
+
+ return function ssrRender(_ctx, _push, _parent) {
+ _push(\`<!---->\`)
+ _renderList(_ctx.list, (i) => {
+ _push(\`<!---->\${_interpolate(i)}<!---->\`)
+ })
+ _push(\`<!---->\`)
+ }"
+ `)
+ })
+
+ test('template v-for (single element)', () => {
+ expect(
+ compile(`<template v-for="i in list"><span>{{ i }}</span></template>`)
+ .code
+ ).toMatchInlineSnapshot(`
+ "const { _interpolate, _renderList } = require(\\"@vue/server-renderer\\")
+
+ return function ssrRender(_ctx, _push, _parent) {
+ _push(\`<!---->\`)
+ _renderList(_ctx.list, (i) => {
+ _push(\`<span>\${_interpolate(i)}</span>\`)
+ })
+ _push(\`<!---->\`)
+ }"
+ `)
+ })
+
+ test('template v-for (multi element)', () => {
+ expect(
+ compile(
+ `<template v-for="i in list"><span>{{ i }}</span><span>{{ i + 1 }}</span></template>`
+ ).code
+ ).toMatchInlineSnapshot(`
+ "const { _interpolate, _renderList } = require(\\"@vue/server-renderer\\")
+
+ return function ssrRender(_ctx, _push, _parent) {
+ _push(\`<!---->\`)
+ _renderList(_ctx.list, (i) => {
+ _push(\`<!----><span>\${_interpolate(i)}</span><span>\${_interpolate(i + 1)}</span><!---->\`)
+ })
+ _push(\`<!---->\`)
+ }"
+ `)
+ })
+
+ test('render loop args should not be prefixed', () => {
+ const { code } = compile(
+ `<div v-for="{ foo }, index in list">{{ foo + bar + index }}</div>`
+ )
+ expect(code).toMatch(`_ctx.bar`)
+ expect(code).not.toMatch(`_ctx.foo`)
+ expect(code).not.toMatch(`_ctx.index`)
+ expect(code).toMatchInlineSnapshot(`
+ "const { _interpolate, _renderList } = require(\\"@vue/server-renderer\\")
+
+ return function ssrRender(_ctx, _push, _parent) {
+ _push(\`<!---->\`)
+ _renderList(_ctx.list, ({ foo }, index) => {
+ _push(\`<div>\${_interpolate(foo + _ctx.bar + index)}</div>\`)
+ })
+ _push(\`<!---->\`)
+ }"
+ `)
+ })
+})
createCallExpression,
createFunctionExpression,
createForLoopParams,
- createBlockStatement
+ createBlockStatement,
+ NodeTypes
} from '@vue/compiler-dom'
import {
SSRTransformContext,
// This is called during the 2nd transform pass to construct the SSR-sepcific
// codegen nodes.
export function processFor(node: ForNode, context: SSRTransformContext) {
+ const childContext = createChildContext(context)
+ const needFragmentWrapper =
+ node.children.length !== 1 || node.children[0].type !== NodeTypes.ELEMENT
+ if (needFragmentWrapper) {
+ childContext.pushStringPart(`<!---->`)
+ }
+ processChildren(node.children, childContext)
+ if (needFragmentWrapper) {
+ childContext.pushStringPart(`<!---->`)
+ }
const renderLoop = createFunctionExpression(
createForLoopParams(node.parseResult)
)
- const childContext = createChildContext(context)
- processChildren(node.children, childContext)
renderLoop.body = createBlockStatement(childContext.body)
+
+ // v-for always renders a fragment
+ context.pushStringPart(`<!---->`)
context.pushStatement(
createCallExpression(context.helper(SSR_RENDER_LIST), [
node.source,
renderLoop
])
)
+ context.pushStringPart(`<!---->`)
}
context: SSRTransformContext
): BlockStatement {
const { children } = branch
- const firstChild = children[0]
- // TODO optimize away nested fragments when the only child is a ForNode
const needFragmentWrapper =
- children.length !== 1 || firstChild.type !== NodeTypes.ELEMENT
+ (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)
const childContext = createChildContext(context)
if (needFragmentWrapper) {
childContext.pushStringPart(`<!---->`)
}
- processChildren(branch.children, childContext)
+ processChildren(children, childContext)
if (needFragmentWrapper) {
childContext.pushStringPart(`<!---->`)
}