ObjectExpression,
CompilerOptions,
ErrorCodes,
- NodeTypes
+ NodeTypes,
+ CallExpression
} from '../../src'
import { transformOn } from '../../src/transforms/vOn'
import { transformElement } from '../../src/transforms/transformElement'
describe('compiler: transform v-on', () => {
test('basic', () => {
const node = parseWithVOn(`<div v-on:click="onClick"/>`)
- 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`,
test('dynamic arg', () => {
const node = parseWithVOn(`<div v-on:[event]="handler"/>`)
- 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,
const node = parseWithVOn(`<div v-on:[event]="handler"/>`, {
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,
const node = parseWithVOn(`<div v-on:[event(foo)]="handler"/>`, {
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,
test('should wrap as function if expression is inline statement', () => {
const node = parseWithVOn(`<div @click="i++"/>`)
- 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: {
const node = parseWithVOn(`<div @click="foo($event)"/>`, {
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: {
test('should NOT wrap as function if expression is already function expression', () => {
const node = parseWithVOn(`<div @click="$event => 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: {
const node = parseWithVOn(`<div @click="e => 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: {
ElementNode,
NodeTypes,
ErrorCodes,
- ForNode
+ ForNode,
+ CallExpression
} from '../../src'
import { transformElement } from '../../src/transforms/transformElement'
import { transformOn } from '../../src/transforms/vOn'
root: ast,
slots:
ast.children[0].type === NodeTypes.ELEMENT
- ? ast.children[0].codegenNode!.arguments[2]
+ ? (ast.children[0].codegenNode as CallExpression).arguments[2]
: null
}
}
isSelfClosing: boolean
props: Array<AttributeNode | DirectiveNode>
children: TemplateChildNode[]
- codegenNode: CallExpression | undefined
+ codegenNode: CallExpression | SimpleExpressionNode | undefined
}
export interface TextNode extends Node {
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)
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:
//
nodeTransforms?: NodeTransform[]
directiveTransforms?: { [name: string]: DirectiveTransform }
prefixIdentifiers?: boolean
+ hoistStaticTrees?: boolean
onError?: (error: CompilerError) => void
}
root: RootNode,
{
prefixIdentifiers = false,
+ hoistStaticTrees = false,
nodeTransforms = [],
directiveTransforms = {},
onError = defaultOnError
vOnce: 0
},
prefixIdentifiers,
+ hoistStaticTrees,
nodeTransforms,
directiveTransforms,
onError,
export function transform(root: RootNode, options: TransformOptions) {
const context = createTransformContext(root, options)
traverseNode(root, context)
+ if (options.hoistStaticTrees) {
+ hoistStaticTrees(root, context)
+ }
finalizeRoot(root, context)
}
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(
--- /dev/null
+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
+ }
+}
createFunctionExpression,
ElementTypes,
createObjectExpression,
- createObjectProperty
+ createObjectProperty,
+ CallExpression
} from '../ast'
import { createCompilerError, ErrorCodes } from '../errors'
import {
: 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.
// 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
)
}
}
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)) {
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(
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'
bind: transformBind
}
})
- expect(node.codegenNode!.arguments[1]).toMatchObject({
+ expect((node.codegenNode as CallExpression).arguments[1]).toMatchObject({
type: NodeTypes.JS_OBJECT_EXPRESSION,
properties: [
{
template: string,
options?: CompilerOptions
): RenderFunction {
- const { code } = compile(template, options)
+ const { code } = compile(template, {
+ hoistStaticTrees: true,
+ ...options
+ })
return new Function(code)() as RenderFunction
}