feat(compiler): block optimization codegen for RootNode

This commit is contained in:
Evan You
2019-10-02 13:11:07 -04:00
parent c0bf341748
commit 24bd6c27e0
15 changed files with 376 additions and 491 deletions

View File

@@ -78,6 +78,7 @@ export interface RootNode extends Node {
imports: string[]
statements: string[]
hoists: JSChildNode[]
codegenNode: TemplateChildNode | JSChildNode | undefined
}
export interface ElementNode extends Node {

View File

@@ -230,8 +230,11 @@ export function generate(
// generate the VNode tree expression
push(`return `)
genRoot(ast, context)
if (ast.codegenNode) {
genNode(ast.codegenNode, context)
} else {
push(`null`)
}
if (useWithBlock) {
deindent()
@@ -256,18 +259,6 @@ function genHoists(hoists: JSChildNode[], context: CodegenContext) {
context.newline()
}
function genRoot(root: RootNode, context: CodegenContext) {
// TODO handle blocks
const { children } = root
if (children.length === 0) {
context.push(`null`)
} else if (children.length === 1) {
genNode(children[0], context)
} else {
genNodeListAsArray(children, context)
}
}
function genNodeListAsArray(
nodes: (string | CodegenNode | TemplateChildNode[])[],
context: CodegenContext

View File

@@ -86,6 +86,7 @@ export function parse(content: string, options: ParserOptions = {}): RootNode {
imports: [],
statements: [],
hoists: [],
codegenNode: undefined,
loc: getSelection(context, start)
}
}

View File

@@ -9,11 +9,13 @@ import {
ExpressionNode,
createSimpleExpression,
JSChildNode,
SimpleExpressionNode
SimpleExpressionNode,
ElementTypes
} from './ast'
import { isString, isArray } from '@vue/shared'
import { CompilerError, defaultOnError } from './errors'
import { TO_STRING, COMMENT, CREATE_VNODE } from './runtimeConstants'
import { TO_STRING, COMMENT, CREATE_VNODE, FRAGMENT } from './runtimeConstants'
import { createBlockExpression } from './utils'
// There are two types of transforms:
//
@@ -182,6 +184,42 @@ function createTransformContext(
export function transform(root: RootNode, options: TransformOptions) {
const context = createTransformContext(root, options)
traverseNode(root, context)
finalizeRoot(root, context)
}
function finalizeRoot(root: RootNode, context: TransformContext) {
const { helper } = context
const { children } = root
if (children.length === 1) {
const child = children[0]
if (child.type === NodeTypes.ELEMENT && child.codegenNode) {
// only child is a <slot/> - it needs to be in a fragment block.
if (child.tagType === ElementTypes.SLOT) {
root.codegenNode = createBlockExpression(
[helper(FRAGMENT), `null`, child.codegenNode],
context
)
} else {
// turn root element into a block
root.codegenNode = createBlockExpression(
child.codegenNode.arguments,
context
)
}
} else {
// IfNode, ForNode, TextNodes or transform calls without transformElement.
// Just generate the node as-is
root.codegenNode = child
}
} else if (children.length > 1) {
// root has multiple nodes - return a fragment block.
root.codegenNode = createBlockExpression(
[helper(FRAGMENT), `null`, root.children],
context
)
}
// finalize meta information
root.imports = [...context.imports]
root.statements = [...context.statements]
root.hoists = context.hoists
@@ -214,8 +252,7 @@ export function traverseNode(
const { nodeTransforms } = context
const exitFns = []
for (let i = 0; i < nodeTransforms.length; i++) {
const plugin = nodeTransforms[i]
const onExit = plugin(node, context)
const onExit = nodeTransforms[i](node, context)
if (onExit) {
if (isArray(onExit)) {
exitFns.push(...onExit)
@@ -234,9 +271,9 @@ export function traverseNode(
switch (node.type) {
case NodeTypes.COMMENT:
context.helper(CREATE_VNODE)
// inject import for the Comment symbol, which is needed for creating
// comment nodes with `createVNode`
context.helper(CREATE_VNODE)
context.helper(COMMENT)
break
case NodeTypes.INTERPOLATION:

View File

@@ -27,7 +27,7 @@ import {
TO_HANDLERS
} from '../runtimeConstants'
import { getInnerRange } from '../utils'
import { buildSlots } from './vSlot'
import { buildSlots, isVSlot } from './vSlot'
const toValidId = (str: string): string => str.replace(/[^\w]/g, '')
@@ -36,7 +36,10 @@ export const transformElement: NodeTransform = (node, context) => {
if (node.type === NodeTypes.ELEMENT) {
if (
node.tagType === ElementTypes.ELEMENT ||
node.tagType === ElementTypes.COMPONENT
node.tagType === ElementTypes.COMPONENT ||
// <template> with v-if or v-for are ignored during traversal.
// <template> without v-slot should be treated as a normal element.
(node.tagType === ElementTypes.TEMPLATE && !node.props.some(isVSlot))
) {
// perform the work on exit, after all child expressions have been
// processed and merged.

View File

@@ -19,7 +19,7 @@ import {
CallExpression
} from '../ast'
import { createCompilerError, ErrorCodes } from '../errors'
import { getInnerRange, findProp } from '../utils'
import { getInnerRange, findProp, createBlockExpression } from '../utils'
import {
RENDER_LIST,
OPEN_BLOCK,
@@ -69,7 +69,8 @@ export const transformFor = createStructuralDirectiveTransform(
valueAlias: value,
keyAlias: key,
objectIndexAlias: index,
children: [node],
children:
node.tagType === ElementTypes.TEMPLATE ? node.children : [node],
codegenNode
})
@@ -126,6 +127,8 @@ export const transformFor = createStructuralDirectiveTransform(
}
let childBlockChildren: TemplateChildNode[] | CallExpression =
node.children
// if the only child is a <slot/>, use it directly as fragment
// children since it already returns an array.
if (childBlockChildren.length === 1) {
const child = childBlockChildren[0]
if (
@@ -135,22 +138,17 @@ export const transformFor = createStructuralDirectiveTransform(
childBlockChildren = child.codegenNode!
}
}
childBlock = createSequenceExpression([
createCallExpression(helper(OPEN_BLOCK)),
createCallExpression(helper(CREATE_BLOCK), [
helper(FRAGMENT),
childBlockProps,
childBlockChildren
])
])
childBlock = createBlockExpression(
[helper(FRAGMENT), childBlockProps, childBlockChildren],
context
)
} else {
// Normal element v-for. Directly use the child's codegenNode,
// Normal element v-for. Directly use the child's codegenNode arguments,
// but replace createVNode() with createBlock()
node.codegenNode!.callee = helper(CREATE_BLOCK)
childBlock = createSequenceExpression([
createCallExpression(helper(OPEN_BLOCK)),
node.codegenNode!
])
childBlock = createBlockExpression(
node.codegenNode!.arguments,
context
)
}
renderExp.arguments.push(

View File

@@ -17,7 +17,7 @@ import { TransformContext, NodeTransform } from '../transform'
import { createCompilerError, ErrorCodes } from '../errors'
import { isString } from '@vue/shared'
const isVSlot = (p: ElementNode['props'][0]): p is DirectiveNode =>
export const isVSlot = (p: ElementNode['props'][0]): p is DirectiveNode =>
p.type === NodeTypes.DIRECTIVE && p.name === 'slot'
// A NodeTransform that tracks scope identifiers for scoped slots so that they

View File

@@ -1,6 +1,17 @@
import { SourceLocation, Position, ElementNode, NodeTypes } from './ast'
import {
SourceLocation,
Position,
ElementNode,
NodeTypes,
CallExpression,
SequenceExpression,
createSequenceExpression,
createCallExpression
} from './ast'
import { parse } from 'acorn'
import { walk } from 'estree-walker'
import { TransformContext } from './transform'
import { OPEN_BLOCK, CREATE_BLOCK } from './runtimeConstants'
// cache node requires
// lazy require dependencies so that they don't end up in rollup's dep graph
@@ -116,3 +127,13 @@ export function findProp(
}
}
}
export function createBlockExpression(
args: CallExpression['arguments'],
context: TransformContext
): SequenceExpression {
return createSequenceExpression([
createCallExpression(context.helper(OPEN_BLOCK)),
createCallExpression(context.helper(CREATE_BLOCK), args)
])
}