From e8c5de6cfd90724b2ed9552889d6d8f2fd21f4b5 Mon Sep 17 00:00:00 2001 From: Evan You Date: Mon, 3 Feb 2020 15:51:41 -0500 Subject: [PATCH] wip(compiler-ssr): v-if --- .../__snapshots__/compile.spec.ts.snap | 6 +- .../__snapshots__/hoistStatic.spec.ts.snap | 2 +- .../__snapshots__/vFor.spec.ts.snap | 2 +- .../transforms/__snapshots__/vIf.spec.ts.snap | 16 +- .../__tests__/transforms/vIf.spec.ts | 5 +- packages/compiler-core/src/ast.ts | 6 +- packages/compiler-core/src/index.ts | 6 +- packages/compiler-core/src/transforms/vIf.ts | 170 ++++++++++-------- .../compiler-ssr/__tests__/ssrElement.spec.ts | 2 +- .../compiler-ssr/__tests__/ssrText.spec.ts | 2 +- .../compiler-ssr/__tests__/ssrVIf.spec.ts | 141 +++++++++++++++ .../compiler-ssr/src/ssrCodegenTransform.ts | 26 +-- .../src/transforms/ssrTransformElement.ts | 27 --- .../compiler-ssr/src/transforms/ssrVIf.ts | 77 +++++++- 14 files changed, 351 insertions(+), 137 deletions(-) create mode 100644 packages/compiler-ssr/__tests__/ssrVIf.spec.ts diff --git a/packages/compiler-core/__tests__/__snapshots__/compile.spec.ts.snap b/packages/compiler-core/__tests__/__snapshots__/compile.spec.ts.snap index a5b0cc9b..d28911bd 100644 --- a/packages/compiler-core/__tests__/__snapshots__/compile.spec.ts.snap +++ b/packages/compiler-core/__tests__/__snapshots__/compile.spec.ts.snap @@ -5,7 +5,7 @@ exports[`compiler: integration tests function mode 1`] = ` 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\\", @@ -26,7 +26,7 @@ return function render() { `; 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 @@ -48,7 +48,7 @@ return function render() { `; 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 diff --git a/packages/compiler-core/__tests__/transforms/__snapshots__/hoistStatic.spec.ts.snap b/packages/compiler-core/__tests__/transforms/__snapshots__/hoistStatic.spec.ts.snap index 10db0e92..9e4a7c79 100644 --- a/packages/compiler-core/__tests__/transforms/__snapshots__/hoistStatic.spec.ts.snap +++ b/packages/compiler-core/__tests__/transforms/__snapshots__/hoistStatic.spec.ts.snap @@ -380,7 +380,7 @@ const _hoisted_2 = _createVNode(\\"span\\") 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 diff --git a/packages/compiler-core/__tests__/transforms/__snapshots__/vFor.spec.ts.snap b/packages/compiler-core/__tests__/transforms/__snapshots__/vFor.spec.ts.snap index edf9326f..8baed87f 100644 --- a/packages/compiler-core/__tests__/transforms/__snapshots__/vFor.spec.ts.snap +++ b/packages/compiler-core/__tests__/transforms/__snapshots__/vFor.spec.ts.snap @@ -155,7 +155,7 @@ exports[`compiler: v-for codegen v-if + v-for 1`] = ` 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) => { diff --git a/packages/compiler-core/__tests__/transforms/__snapshots__/vIf.spec.ts.snap b/packages/compiler-core/__tests__/transforms/__snapshots__/vIf.spec.ts.snap index d647b073..3ed8fc91 100644 --- a/packages/compiler-core/__tests__/transforms/__snapshots__/vIf.spec.ts.snap +++ b/packages/compiler-core/__tests__/transforms/__snapshots__/vIf.spec.ts.snap @@ -5,7 +5,7 @@ exports[`compiler: v-if codegen basic v-if 1`] = ` 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 }) @@ -19,7 +19,7 @@ exports[`compiler: v-if codegen template v-if 1`] = ` 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 }, [ @@ -37,7 +37,7 @@ exports[`compiler: v-if codegen template v-if w/ single child 1`] = ` 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 }) @@ -51,7 +51,7 @@ exports[`compiler: v-if codegen v-if + v-else 1`] = ` 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 }) @@ -65,7 +65,7 @@ exports[`compiler: v-if codegen v-if + v-else-if + v-else 1`] = ` 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 }) @@ -81,7 +81,7 @@ exports[`compiler: v-if codegen v-if + v-else-if 1`] = ` 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 }) @@ -97,7 +97,7 @@ exports[`compiler: v-if codegen v-if on 1`] = ` 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 }) @@ -111,7 +111,7 @@ exports[`compiler: v-if codegen v-if with key 1`] = ` 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\\" }) diff --git a/packages/compiler-core/__tests__/transforms/vIf.spec.ts b/packages/compiler-core/__tests__/transforms/vIf.spec.ts index b6c2e86a..9183b9d9 100644 --- a/packages/compiler-core/__tests__/transforms/vIf.spec.ts +++ b/packages/compiler-core/__tests__/transforms/vIf.spec.ts @@ -12,7 +12,8 @@ import { SimpleExpressionNode, SequenceExpression, ConditionalExpression, - CallExpression + CallExpression, + IfCodegenNode } from '../../src/ast' import { ErrorCodes } from '../../src/errors' import { CompilerOptions, generate } from '../../src' @@ -43,7 +44,7 @@ function parseWithIfTransform( } return { root: ast, - node: ast.children[returnIndex] as IfNode + node: ast.children[returnIndex] as IfNode & { codegenNode: IfCodegenNode } } } diff --git a/packages/compiler-core/src/ast.ts b/packages/compiler-core/src/ast.ts index 73efd832..d686c1aa 100644 --- a/packages/compiler-core/src/ast.ts +++ b/packages/compiler-core/src/ast.ts @@ -102,7 +102,7 @@ export interface RootNode extends Node { hoists: JSChildNode[] imports: ImportItem[] cached: number - codegenNode: TemplateChildNode | JSChildNode | BlockStatement | undefined + codegenNode?: TemplateChildNode | JSChildNode | BlockStatement | undefined } export type ElementNode = @@ -213,7 +213,7 @@ export interface CompoundExpressionNode extends Node { export interface IfNode extends Node { type: NodeTypes.IF branches: IfBranchNode[] - codegenNode: IfCodegenNode + codegenNode?: IfCodegenNode } export interface IfBranchNode extends Node { @@ -229,7 +229,7 @@ export interface ForNode extends Node { keyAlias: ExpressionNode | undefined objectIndexAlias: ExpressionNode | undefined children: TemplateChildNode[] - codegenNode: ForCodegenNode + codegenNode?: ForCodegenNode } export interface TextCallNode extends Node { diff --git a/packages/compiler-core/src/index.ts b/packages/compiler-core/src/index.ts index 43597910..b87cfe42 100644 --- a/packages/compiler-core/src/index.ts +++ b/packages/compiler-core/src/index.ts @@ -32,7 +32,11 @@ export { transformModel } from './transforms/vModel' 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' diff --git a/packages/compiler-core/src/transforms/vIf.ts b/packages/compiler-core/src/transforms/vIf.ts index 3dcc345e..53e197eb 100644 --- a/packages/compiler-core/src/transforms/vIf.ts +++ b/packages/compiler-core/src/transforms/vIf.ts @@ -23,7 +23,8 @@ import { BlockCodegenNode, SlotOutletCodegenNode, ElementCodegenNode, - ComponentCodegenNode + ComponentCodegenNode, + IfNode } from '../ast' import { createCompilerError, ErrorCodes } from '../errors' import { processExpression } from './transformExpression' @@ -40,73 +41,18 @@ import { injectProp } from '../utils' 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 === @@ -116,20 +62,92 @@ export const transformIf = createStructuralDirectiveTransform( } 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, @@ -171,25 +189,25 @@ function createChildrenCodegenNode( 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 diff --git a/packages/compiler-ssr/__tests__/ssrElement.spec.ts b/packages/compiler-ssr/__tests__/ssrElement.spec.ts index c4c9add5..cf9ca313 100644 --- a/packages/compiler-ssr/__tests__/ssrElement.spec.ts +++ b/packages/compiler-ssr/__tests__/ssrElement.spec.ts @@ -1,6 +1,6 @@ import { getCompiledString } from './utils' -describe('element', () => { +describe('ssr: element', () => { test('basic elements', () => { expect(getCompiledString(`
`)).toMatchInlineSnapshot( `"\`
\`"` diff --git a/packages/compiler-ssr/__tests__/ssrText.spec.ts b/packages/compiler-ssr/__tests__/ssrText.spec.ts index 9f0bb224..35ed4934 100644 --- a/packages/compiler-ssr/__tests__/ssrText.spec.ts +++ b/packages/compiler-ssr/__tests__/ssrText.spec.ts @@ -1,6 +1,6 @@ import { getCompiledString } from './utils' -describe('text', () => { +describe('ssr: text', () => { test('static text', () => { expect(getCompiledString(`foo`)).toMatchInlineSnapshot(`"\`foo\`"`) }) diff --git a/packages/compiler-ssr/__tests__/ssrVIf.spec.ts b/packages/compiler-ssr/__tests__/ssrVIf.spec.ts new file mode 100644 index 00000000..a5a80d12 --- /dev/null +++ b/packages/compiler-ssr/__tests__/ssrVIf.spec.ts @@ -0,0 +1,141 @@ +import { compile } from '../src' + +describe('ssr: v-if', () => { + test('basic', () => { + expect(compile(`
`).code).toMatchInlineSnapshot(` + " + return function ssrRender(_ctx, _push, _parent) { + if (_ctx.foo) { + _push(\`
\`) + } else { + _push(\`\`) + } + }" + `) + }) + + test('with nested content', () => { + expect(compile(`
hellook
`).code) + .toMatchInlineSnapshot(` + " + return function ssrRender(_ctx, _push, _parent) { + if (_ctx.foo) { + _push(\`
hellook
\`) + } else { + _push(\`\`) + } + }" + `) + }) + + test('v-if + v-else', () => { + expect(compile(`
`).code) + .toMatchInlineSnapshot(` + " + return function ssrRender(_ctx, _push, _parent) { + if (_ctx.foo) { + _push(\`
\`) + } else { + _push(\`\`) + } + }" + `) + }) + + test('v-if + v-else-if', () => { + expect(compile(`
`).code) + .toMatchInlineSnapshot(` + " + return function ssrRender(_ctx, _push, _parent) { + if (_ctx.foo) { + _push(\`
\`) + } else if (_ctx.bar) { + _push(\`\`) + } else { + _push(\`\`) + } + }" + `) + }) + + test('v-if + v-else-if + v-else', () => { + expect(compile(`

`).code) + .toMatchInlineSnapshot(` + " + return function ssrRender(_ctx, _push, _parent) { + if (_ctx.foo) { + _push(\`

\`) + } else if (_ctx.bar) { + _push(\`\`) + } else { + _push(\`

\`) + } + }" + `) + }) + + test('