return function render() {
with (this) {
- const { toDisplayString: _toDisplayString, openBlock: _openBlock, createVNode: _createVNode, createBlock: _createBlock, createCommentVNode: _createCommentVNode, Fragment: _Fragment, renderList: _renderList, createTextVNode: _createTextVNode } = _Vue
+ const { toDisplayString: _toDisplayString, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock, createCommentVNode: _createCommentVNode, Fragment: _Fragment, renderList: _renderList, createTextVNode: _createTextVNode } = _Vue
return (_openBlock(), _createBlock(\\"div\\", {
id: \\"foo\\",
`;
exports[`compiler: integration tests function mode w/ prefixIdentifiers: true 1`] = `
-"const { toDisplayString, openBlock, createVNode, createBlock, createCommentVNode, Fragment, renderList, createTextVNode } = Vue
+"const { toDisplayString, createVNode, openBlock, createBlock, createCommentVNode, Fragment, renderList, createTextVNode } = Vue
return function render() {
const _ctx = this
`;
exports[`compiler: integration tests module mode 1`] = `
-"import { toDisplayString, openBlock, createVNode, createBlock, createCommentVNode, Fragment, renderList, createTextVNode } from \\"vue\\"
+"import { toDisplayString, createVNode, openBlock, createBlock, createCommentVNode, Fragment, renderList, createTextVNode } from \\"vue\\"
export function render() {
const _ctx = this
return function render() {
with (this) {
- const { openBlock: _openBlock, createVNode: _createVNode, createBlock: _createBlock, createCommentVNode: _createCommentVNode } = _Vue
+ const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock, createCommentVNode: _createCommentVNode } = _Vue
return (_openBlock(), _createBlock(\\"div\\", null, [
(_openBlock(), ok
return function render() {
with (this) {
- const { openBlock: _openBlock, renderList: _renderList, createBlock: _createBlock, Fragment: _Fragment, createVNode: _createVNode, createCommentVNode: _createCommentVNode } = _Vue
+ const { renderList: _renderList, openBlock: _openBlock, createBlock: _createBlock, Fragment: _Fragment, createVNode: _createVNode, createCommentVNode: _createCommentVNode } = _Vue
return (_openBlock(), ok
? _createBlock(_Fragment, { key: 0 }, _renderList(list, (i) => {
return function render() {
with (this) {
- const { openBlock: _openBlock, createVNode: _createVNode, createBlock: _createBlock, createCommentVNode: _createCommentVNode } = _Vue
+ const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock, createCommentVNode: _createCommentVNode } = _Vue
return (_openBlock(), ok
? _createBlock(\\"div\\", { key: 0 })
return function render() {
with (this) {
- const { openBlock: _openBlock, createVNode: _createVNode, Fragment: _Fragment, createBlock: _createBlock, createCommentVNode: _createCommentVNode } = _Vue
+ const { createVNode: _createVNode, openBlock: _openBlock, Fragment: _Fragment, createBlock: _createBlock, createCommentVNode: _createCommentVNode } = _Vue
return (_openBlock(), ok
? _createBlock(_Fragment, { key: 0 }, [
return function render() {
with (this) {
- const { openBlock: _openBlock, renderSlot: _renderSlot, createCommentVNode: _createCommentVNode } = _Vue
+ const { renderSlot: _renderSlot, openBlock: _openBlock, createCommentVNode: _createCommentVNode } = _Vue
return (_openBlock(), ok
? _renderSlot($slots, \\"default\\", { key: 0 })
return function render() {
with (this) {
- const { openBlock: _openBlock, createVNode: _createVNode, createBlock: _createBlock, createCommentVNode: _createCommentVNode } = _Vue
+ const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock, createCommentVNode: _createCommentVNode } = _Vue
return (_openBlock(), ok
? _createBlock(\\"div\\", { key: 0 })
return function render() {
with (this) {
- const { openBlock: _openBlock, createVNode: _createVNode, createBlock: _createBlock, createCommentVNode: _createCommentVNode, Fragment: _Fragment } = _Vue
+ const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock, createCommentVNode: _createCommentVNode, Fragment: _Fragment } = _Vue
return (_openBlock(), ok
? _createBlock(\\"div\\", { key: 0 })
return function render() {
with (this) {
- const { openBlock: _openBlock, createVNode: _createVNode, createBlock: _createBlock, createCommentVNode: _createCommentVNode } = _Vue
+ const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock, createCommentVNode: _createCommentVNode } = _Vue
return (_openBlock(), ok
? _createBlock(\\"div\\", { key: 0 })
return function render() {
with (this) {
- const { openBlock: _openBlock, renderSlot: _renderSlot, createCommentVNode: _createCommentVNode } = _Vue
+ const { renderSlot: _renderSlot, openBlock: _openBlock, createCommentVNode: _createCommentVNode } = _Vue
return (_openBlock(), ok
? _renderSlot($slots, \\"default\\", { key: 0 })
return function render() {
with (this) {
- const { openBlock: _openBlock, createVNode: _createVNode, createBlock: _createBlock, createCommentVNode: _createCommentVNode } = _Vue
+ const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock, createCommentVNode: _createCommentVNode } = _Vue
return (_openBlock(), ok
? _createBlock(\\"div\\", { key: \\"some-key\\" })
SimpleExpressionNode,
SequenceExpression,
ConditionalExpression,
- CallExpression
+ CallExpression,
+ IfCodegenNode
} from '../../src/ast'
import { ErrorCodes } from '../../src/errors'
import { CompilerOptions, generate } from '../../src'
}
return {
root: ast,
- node: ast.children[returnIndex] as IfNode
+ node: ast.children[returnIndex] as IfNode & { codegenNode: IfCodegenNode }
}
}
hoists: JSChildNode[]
imports: ImportItem[]
cached: number
- codegenNode: TemplateChildNode | JSChildNode | BlockStatement | undefined
+ codegenNode?: TemplateChildNode | JSChildNode | BlockStatement | undefined
}
export type ElementNode =
export interface IfNode extends Node {
type: NodeTypes.IF
branches: IfBranchNode[]
- codegenNode: IfCodegenNode
+ codegenNode?: IfCodegenNode
}
export interface IfBranchNode extends Node {
keyAlias: ExpressionNode | undefined
objectIndexAlias: ExpressionNode | undefined
children: TemplateChildNode[]
- codegenNode: ForCodegenNode
+ codegenNode?: ForCodegenNode
}
export interface TextCallNode extends Node {
export { transformOn } from './transforms/vOn'
// exported for compiler-ssr
-export { transformExpression } from './transforms/transformExpression'
+export { processIfBranches } from './transforms/vIf'
+export {
+ transformExpression,
+ processExpression
+} from './transforms/transformExpression'
export { trackVForSlotScopes, trackSlotScopes } from './transforms/vSlot'
export { buildProps } from './transforms/transformElement'
BlockCodegenNode,
SlotOutletCodegenNode,
ElementCodegenNode,
- ComponentCodegenNode
+ ComponentCodegenNode,
+ IfNode
} from '../ast'
import { createCompilerError, ErrorCodes } from '../errors'
import { processExpression } from './transformExpression'
export const transformIf = createStructuralDirectiveTransform(
/^(if|else|else-if)$/,
(node, dir, context) => {
- if (
- dir.name !== 'else' &&
- (!dir.exp || !(dir.exp as SimpleExpressionNode).content.trim())
- ) {
- const loc = dir.exp ? dir.exp.loc : node.loc
- context.onError(
- createCompilerError(ErrorCodes.X_V_IF_NO_EXPRESSION, dir.loc)
- )
- dir.exp = createSimpleExpression(`true`, false, loc)
- }
-
- if (!__BROWSER__ && context.prefixIdentifiers && dir.exp) {
- // dir.exp can only be simple expression because vIf transform is applied
- // before expression transform.
- dir.exp = processExpression(dir.exp as SimpleExpressionNode, context)
- }
-
- if (dir.name === 'if') {
- const branch = createIfBranch(node, dir)
- const codegenNode = createSequenceExpression([
- createCallExpression(context.helper(OPEN_BLOCK))
- ]) as IfCodegenNode
-
- context.replaceNode({
- type: NodeTypes.IF,
- loc: node.loc,
- branches: [branch],
- codegenNode
- })
-
+ return processIfBranches(node, dir, context, (ifNode, branch, isRoot) => {
// Exit callback. Complete the codegenNode when all children have been
// transformed.
return () => {
- codegenNode.expressions.push(createCodegenNodeForBranch(
- branch,
- 0,
- context
- ) as IfConditionalExpression)
- }
- } else {
- // locate the adjacent v-if
- const siblings = context.parent!.children
- const comments = []
- let i = siblings.indexOf(node)
- while (i-- >= -1) {
- const sibling = siblings[i]
- if (__DEV__ && sibling && sibling.type === NodeTypes.COMMENT) {
- context.removeNode(sibling)
- comments.unshift(sibling)
- continue
- }
- if (sibling && sibling.type === NodeTypes.IF) {
- // move the node to the if node's branches
- context.removeNode()
- const branch = createIfBranch(node, dir)
- if (__DEV__ && comments.length) {
- branch.children = [...comments, ...branch.children]
- }
- sibling.branches.push(branch)
- // since the branch was removed, it will not be traversed.
- // make sure to traverse here.
- traverseChildren(branch, context)
- // make sure to reset currentNode after traversal to indicate this
- // node has been removed.
- context.currentNode = null
+ if (isRoot) {
+ ifNode.codegenNode = createSequenceExpression([
+ createCallExpression(context.helper(OPEN_BLOCK)),
+ createCodegenNodeForBranch(branch, 0, context)
+ ]) as IfCodegenNode
+ } else {
// attach this branch's codegen node to the v-if root.
- let parentCondition = sibling.codegenNode
+ let parentCondition = ifNode.codegenNode!
.expressions[1] as ConditionalExpression
while (
parentCondition.alternate.type ===
}
parentCondition.alternate = createCodegenNodeForBranch(
branch,
- sibling.branches.length - 1,
+ ifNode.branches.length - 1,
context
)
- } else {
- context.onError(
- createCompilerError(ErrorCodes.X_V_ELSE_NO_ADJACENT_IF, node.loc)
- )
}
- break
}
- }
+ })
}
)
+export const processIfBranches = (
+ node: ElementNode,
+ dir: DirectiveNode,
+ context: TransformContext,
+ processCodegen?: (
+ node: IfNode,
+ branch: IfBranchNode,
+ isRoot: boolean
+ ) => (() => void) | void
+) => {
+ if (
+ dir.name !== 'else' &&
+ (!dir.exp || !(dir.exp as SimpleExpressionNode).content.trim())
+ ) {
+ const loc = dir.exp ? dir.exp.loc : node.loc
+ context.onError(
+ createCompilerError(ErrorCodes.X_V_IF_NO_EXPRESSION, dir.loc)
+ )
+ dir.exp = createSimpleExpression(`true`, false, loc)
+ }
+
+ if (!__BROWSER__ && context.prefixIdentifiers && dir.exp) {
+ // dir.exp can only be simple expression because vIf transform is applied
+ // before expression transform.
+ dir.exp = processExpression(dir.exp as SimpleExpressionNode, context)
+ }
+
+ if (dir.name === 'if') {
+ const branch = createIfBranch(node, dir)
+ const ifNode: IfNode = {
+ type: NodeTypes.IF,
+ loc: node.loc,
+ branches: [branch]
+ }
+ context.replaceNode(ifNode)
+ if (processCodegen) {
+ return processCodegen(ifNode, branch, true)
+ }
+ } else {
+ // locate the adjacent v-if
+ const siblings = context.parent!.children
+ const comments = []
+ let i = siblings.indexOf(node)
+ while (i-- >= -1) {
+ const sibling = siblings[i]
+ if (__DEV__ && sibling && sibling.type === NodeTypes.COMMENT) {
+ context.removeNode(sibling)
+ comments.unshift(sibling)
+ continue
+ }
+ if (sibling && sibling.type === NodeTypes.IF) {
+ // move the node to the if node's branches
+ context.removeNode()
+ const branch = createIfBranch(node, dir)
+ if (__DEV__ && comments.length) {
+ branch.children = [...comments, ...branch.children]
+ }
+ sibling.branches.push(branch)
+ const onExit = processCodegen && processCodegen(sibling, branch, false)
+ // since the branch was removed, it will not be traversed.
+ // make sure to traverse here.
+ traverseChildren(branch, context)
+ // call on exit
+ if (onExit) onExit()
+ // make sure to reset currentNode after traversal to indicate this
+ // node has been removed.
+ context.currentNode = null
+ } else {
+ context.onError(
+ createCompilerError(ErrorCodes.X_V_ELSE_NO_ADJACENT_IF, node.loc)
+ )
+ }
+ break
+ }
+ }
+}
+
function createIfBranch(node: ElementNode, dir: DirectiveNode): IfBranchNode {
return {
type: NodeTypes.IF_BRANCH,
createSimpleExpression(index + '', false)
)
const { children } = branch
- const child = children[0]
+ const firstChild = children[0]
const needFragmentWrapper =
- children.length !== 1 || child.type !== NodeTypes.ELEMENT
+ children.length !== 1 || firstChild.type !== NodeTypes.ELEMENT
if (needFragmentWrapper) {
const blockArgs: CallExpression['arguments'] = [
helper(FRAGMENT),
createObjectExpression([keyProperty]),
children
]
- if (children.length === 1 && child.type === NodeTypes.FOR) {
+ if (children.length === 1 && firstChild.type === NodeTypes.FOR) {
// optimize away nested fragments when child is a ForNode
- const forBlockArgs = child.codegenNode.expressions[1].arguments
+ const forBlockArgs = firstChild.codegenNode!.expressions[1].arguments
// directly use the for block's children and patchFlag
blockArgs[2] = forBlockArgs[2]
blockArgs[3] = forBlockArgs[3]
}
return createCallExpression(helper(CREATE_BLOCK), blockArgs)
} else {
- const childCodegen = (child as ElementNode).codegenNode as
+ const childCodegen = (firstChild as ElementNode).codegenNode as
| ElementCodegenNode
| ComponentCodegenNode
| SlotOutletCodegenNode
import { getCompiledString } from './utils'
-describe('element', () => {
+describe('ssr: element', () => {
test('basic elements', () => {
expect(getCompiledString(`<div></div>`)).toMatchInlineSnapshot(
`"\`<div></div>\`"`
import { getCompiledString } from './utils'
-describe('text', () => {
+describe('ssr: text', () => {
test('static text', () => {
expect(getCompiledString(`foo`)).toMatchInlineSnapshot(`"\`foo\`"`)
})
--- /dev/null
+import { compile } from '../src'
+
+describe('ssr: v-if', () => {
+ test('basic', () => {
+ expect(compile(`<div v-if="foo"></div>`).code).toMatchInlineSnapshot(`
+ "
+ return function ssrRender(_ctx, _push, _parent) {
+ if (_ctx.foo) {
+ _push(\`<div></div>\`)
+ } else {
+ _push(\`<!---->\`)
+ }
+ }"
+ `)
+ })
+
+ test('with nested content', () => {
+ expect(compile(`<div v-if="foo">hello<span>ok</span></div>`).code)
+ .toMatchInlineSnapshot(`
+ "
+ return function ssrRender(_ctx, _push, _parent) {
+ if (_ctx.foo) {
+ _push(\`<div>hello<span>ok</span></div>\`)
+ } else {
+ _push(\`<!---->\`)
+ }
+ }"
+ `)
+ })
+
+ test('v-if + v-else', () => {
+ expect(compile(`<div v-if="foo"/><span v-else/>`).code)
+ .toMatchInlineSnapshot(`
+ "
+ return function ssrRender(_ctx, _push, _parent) {
+ if (_ctx.foo) {
+ _push(\`<div></div>\`)
+ } else {
+ _push(\`<span></span>\`)
+ }
+ }"
+ `)
+ })
+
+ test('v-if + v-else-if', () => {
+ expect(compile(`<div v-if="foo"/><span v-else-if="bar"/>`).code)
+ .toMatchInlineSnapshot(`
+ "
+ return function ssrRender(_ctx, _push, _parent) {
+ if (_ctx.foo) {
+ _push(\`<div></div>\`)
+ } else if (_ctx.bar) {
+ _push(\`<span></span>\`)
+ } else {
+ _push(\`<!---->\`)
+ }
+ }"
+ `)
+ })
+
+ test('v-if + v-else-if + v-else', () => {
+ expect(compile(`<div v-if="foo"/><span v-else-if="bar"/><p v-else/>`).code)
+ .toMatchInlineSnapshot(`
+ "
+ return function ssrRender(_ctx, _push, _parent) {
+ if (_ctx.foo) {
+ _push(\`<div></div>\`)
+ } else if (_ctx.bar) {
+ _push(\`<span></span>\`)
+ } else {
+ _push(\`<p></p>\`)
+ }
+ }"
+ `)
+ })
+
+ test('<template v-if> (text)', () => {
+ expect(compile(`<template v-if="foo">hello</template>`).code)
+ .toMatchInlineSnapshot(`
+ "
+ return function ssrRender(_ctx, _push, _parent) {
+ if (_ctx.foo) {
+ _push(\`<!---->hello<!---->\`)
+ } else {
+ _push(\`<!---->\`)
+ }
+ }"
+ `)
+ })
+
+ test('<template v-if> (single element)', () => {
+ // single element should not wrap with fragment
+ expect(compile(`<template v-if="foo"><div>hi</div></template>`).code)
+ .toMatchInlineSnapshot(`
+ "
+ return function ssrRender(_ctx, _push, _parent) {
+ if (_ctx.foo) {
+ _push(\`<div>hi</div>\`)
+ } else {
+ _push(\`<!---->\`)
+ }
+ }"
+ `)
+ })
+
+ test('<template v-if> (multiple element)', () => {
+ expect(
+ compile(`<template v-if="foo"><div>hi</div><div>ho</div></template>`).code
+ ).toMatchInlineSnapshot(`
+ "
+ return function ssrRender(_ctx, _push, _parent) {
+ if (_ctx.foo) {
+ _push(\`<!----><div>hi</div><div>ho</div><!---->\`)
+ } else {
+ _push(\`<!---->\`)
+ }
+ }"
+ `)
+ })
+
+ test('<template v-if> (with v-for inside)', () => {
+ // TODO should not contain nested fragments
+ })
+
+ test('<template v-if> + normal v-else', () => {
+ expect(
+ compile(
+ `<template v-if="foo"><div>hi</div><div>ho</div></template><div v-else/>`
+ ).code
+ ).toMatchInlineSnapshot(`
+ "
+ return function ssrRender(_ctx, _push, _parent) {
+ if (_ctx.foo) {
+ _push(\`<!----><div>hi</div><div>ho</div><!---->\`)
+ } else {
+ _push(\`<div></div>\`)
+ }
+ }"
+ `)
+ })
+})
import {
RootNode,
BlockStatement,
- CallExpression,
TemplateLiteral,
createCallExpression,
createTemplateLiteral,
ElementTypes,
createBlockStatement,
CompilerOptions,
- isText
+ isText,
+ IfStatement,
+ CallExpression
} from '@vue/compiler-dom'
import { isString, escapeHtml, NO } from '@vue/shared'
import { INTERPOLATE } from './runtimeHelpers'
+import { processIf } from './transforms/ssrVIf'
// Because SSR codegen output is completely different from client-side output
// (e.g. multiple elements can be concatenated into a single template literal
ast.codegenNode = createBlockStatement(context.body)
}
-type SSRTransformContext = ReturnType<typeof createSSRTransformContext>
+export type SSRTransformContext = ReturnType<typeof createSSRTransformContext>
-function createSSRTransformContext(options: CompilerOptions) {
+export function createSSRTransformContext(options: CompilerOptions) {
const body: BlockStatement['body'] = []
- let currentCall: CallExpression | null = null
let currentString: TemplateLiteral | null = null
return {
options,
body,
pushStringPart(part: TemplateLiteral['elements'][0]) {
- if (!currentCall) {
- currentCall = createCallExpression(`_push`)
- body.push(currentCall)
- }
if (!currentString) {
+ const currentCall = createCallExpression(`_push`)
+ body.push(currentCall)
currentString = createTemplateLiteral([])
currentCall.arguments.push(currentString)
}
} else {
bufferedElements.push(part)
}
+ },
+ pushStatement(statement: IfStatement | CallExpression) {
+ // close current string
+ currentString = null
+ body.push(statement)
}
}
}
-function processChildren(
+export function processChildren(
children: TemplateChildNode[],
context: SSRTransformContext
) {
} else if (child.type === NodeTypes.INTERPOLATION) {
context.pushStringPart(createCallExpression(INTERPOLATE, [child.content]))
} else if (child.type === NodeTypes.IF) {
- // TODO
+ processIf(child, context)
} else if (child.type === NodeTypes.FOR) {
// TODO
}
} from '@vue/compiler-dom'
import { escapeHtml } from '@vue/shared'
-/*
-## Simple Element
-
-``` html
-<div></div>
-```
-``` js
-return function render(_ctx, _push, _parent) {
- _push(`<div></div>`)
-}
-```
-
-## Consecutive Elements
-
-``` html
-<div>
- <span></span>
-</div>
-<div></div>
-```
-``` js
-return function render(_ctx, _push, _parent) {
- _push(`<div><span></span></div><div></div>`)
-}
-```
-*/
-
export const ssrTransformElement: NodeTransform = (node, context) => {
if (
node.type === NodeTypes.ELEMENT &&
-import { NodeTransform } from '@vue/compiler-dom'
+import {
+ createStructuralDirectiveTransform,
+ processIfBranches,
+ IfNode,
+ createIfStatement,
+ createBlockStatement,
+ createCallExpression,
+ IfBranchNode,
+ BlockStatement,
+ NodeTypes
+} from '@vue/compiler-dom'
+import {
+ SSRTransformContext,
+ createSSRTransformContext,
+ processChildren
+} from '../ssrCodegenTransform'
-export const ssrTransformIf: NodeTransform = () => {}
+// This is the plugin for the first transform pass, which simply constructs the
+// if node and its branches.
+export const ssrTransformIf = createStructuralDirectiveTransform(
+ /^(if|else|else-if)$/,
+ processIfBranches
+)
+
+// This is called during the 2nd transform pass to construct the SSR-sepcific
+// codegen nodes.
+export function processIf(node: IfNode, context: SSRTransformContext) {
+ const [rootBranch] = node.branches
+ const ifStatement = createIfStatement(
+ rootBranch.condition!,
+ processIfBranch(rootBranch, context)
+ )
+ 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)
+ if (branch.condition) {
+ // else-if
+ currentIf = currentIf.alternate = createIfStatement(
+ branch.condition,
+ branchBlockStatement
+ )
+ } else {
+ // else
+ currentIf.alternate = branchBlockStatement
+ }
+ }
+
+ if (!currentIf.alternate) {
+ currentIf.alternate = createBlockStatement([
+ createCallExpression(`_push`, ['`<!---->`'])
+ ])
+ }
+}
+
+function processIfBranch(
+ branch: IfBranchNode,
+ 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
+ const childContext = createSSRTransformContext(context.options)
+ if (needFragmentWrapper) {
+ childContext.pushStringPart(`<!---->`)
+ }
+ processChildren(branch.children, childContext)
+ if (needFragmentWrapper) {
+ childContext.pushStringPart(`<!---->`)
+ }
+ return createBlockStatement(childContext.body)
+}