wip(compiler-ssr): v-if

This commit is contained in:
Evan You 2020-02-03 15:51:41 -05:00
parent 090eb0ce67
commit e8c5de6cfd
14 changed files with 351 additions and 137 deletions

View File

@ -5,7 +5,7 @@ exports[`compiler: integration tests function mode 1`] = `
return function render() { return function render() {
with (this) { 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\\", { return (_openBlock(), _createBlock(\\"div\\", {
id: \\"foo\\", id: \\"foo\\",
@ -26,7 +26,7 @@ return function render() {
`; `;
exports[`compiler: integration tests function mode w/ prefixIdentifiers: true 1`] = ` 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() { return function render() {
const _ctx = this const _ctx = this
@ -48,7 +48,7 @@ return function render() {
`; `;
exports[`compiler: integration tests module mode 1`] = ` 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() { export function render() {
const _ctx = this const _ctx = this

View File

@ -380,7 +380,7 @@ const _hoisted_2 = _createVNode(\\"span\\")
return function render() { return function render() {
with (this) { 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, [ return (_openBlock(), _createBlock(\\"div\\", null, [
(_openBlock(), ok (_openBlock(), ok

View File

@ -155,7 +155,7 @@ exports[`compiler: v-for codegen v-if + v-for 1`] = `
return function render() { return function render() {
with (this) { 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 return (_openBlock(), ok
? _createBlock(_Fragment, { key: 0 }, _renderList(list, (i) => { ? _createBlock(_Fragment, { key: 0 }, _renderList(list, (i) => {

View File

@ -5,7 +5,7 @@ exports[`compiler: v-if codegen basic v-if 1`] = `
return function render() { return function render() {
with (this) { 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 return (_openBlock(), ok
? _createBlock(\\"div\\", { key: 0 }) ? _createBlock(\\"div\\", { key: 0 })
@ -19,7 +19,7 @@ exports[`compiler: v-if codegen template v-if 1`] = `
return function render() { return function render() {
with (this) { 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 return (_openBlock(), ok
? _createBlock(_Fragment, { key: 0 }, [ ? _createBlock(_Fragment, { key: 0 }, [
@ -37,7 +37,7 @@ exports[`compiler: v-if codegen template v-if w/ single <slot/> child 1`] = `
return function render() { return function render() {
with (this) { with (this) {
const { openBlock: _openBlock, renderSlot: _renderSlot, createCommentVNode: _createCommentVNode } = _Vue const { renderSlot: _renderSlot, openBlock: _openBlock, createCommentVNode: _createCommentVNode } = _Vue
return (_openBlock(), ok return (_openBlock(), ok
? _renderSlot($slots, \\"default\\", { key: 0 }) ? _renderSlot($slots, \\"default\\", { key: 0 })
@ -51,7 +51,7 @@ exports[`compiler: v-if codegen v-if + v-else 1`] = `
return function render() { return function render() {
with (this) { 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 return (_openBlock(), ok
? _createBlock(\\"div\\", { key: 0 }) ? _createBlock(\\"div\\", { key: 0 })
@ -65,7 +65,7 @@ exports[`compiler: v-if codegen v-if + v-else-if + v-else 1`] = `
return function render() { return function render() {
with (this) { 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 return (_openBlock(), ok
? _createBlock(\\"div\\", { key: 0 }) ? _createBlock(\\"div\\", { key: 0 })
@ -81,7 +81,7 @@ exports[`compiler: v-if codegen v-if + v-else-if 1`] = `
return function render() { return function render() {
with (this) { 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 return (_openBlock(), ok
? _createBlock(\\"div\\", { key: 0 }) ? _createBlock(\\"div\\", { key: 0 })
@ -97,7 +97,7 @@ exports[`compiler: v-if codegen v-if on <slot/> 1`] = `
return function render() { return function render() {
with (this) { with (this) {
const { openBlock: _openBlock, renderSlot: _renderSlot, createCommentVNode: _createCommentVNode } = _Vue const { renderSlot: _renderSlot, openBlock: _openBlock, createCommentVNode: _createCommentVNode } = _Vue
return (_openBlock(), ok return (_openBlock(), ok
? _renderSlot($slots, \\"default\\", { key: 0 }) ? _renderSlot($slots, \\"default\\", { key: 0 })
@ -111,7 +111,7 @@ exports[`compiler: v-if codegen v-if with key 1`] = `
return function render() { return function render() {
with (this) { 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 return (_openBlock(), ok
? _createBlock(\\"div\\", { key: \\"some-key\\" }) ? _createBlock(\\"div\\", { key: \\"some-key\\" })

View File

@ -12,7 +12,8 @@ import {
SimpleExpressionNode, SimpleExpressionNode,
SequenceExpression, SequenceExpression,
ConditionalExpression, ConditionalExpression,
CallExpression CallExpression,
IfCodegenNode
} from '../../src/ast' } from '../../src/ast'
import { ErrorCodes } from '../../src/errors' import { ErrorCodes } from '../../src/errors'
import { CompilerOptions, generate } from '../../src' import { CompilerOptions, generate } from '../../src'
@ -43,7 +44,7 @@ function parseWithIfTransform(
} }
return { return {
root: ast, root: ast,
node: ast.children[returnIndex] as IfNode node: ast.children[returnIndex] as IfNode & { codegenNode: IfCodegenNode }
} }
} }

View File

@ -102,7 +102,7 @@ export interface RootNode extends Node {
hoists: JSChildNode[] hoists: JSChildNode[]
imports: ImportItem[] imports: ImportItem[]
cached: number cached: number
codegenNode: TemplateChildNode | JSChildNode | BlockStatement | undefined codegenNode?: TemplateChildNode | JSChildNode | BlockStatement | undefined
} }
export type ElementNode = export type ElementNode =
@ -213,7 +213,7 @@ export interface CompoundExpressionNode extends Node {
export interface IfNode extends Node { export interface IfNode extends Node {
type: NodeTypes.IF type: NodeTypes.IF
branches: IfBranchNode[] branches: IfBranchNode[]
codegenNode: IfCodegenNode codegenNode?: IfCodegenNode
} }
export interface IfBranchNode extends Node { export interface IfBranchNode extends Node {
@ -229,7 +229,7 @@ export interface ForNode extends Node {
keyAlias: ExpressionNode | undefined keyAlias: ExpressionNode | undefined
objectIndexAlias: ExpressionNode | undefined objectIndexAlias: ExpressionNode | undefined
children: TemplateChildNode[] children: TemplateChildNode[]
codegenNode: ForCodegenNode codegenNode?: ForCodegenNode
} }
export interface TextCallNode extends Node { export interface TextCallNode extends Node {

View File

@ -32,7 +32,11 @@ export { transformModel } from './transforms/vModel'
export { transformOn } from './transforms/vOn' export { transformOn } from './transforms/vOn'
// exported for compiler-ssr // 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 { trackVForSlotScopes, trackSlotScopes } from './transforms/vSlot'
export { buildProps } from './transforms/transformElement' export { buildProps } from './transforms/transformElement'

View File

@ -23,7 +23,8 @@ import {
BlockCodegenNode, BlockCodegenNode,
SlotOutletCodegenNode, SlotOutletCodegenNode,
ElementCodegenNode, ElementCodegenNode,
ComponentCodegenNode ComponentCodegenNode,
IfNode
} from '../ast' } from '../ast'
import { createCompilerError, ErrorCodes } from '../errors' import { createCompilerError, ErrorCodes } from '../errors'
import { processExpression } from './transformExpression' import { processExpression } from './transformExpression'
@ -40,73 +41,18 @@ import { injectProp } from '../utils'
export const transformIf = createStructuralDirectiveTransform( export const transformIf = createStructuralDirectiveTransform(
/^(if|else|else-if)$/, /^(if|else|else-if)$/,
(node, dir, context) => { (node, dir, context) => {
if ( return processIfBranches(node, dir, context, (ifNode, branch, isRoot) => {
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
})
// Exit callback. Complete the codegenNode when all children have been // Exit callback. Complete the codegenNode when all children have been
// transformed. // transformed.
return () => { return () => {
codegenNode.expressions.push(createCodegenNodeForBranch( if (isRoot) {
branch, ifNode.codegenNode = createSequenceExpression([
0, createCallExpression(context.helper(OPEN_BLOCK)),
context createCodegenNodeForBranch(branch, 0, context)
) as IfConditionalExpression) ]) as IfCodegenNode
} } else {
} 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
// attach this branch's codegen node to the v-if root. // attach this branch's codegen node to the v-if root.
let parentCondition = sibling.codegenNode let parentCondition = ifNode.codegenNode!
.expressions[1] as ConditionalExpression .expressions[1] as ConditionalExpression
while ( while (
parentCondition.alternate.type === parentCondition.alternate.type ===
@ -116,20 +62,92 @@ export const transformIf = createStructuralDirectiveTransform(
} }
parentCondition.alternate = createCodegenNodeForBranch( parentCondition.alternate = createCodegenNodeForBranch(
branch, branch,
sibling.branches.length - 1, ifNode.branches.length - 1,
context 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 { function createIfBranch(node: ElementNode, dir: DirectiveNode): IfBranchNode {
return { return {
type: NodeTypes.IF_BRANCH, type: NodeTypes.IF_BRANCH,
@ -171,25 +189,25 @@ function createChildrenCodegenNode(
createSimpleExpression(index + '', false) createSimpleExpression(index + '', false)
) )
const { children } = branch const { children } = branch
const child = children[0] const firstChild = children[0]
const needFragmentWrapper = const needFragmentWrapper =
children.length !== 1 || child.type !== NodeTypes.ELEMENT children.length !== 1 || firstChild.type !== NodeTypes.ELEMENT
if (needFragmentWrapper) { if (needFragmentWrapper) {
const blockArgs: CallExpression['arguments'] = [ const blockArgs: CallExpression['arguments'] = [
helper(FRAGMENT), helper(FRAGMENT),
createObjectExpression([keyProperty]), createObjectExpression([keyProperty]),
children 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 // 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 // directly use the for block's children and patchFlag
blockArgs[2] = forBlockArgs[2] blockArgs[2] = forBlockArgs[2]
blockArgs[3] = forBlockArgs[3] blockArgs[3] = forBlockArgs[3]
} }
return createCallExpression(helper(CREATE_BLOCK), blockArgs) return createCallExpression(helper(CREATE_BLOCK), blockArgs)
} else { } else {
const childCodegen = (child as ElementNode).codegenNode as const childCodegen = (firstChild as ElementNode).codegenNode as
| ElementCodegenNode | ElementCodegenNode
| ComponentCodegenNode | ComponentCodegenNode
| SlotOutletCodegenNode | SlotOutletCodegenNode

View File

@ -1,6 +1,6 @@
import { getCompiledString } from './utils' import { getCompiledString } from './utils'
describe('element', () => { describe('ssr: element', () => {
test('basic elements', () => { test('basic elements', () => {
expect(getCompiledString(`<div></div>`)).toMatchInlineSnapshot( expect(getCompiledString(`<div></div>`)).toMatchInlineSnapshot(
`"\`<div></div>\`"` `"\`<div></div>\`"`

View File

@ -1,6 +1,6 @@
import { getCompiledString } from './utils' import { getCompiledString } from './utils'
describe('text', () => { describe('ssr: text', () => {
test('static text', () => { test('static text', () => {
expect(getCompiledString(`foo`)).toMatchInlineSnapshot(`"\`foo\`"`) expect(getCompiledString(`foo`)).toMatchInlineSnapshot(`"\`foo\`"`)
}) })

View File

@ -0,0 +1,141 @@
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>\`)
}
}"
`)
})
})

View File

@ -1,7 +1,6 @@
import { import {
RootNode, RootNode,
BlockStatement, BlockStatement,
CallExpression,
TemplateLiteral, TemplateLiteral,
createCallExpression, createCallExpression,
createTemplateLiteral, createTemplateLiteral,
@ -10,10 +9,13 @@ import {
ElementTypes, ElementTypes,
createBlockStatement, createBlockStatement,
CompilerOptions, CompilerOptions,
isText isText,
IfStatement,
CallExpression
} from '@vue/compiler-dom' } from '@vue/compiler-dom'
import { isString, escapeHtml, NO } from '@vue/shared' import { isString, escapeHtml, NO } from '@vue/shared'
import { INTERPOLATE } from './runtimeHelpers' import { INTERPOLATE } from './runtimeHelpers'
import { processIf } from './transforms/ssrVIf'
// Because SSR codegen output is completely different from client-side output // Because SSR codegen output is completely different from client-side output
// (e.g. multiple elements can be concatenated into a single template literal // (e.g. multiple elements can be concatenated into a single template literal
@ -37,22 +39,19 @@ export function ssrCodegenTransform(ast: RootNode, options: CompilerOptions) {
ast.codegenNode = createBlockStatement(context.body) 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'] = [] const body: BlockStatement['body'] = []
let currentCall: CallExpression | null = null
let currentString: TemplateLiteral | null = null let currentString: TemplateLiteral | null = null
return { return {
options, options,
body, body,
pushStringPart(part: TemplateLiteral['elements'][0]) { pushStringPart(part: TemplateLiteral['elements'][0]) {
if (!currentCall) {
currentCall = createCallExpression(`_push`)
body.push(currentCall)
}
if (!currentString) { if (!currentString) {
const currentCall = createCallExpression(`_push`)
body.push(currentCall)
currentString = createTemplateLiteral([]) currentString = createTemplateLiteral([])
currentCall.arguments.push(currentString) currentCall.arguments.push(currentString)
} }
@ -63,11 +62,16 @@ function createSSRTransformContext(options: CompilerOptions) {
} else { } else {
bufferedElements.push(part) bufferedElements.push(part)
} }
},
pushStatement(statement: IfStatement | CallExpression) {
// close current string
currentString = null
body.push(statement)
} }
} }
} }
function processChildren( export function processChildren(
children: TemplateChildNode[], children: TemplateChildNode[],
context: SSRTransformContext context: SSRTransformContext
) { ) {
@ -98,7 +102,7 @@ function processChildren(
} else if (child.type === NodeTypes.INTERPOLATION) { } else if (child.type === NodeTypes.INTERPOLATION) {
context.pushStringPart(createCallExpression(INTERPOLATE, [child.content])) context.pushStringPart(createCallExpression(INTERPOLATE, [child.content]))
} else if (child.type === NodeTypes.IF) { } else if (child.type === NodeTypes.IF) {
// TODO processIf(child, context)
} else if (child.type === NodeTypes.FOR) { } else if (child.type === NodeTypes.FOR) {
// TODO // TODO
} }

View File

@ -8,33 +8,6 @@ import {
} from '@vue/compiler-dom' } from '@vue/compiler-dom'
import { escapeHtml } from '@vue/shared' 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) => { export const ssrTransformElement: NodeTransform = (node, context) => {
if ( if (
node.type === NodeTypes.ELEMENT && node.type === NodeTypes.ELEMENT &&

View File

@ -1,3 +1,76 @@
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)
}