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

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

View File

@@ -1,6 +1,6 @@
import { getCompiledString } from './utils'
describe('text', () => {
describe('ssr: text', () => {
test('static text', () => {
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 {
RootNode,
BlockStatement,
CallExpression,
TemplateLiteral,
createCallExpression,
createTemplateLiteral,
@@ -10,10 +9,13 @@ import {
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
@@ -37,22 +39,19 @@ export function ssrCodegenTransform(ast: RootNode, options: CompilerOptions) {
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)
}
@@ -63,11 +62,16 @@ function createSSRTransformContext(options: CompilerOptions) {
} 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
) {
@@ -98,7 +102,7 @@ function processChildren(
} 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
}

View File

@@ -8,33 +8,6 @@ import {
} 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 &&

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)
}