feat(compiler): hoist static trees
This commit is contained in:
@@ -90,7 +90,7 @@ export interface ElementNode extends Node {
|
||||
isSelfClosing: boolean
|
||||
props: Array<AttributeNode | DirectiveNode>
|
||||
children: TemplateChildNode[]
|
||||
codegenNode: CallExpression | undefined
|
||||
codegenNode: CallExpression | SimpleExpressionNode | undefined
|
||||
}
|
||||
|
||||
export interface TextNode extends Node {
|
||||
|
||||
@@ -185,8 +185,15 @@ export function generate(
|
||||
if (prefixIdentifiers) {
|
||||
push(`const { ${ast.imports.join(', ')} } = Vue\n`)
|
||||
} else {
|
||||
// "with" mode.
|
||||
// save Vue in a separate variable to avoid collision
|
||||
push(`const _Vue = Vue\n`)
|
||||
// in "with" mode, helpers are declared inside the with block to avoid
|
||||
// has check cost, but hosits are lifted out of the function - we need
|
||||
// to provide the helper here.
|
||||
if (ast.hoists.length) {
|
||||
push(`const _${CREATE_VNODE} = Vue.createVNode\n`)
|
||||
}
|
||||
}
|
||||
}
|
||||
genHoists(ast.hoists, context)
|
||||
|
||||
@@ -16,6 +16,7 @@ import { isString, isArray } from '@vue/shared'
|
||||
import { CompilerError, defaultOnError } from './errors'
|
||||
import { TO_STRING, COMMENT, CREATE_VNODE, FRAGMENT } from './runtimeConstants'
|
||||
import { isVSlot, createBlockExpression, isSlotOutlet } from './utils'
|
||||
import { hoistStaticTrees } from './transforms/hoistStatic'
|
||||
|
||||
// There are two types of transforms:
|
||||
//
|
||||
@@ -50,6 +51,7 @@ export interface TransformOptions {
|
||||
nodeTransforms?: NodeTransform[]
|
||||
directiveTransforms?: { [name: string]: DirectiveTransform }
|
||||
prefixIdentifiers?: boolean
|
||||
hoistStaticTrees?: boolean
|
||||
onError?: (error: CompilerError) => void
|
||||
}
|
||||
|
||||
@@ -81,6 +83,7 @@ function createTransformContext(
|
||||
root: RootNode,
|
||||
{
|
||||
prefixIdentifiers = false,
|
||||
hoistStaticTrees = false,
|
||||
nodeTransforms = [],
|
||||
directiveTransforms = {},
|
||||
onError = defaultOnError
|
||||
@@ -99,6 +102,7 @@ function createTransformContext(
|
||||
vOnce: 0
|
||||
},
|
||||
prefixIdentifiers,
|
||||
hoistStaticTrees,
|
||||
nodeTransforms,
|
||||
directiveTransforms,
|
||||
onError,
|
||||
@@ -200,6 +204,9 @@ function createTransformContext(
|
||||
export function transform(root: RootNode, options: TransformOptions) {
|
||||
const context = createTransformContext(root, options)
|
||||
traverseNode(root, context)
|
||||
if (options.hoistStaticTrees) {
|
||||
hoistStaticTrees(root, context)
|
||||
}
|
||||
finalizeRoot(root, context)
|
||||
}
|
||||
|
||||
@@ -211,7 +218,8 @@ function finalizeRoot(root: RootNode, context: TransformContext) {
|
||||
if (
|
||||
child.type === NodeTypes.ELEMENT &&
|
||||
!isSlotOutlet(child) &&
|
||||
child.codegenNode
|
||||
child.codegenNode &&
|
||||
child.codegenNode.type === NodeTypes.JS_CALL_EXPRESSION
|
||||
) {
|
||||
// turn root element into a block
|
||||
root.codegenNode = createBlockExpression(
|
||||
|
||||
98
packages/compiler-core/src/transforms/hoistStatic.ts
Normal file
98
packages/compiler-core/src/transforms/hoistStatic.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
import {
|
||||
RootNode,
|
||||
NodeTypes,
|
||||
TemplateChildNode,
|
||||
CallExpression,
|
||||
ElementNode
|
||||
} from '../ast'
|
||||
import { TransformContext } from '../transform'
|
||||
import { CREATE_VNODE } from '../runtimeConstants'
|
||||
import { PropsExpression } from './transformElement'
|
||||
|
||||
export function hoistStaticTrees(root: RootNode, context: TransformContext) {
|
||||
walk(root.children, context, new Set<TemplateChildNode>())
|
||||
}
|
||||
|
||||
function walk(
|
||||
children: TemplateChildNode[],
|
||||
context: TransformContext,
|
||||
knownStaticNodes: Set<TemplateChildNode>
|
||||
) {
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
const child = children[i]
|
||||
if (child.type === NodeTypes.ELEMENT) {
|
||||
if (isStaticNode(child, knownStaticNodes)) {
|
||||
// whole tree is static
|
||||
child.codegenNode = context.hoist(child.codegenNode!)
|
||||
continue
|
||||
} else if (!getPatchFlag(child)) {
|
||||
// has dynamic children, but self props are static, hoist props instead
|
||||
const props = (child.codegenNode as CallExpression).arguments[1] as
|
||||
| PropsExpression
|
||||
| `null`
|
||||
if (props !== `null`) {
|
||||
;(child.codegenNode as CallExpression).arguments[1] = context.hoist(
|
||||
props
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (child.type === NodeTypes.ELEMENT || child.type === NodeTypes.FOR) {
|
||||
walk(child.children, context, knownStaticNodes)
|
||||
} else if (child.type === NodeTypes.IF) {
|
||||
for (let i = 0; i < child.branches.length; i++) {
|
||||
walk(child.branches[i].children, context, knownStaticNodes)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getPatchFlag(node: ElementNode): number | undefined {
|
||||
const codegenNode = node.codegenNode as CallExpression
|
||||
if (
|
||||
// callee is createVNode (i.e. no runtime directives)
|
||||
codegenNode.callee.includes(CREATE_VNODE)
|
||||
) {
|
||||
const flag = codegenNode.arguments[3]
|
||||
return flag ? parseInt(flag as string, 10) : undefined
|
||||
}
|
||||
}
|
||||
|
||||
function isStaticNode(
|
||||
node: TemplateChildNode,
|
||||
knownStaticNodes: Set<TemplateChildNode>
|
||||
): boolean {
|
||||
switch (node.type) {
|
||||
case NodeTypes.ELEMENT:
|
||||
if (knownStaticNodes.has(node)) {
|
||||
return true
|
||||
}
|
||||
const flag = getPatchFlag(node)
|
||||
if (!flag) {
|
||||
// element self is static. check its children.
|
||||
for (let i = 0; i < node.children.length; i++) {
|
||||
if (!isStaticNode(node.children[i], knownStaticNodes)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
knownStaticNodes.add(node)
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case NodeTypes.TEXT:
|
||||
case NodeTypes.COMMENT:
|
||||
return true
|
||||
case NodeTypes.IF:
|
||||
case NodeTypes.FOR:
|
||||
case NodeTypes.INTERPOLATION:
|
||||
case NodeTypes.COMPOUND_EXPRESSION:
|
||||
return false
|
||||
default:
|
||||
if (__DEV__) {
|
||||
const exhaustiveCheck: never = node
|
||||
exhaustiveCheck
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,8 @@ import {
|
||||
createFunctionExpression,
|
||||
ElementTypes,
|
||||
createObjectExpression,
|
||||
createObjectProperty
|
||||
createObjectProperty,
|
||||
CallExpression
|
||||
} from '../ast'
|
||||
import { createCompilerError, ErrorCodes } from '../errors'
|
||||
import {
|
||||
@@ -117,7 +118,7 @@ export const transformFor = createStructuralDirectiveTransform(
|
||||
: null
|
||||
if (slotOutlet) {
|
||||
// <slot v-for="..."> or <template v-for="..."><slot/></template>
|
||||
childBlock = slotOutlet.codegenNode!
|
||||
childBlock = slotOutlet.codegenNode as CallExpression
|
||||
if (isTemplate && keyProperty) {
|
||||
// <template v-for="..." :key="..."><slot/></template>
|
||||
// we need to inject the key to the renderSlot() call.
|
||||
@@ -147,7 +148,7 @@ export const transformFor = createStructuralDirectiveTransform(
|
||||
// Normal element v-for. Directly use the child's codegenNode
|
||||
// arguments, but replace createVNode() with createBlock()
|
||||
childBlock = createBlockExpression(
|
||||
node.codegenNode!.arguments,
|
||||
(node.codegenNode as CallExpression).arguments,
|
||||
context
|
||||
)
|
||||
}
|
||||
|
||||
@@ -179,7 +179,7 @@ function createChildrenCodegenNode(
|
||||
}
|
||||
return createCallExpression(helper(CREATE_BLOCK), blockArgs)
|
||||
} else {
|
||||
const childCodegen = (child as ElementNode).codegenNode!
|
||||
const childCodegen = (child as ElementNode).codegenNode as CallExpression
|
||||
let vnodeCall = childCodegen
|
||||
// Element with custom directives. Locate the actual createVNode() call.
|
||||
if (vnodeCall.callee.includes(APPLY_DIRECTIVES)) {
|
||||
|
||||
@@ -175,7 +175,7 @@ export const isTemplateNode = (
|
||||
|
||||
export const isSlotOutlet = (
|
||||
node: RootNode | TemplateChildNode
|
||||
): node is ElementNode & { tagType: ElementTypes.SLOT } =>
|
||||
): node is ElementNode & { tagType: ElementTypes.ELEMENT } =>
|
||||
node.type === NodeTypes.ELEMENT && node.tagType === ElementTypes.SLOT
|
||||
|
||||
export function injectProp(
|
||||
|
||||
Reference in New Issue
Block a user