diff --git a/packages/compiler-core/__tests__/transforms/vOn.spec.ts b/packages/compiler-core/__tests__/transforms/vOn.spec.ts
index 9a7ba4500..114fd529c 100644
--- a/packages/compiler-core/__tests__/transforms/vOn.spec.ts
+++ b/packages/compiler-core/__tests__/transforms/vOn.spec.ts
@@ -5,7 +5,8 @@ import {
ObjectExpression,
CompilerOptions,
ErrorCodes,
- NodeTypes
+ NodeTypes,
+ CallExpression
} from '../../src'
import { transformOn } from '../../src/transforms/vOn'
import { transformElement } from '../../src/transforms/transformElement'
@@ -29,7 +30,8 @@ function parseWithVOn(
describe('compiler: transform v-on', () => {
test('basic', () => {
const node = parseWithVOn(`
`)
- const props = node.codegenNode!.arguments[1] as ObjectExpression
+ const props = (node.codegenNode as CallExpression)
+ .arguments[1] as ObjectExpression
expect(props.properties[0]).toMatchObject({
key: {
content: `onClick`,
@@ -64,7 +66,8 @@ describe('compiler: transform v-on', () => {
test('dynamic arg', () => {
const node = parseWithVOn(``)
- const props = node.codegenNode!.arguments[1] as ObjectExpression
+ const props = (node.codegenNode as CallExpression)
+ .arguments[1] as ObjectExpression
expect(props.properties[0]).toMatchObject({
key: {
type: NodeTypes.COMPOUND_EXPRESSION,
@@ -82,7 +85,8 @@ describe('compiler: transform v-on', () => {
const node = parseWithVOn(``, {
prefixIdentifiers: true
})
- const props = node.codegenNode!.arguments[1] as ObjectExpression
+ const props = (node.codegenNode as CallExpression)
+ .arguments[1] as ObjectExpression
expect(props.properties[0]).toMatchObject({
key: {
type: NodeTypes.COMPOUND_EXPRESSION,
@@ -100,7 +104,8 @@ describe('compiler: transform v-on', () => {
const node = parseWithVOn(``, {
prefixIdentifiers: true
})
- const props = node.codegenNode!.arguments[1] as ObjectExpression
+ const props = (node.codegenNode as CallExpression)
+ .arguments[1] as ObjectExpression
expect(props.properties[0]).toMatchObject({
key: {
type: NodeTypes.COMPOUND_EXPRESSION,
@@ -123,7 +128,8 @@ describe('compiler: transform v-on', () => {
test('should wrap as function if expression is inline statement', () => {
const node = parseWithVOn(``)
- const props = node.codegenNode!.arguments[1] as ObjectExpression
+ const props = (node.codegenNode as CallExpression)
+ .arguments[1] as ObjectExpression
expect(props.properties[0]).toMatchObject({
key: { content: `onClick` },
value: {
@@ -137,7 +143,8 @@ describe('compiler: transform v-on', () => {
const node = parseWithVOn(``, {
prefixIdentifiers: true
})
- const props = node.codegenNode!.arguments[1] as ObjectExpression
+ const props = (node.codegenNode as CallExpression)
+ .arguments[1] as ObjectExpression
expect(props.properties[0]).toMatchObject({
key: { content: `onClick` },
value: {
@@ -157,7 +164,8 @@ describe('compiler: transform v-on', () => {
test('should NOT wrap as function if expression is already function expression', () => {
const node = parseWithVOn(` foo($event)"/>`)
- const props = node.codegenNode!.arguments[1] as ObjectExpression
+ const props = (node.codegenNode as CallExpression)
+ .arguments[1] as ObjectExpression
expect(props.properties[0]).toMatchObject({
key: { content: `onClick` },
value: {
@@ -171,7 +179,8 @@ describe('compiler: transform v-on', () => {
const node = parseWithVOn(`
foo(e)"/>`, {
prefixIdentifiers: true
})
- const props = node.codegenNode!.arguments[1] as ObjectExpression
+ const props = (node.codegenNode as CallExpression)
+ .arguments[1] as ObjectExpression
expect(props.properties[0]).toMatchObject({
key: { content: `onClick` },
value: {
diff --git a/packages/compiler-core/__tests__/transforms/vSlot.spec.ts b/packages/compiler-core/__tests__/transforms/vSlot.spec.ts
index 1b5564246..9e7213e9b 100644
--- a/packages/compiler-core/__tests__/transforms/vSlot.spec.ts
+++ b/packages/compiler-core/__tests__/transforms/vSlot.spec.ts
@@ -6,7 +6,8 @@ import {
ElementNode,
NodeTypes,
ErrorCodes,
- ForNode
+ ForNode,
+ CallExpression
} from '../../src'
import { transformElement } from '../../src/transforms/transformElement'
import { transformOn } from '../../src/transforms/vOn'
@@ -44,7 +45,7 @@ function parseWithSlots(template: string, options: CompilerOptions = {}) {
root: ast,
slots:
ast.children[0].type === NodeTypes.ELEMENT
- ? ast.children[0].codegenNode!.arguments[2]
+ ? (ast.children[0].codegenNode as CallExpression).arguments[2]
: null
}
}
diff --git a/packages/compiler-core/src/ast.ts b/packages/compiler-core/src/ast.ts
index 4eff8ff83..23b8f9650 100644
--- a/packages/compiler-core/src/ast.ts
+++ b/packages/compiler-core/src/ast.ts
@@ -90,7 +90,7 @@ export interface ElementNode extends Node {
isSelfClosing: boolean
props: Array
children: TemplateChildNode[]
- codegenNode: CallExpression | undefined
+ codegenNode: CallExpression | SimpleExpressionNode | undefined
}
export interface TextNode extends Node {
diff --git a/packages/compiler-core/src/codegen.ts b/packages/compiler-core/src/codegen.ts
index 189e180b5..dfde1305e 100644
--- a/packages/compiler-core/src/codegen.ts
+++ b/packages/compiler-core/src/codegen.ts
@@ -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)
diff --git a/packages/compiler-core/src/transform.ts b/packages/compiler-core/src/transform.ts
index 588d3e498..01ee0ff2c 100644
--- a/packages/compiler-core/src/transform.ts
+++ b/packages/compiler-core/src/transform.ts
@@ -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(
diff --git a/packages/compiler-core/src/transforms/hoistStatic.ts b/packages/compiler-core/src/transforms/hoistStatic.ts
new file mode 100644
index 000000000..99594367d
--- /dev/null
+++ b/packages/compiler-core/src/transforms/hoistStatic.ts
@@ -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())
+}
+
+function walk(
+ children: TemplateChildNode[],
+ context: TransformContext,
+ knownStaticNodes: Set
+) {
+ 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
+): 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
+ }
+}
diff --git a/packages/compiler-core/src/transforms/vFor.ts b/packages/compiler-core/src/transforms/vFor.ts
index 30fbf24d6..6deaa3066 100644
--- a/packages/compiler-core/src/transforms/vFor.ts
+++ b/packages/compiler-core/src/transforms/vFor.ts
@@ -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) {
// or
- childBlock = slotOutlet.codegenNode!
+ childBlock = slotOutlet.codegenNode as CallExpression
if (isTemplate && keyProperty) {
//
// 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
)
}
diff --git a/packages/compiler-core/src/transforms/vIf.ts b/packages/compiler-core/src/transforms/vIf.ts
index 47eda2812..b78d395c3 100644
--- a/packages/compiler-core/src/transforms/vIf.ts
+++ b/packages/compiler-core/src/transforms/vIf.ts
@@ -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)) {
diff --git a/packages/compiler-core/src/utils.ts b/packages/compiler-core/src/utils.ts
index ab19a7d04..87d81e851 100644
--- a/packages/compiler-core/src/utils.ts
+++ b/packages/compiler-core/src/utils.ts
@@ -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(
diff --git a/packages/compiler-dom/__tests__/transforms/transformStyle.spec.ts b/packages/compiler-dom/__tests__/transforms/transformStyle.spec.ts
index b9410c7a5..87aa7f958 100644
--- a/packages/compiler-dom/__tests__/transforms/transformStyle.spec.ts
+++ b/packages/compiler-dom/__tests__/transforms/transformStyle.spec.ts
@@ -3,7 +3,8 @@ import {
transform,
CompilerOptions,
ElementNode,
- NodeTypes
+ NodeTypes,
+ CallExpression
} from '@vue/compiler-core'
import { transformBind } from '../../../compiler-core/src/transforms/vBind'
import { transformElement } from '../../../compiler-core/src/transforms/transformElement'
@@ -59,7 +60,7 @@ describe('compiler: style transform', () => {
bind: transformBind
}
})
- expect(node.codegenNode!.arguments[1]).toMatchObject({
+ expect((node.codegenNode as CallExpression).arguments[1]).toMatchObject({
type: NodeTypes.JS_OBJECT_EXPRESSION,
properties: [
{
diff --git a/packages/vue/src/index.ts b/packages/vue/src/index.ts
index e2baaaf6f..98c4b5981 100644
--- a/packages/vue/src/index.ts
+++ b/packages/vue/src/index.ts
@@ -7,7 +7,10 @@ function compileToFunction(
template: string,
options?: CompilerOptions
): RenderFunction {
- const { code } = compile(template, options)
+ const { code } = compile(template, {
+ hoistStaticTrees: true,
+ ...options
+ })
return new Function(code)() as RenderFunction
}