feat(compiler): block optimization codegen for RootNode
This commit is contained in:
@@ -78,6 +78,7 @@ export interface RootNode extends Node {
|
||||
imports: string[]
|
||||
statements: string[]
|
||||
hoists: JSChildNode[]
|
||||
codegenNode: TemplateChildNode | JSChildNode | undefined
|
||||
}
|
||||
|
||||
export interface ElementNode extends Node {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -86,6 +86,7 @@ export function parse(content: string, options: ParserOptions = {}): RootNode {
|
||||
imports: [],
|
||||
statements: [],
|
||||
hoists: [],
|
||||
codegenNode: undefined,
|
||||
loc: getSelection(context, start)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
])
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user