perf: prevent renderer hot functions being inlined by minifiers

Terser will aggressively inline hot functions in renderer.ts in order
to reduce "function" declarations, but the inlining leads to performance
overhead (small, but noticeable in benchmarks).

Since we cannot control user's minifier options, we have to avoid the
deopt in the source code by using arrow functions in hot paths.
This commit is contained in:
Evan You 2020-02-14 03:22:52 -05:00
parent f71a50ae96
commit 629ee75588
2 changed files with 74 additions and 73 deletions

View File

@ -12,6 +12,7 @@ import { ComponentInternalInstance } from './component'
import { invokeDirectiveHook } from './directives' import { invokeDirectiveHook } from './directives'
import { warn } from './warning' import { warn } from './warning'
import { PatchFlags, ShapeFlags, isReservedProp, isOn } from '@vue/shared' import { PatchFlags, ShapeFlags, isReservedProp, isOn } from '@vue/shared'
import { RendererOptions } from './renderer'
// Note: hydration is DOM-specific // Note: hydration is DOM-specific
// But we have to place it in core due to tight coupling with core - splitting // But we have to place it in core due to tight coupling with core - splitting
@ -20,9 +21,9 @@ import { PatchFlags, ShapeFlags, isReservedProp, isOn } from '@vue/shared'
// passed in via arguments. // passed in via arguments.
export function createHydrationFunctions( export function createHydrationFunctions(
mountComponent: any, // TODO mountComponent: any, // TODO
patchProp: any // TODO patchProp: RendererOptions['patchProp']
) { ) {
function hydrate(vnode: VNode, container: Element) { const hydrate = (vnode: VNode, container: Element) => {
if (__DEV__ && !container.hasChildNodes()) { if (__DEV__ && !container.hasChildNodes()) {
warn(`Attempting to hydrate existing markup but container is empty.`) warn(`Attempting to hydrate existing markup but container is empty.`)
return return
@ -34,11 +35,11 @@ export function createHydrationFunctions(
// TODO handle mismatches // TODO handle mismatches
// TODO SVG // TODO SVG
// TODO Suspense // TODO Suspense
function hydrateNode( const hydrateNode = (
node: Node, node: Node,
vnode: VNode, vnode: VNode,
parentComponent: ComponentInternalInstance | null = null parentComponent: ComponentInternalInstance | null = null
): Node | null | undefined { ): Node | null | undefined => {
const { type, shapeFlag } = vnode const { type, shapeFlag } = vnode
vnode.el = node vnode.el = node
switch (type) { switch (type) {
@ -73,11 +74,11 @@ export function createHydrationFunctions(
} }
} }
function hydrateElement( const hydrateElement = (
el: Element, el: Element,
vnode: VNode, vnode: VNode,
parentComponent: ComponentInternalInstance | null parentComponent: ComponentInternalInstance | null
) { ) => {
const { props, patchFlag } = vnode const { props, patchFlag } = vnode
// skip props & children if this is hoisted static nodes // skip props & children if this is hoisted static nodes
if (patchFlag !== PatchFlags.HOISTED) { if (patchFlag !== PatchFlags.HOISTED) {
@ -124,11 +125,11 @@ export function createHydrationFunctions(
return el.nextSibling return el.nextSibling
} }
function hydrateChildren( const hydrateChildren = (
node: Node | null | undefined, node: Node | null | undefined,
vnodes: VNode[], vnodes: VNode[],
parentComponent: ComponentInternalInstance | null parentComponent: ComponentInternalInstance | null
): Node | null | undefined { ): Node | null | undefined => {
for (let i = 0; node != null && i < vnodes.length; i++) { for (let i = 0; node != null && i < vnodes.length; i++) {
// TODO can skip normalizeVNode in optimized mode // TODO can skip normalizeVNode in optimized mode
// (need hint on rendered markup?) // (need hint on rendered markup?)

View File

@ -233,21 +233,7 @@ function baseCreateRenderer<
insertStaticContent: hostInsertStaticContent insertStaticContent: hostInsertStaticContent
} = options } = options
const internals: RendererInternals<HostNode, HostElement> = { const patch = (
patch,
unmount,
move,
next: getNextHostNode,
options
}
let hydrate: ReturnType<typeof createHydrationFunctions>[0] | undefined
let hydrateNode: ReturnType<typeof createHydrationFunctions>[1] | undefined
if (createHydrationFns) {
;[hydrate, hydrateNode] = createHydrationFns(mountComponent, hostPatchProp)
}
function patch(
n1: HostVNode | null, // null means this is a mount n1: HostVNode | null, // null means this is a mount
n2: HostVNode, n2: HostVNode,
container: HostElement, container: HostElement,
@ -256,7 +242,7 @@ function baseCreateRenderer<
parentSuspense: HostSuspenseBoundary | null = null, parentSuspense: HostSuspenseBoundary | null = null,
isSVG: boolean = false, isSVG: boolean = false,
optimized: boolean = false optimized: boolean = false
) { ) => {
// patching & not same type, unmount old tree // patching & not same type, unmount old tree
if (n1 != null && !isSameVNodeType(n1, n2)) { if (n1 != null && !isSameVNodeType(n1, n2)) {
anchor = getNextHostNode(n1) anchor = getNextHostNode(n1)
@ -342,12 +328,12 @@ function baseCreateRenderer<
} }
} }
function processText( const processText = (
n1: HostVNode | null, n1: HostVNode | null,
n2: HostVNode, n2: HostVNode,
container: HostElement, container: HostElement,
anchor: HostNode | null anchor: HostNode | null
) { ) => {
if (n1 == null) { if (n1 == null) {
hostInsert( hostInsert(
(n2.el = hostCreateText(n2.children as string)), (n2.el = hostCreateText(n2.children as string)),
@ -362,12 +348,12 @@ function baseCreateRenderer<
} }
} }
function processCommentNode( const processCommentNode = (
n1: HostVNode | null, n1: HostVNode | null,
n2: HostVNode, n2: HostVNode,
container: HostElement, container: HostElement,
anchor: HostNode | null anchor: HostNode | null
) { ) => {
if (n1 == null) { if (n1 == null) {
hostInsert( hostInsert(
(n2.el = hostCreateComment((n2.children as string) || '')), (n2.el = hostCreateComment((n2.children as string) || '')),
@ -380,12 +366,12 @@ function baseCreateRenderer<
} }
} }
function mountStaticNode( const mountStaticNode = (
n2: HostVNode, n2: HostVNode,
container: HostElement, container: HostElement,
anchor: HostNode | null, anchor: HostNode | null,
isSVG: boolean isSVG: boolean
) { ) => {
if (n2.el != null && hostCloneNode !== undefined) { if (n2.el != null && hostCloneNode !== undefined) {
hostInsert(hostCloneNode(n2.el), container, anchor) hostInsert(hostCloneNode(n2.el), container, anchor)
} else { } else {
@ -400,7 +386,7 @@ function baseCreateRenderer<
} }
} }
function processElement( const processElement = (
n1: HostVNode | null, n1: HostVNode | null,
n2: HostVNode, n2: HostVNode,
container: HostElement, container: HostElement,
@ -409,7 +395,7 @@ function baseCreateRenderer<
parentSuspense: HostSuspenseBoundary | null, parentSuspense: HostSuspenseBoundary | null,
isSVG: boolean, isSVG: boolean,
optimized: boolean optimized: boolean
) { ) => {
isSVG = isSVG || (n2.type as string) === 'svg' isSVG = isSVG || (n2.type as string) === 'svg'
if (n1 == null) { if (n1 == null) {
mountElement( mountElement(
@ -429,7 +415,7 @@ function baseCreateRenderer<
} }
} }
function mountElement( const mountElement = (
vnode: HostVNode, vnode: HostVNode,
container: HostElement, container: HostElement,
anchor: HostNode | null, anchor: HostNode | null,
@ -437,7 +423,7 @@ function baseCreateRenderer<
parentSuspense: HostSuspenseBoundary | null, parentSuspense: HostSuspenseBoundary | null,
isSVG: boolean, isSVG: boolean,
optimized: boolean optimized: boolean
) { ) => {
let el: HostElement let el: HostElement
const { type, props, shapeFlag, transition, scopeId, patchFlag } = vnode const { type, props, shapeFlag, transition, scopeId, patchFlag } = vnode
if ( if (
@ -509,7 +495,7 @@ function baseCreateRenderer<
} }
} }
function mountChildren( const mountChildren = (
children: HostVNodeChildren, children: HostVNodeChildren,
container: HostElement, container: HostElement,
anchor: HostNode | null, anchor: HostNode | null,
@ -518,7 +504,7 @@ function baseCreateRenderer<
isSVG: boolean, isSVG: boolean,
optimized: boolean, optimized: boolean,
start: number = 0 start: number = 0
) { ) => {
for (let i = start; i < children.length; i++) { for (let i = start; i < children.length; i++) {
const child = (children[i] = optimized const child = (children[i] = optimized
? cloneIfMounted(children[i] as HostVNode) ? cloneIfMounted(children[i] as HostVNode)
@ -536,14 +522,14 @@ function baseCreateRenderer<
} }
} }
function patchElement( const patchElement = (
n1: HostVNode, n1: HostVNode,
n2: HostVNode, n2: HostVNode,
parentComponent: ComponentInternalInstance | null, parentComponent: ComponentInternalInstance | null,
parentSuspense: HostSuspenseBoundary | null, parentSuspense: HostSuspenseBoundary | null,
isSVG: boolean, isSVG: boolean,
optimized: boolean optimized: boolean
) { ) => {
const el = (n2.el = n1.el) as HostElement const el = (n2.el = n1.el) as HostElement
let { patchFlag, dynamicChildren } = n2 let { patchFlag, dynamicChildren } = n2
const oldProps = (n1 && n1.props) || EMPTY_OBJ const oldProps = (n1 && n1.props) || EMPTY_OBJ
@ -673,14 +659,14 @@ function baseCreateRenderer<
} }
// The fast path for blocks. // The fast path for blocks.
function patchBlockChildren( const patchBlockChildren = (
oldChildren: HostVNode[], oldChildren: HostVNode[],
newChildren: HostVNode[], newChildren: HostVNode[],
fallbackContainer: HostElement, fallbackContainer: HostElement,
parentComponent: ComponentInternalInstance | null, parentComponent: ComponentInternalInstance | null,
parentSuspense: HostSuspenseBoundary | null, parentSuspense: HostSuspenseBoundary | null,
isSVG: boolean isSVG: boolean
) { ) => {
for (let i = 0; i < newChildren.length; i++) { for (let i = 0; i < newChildren.length; i++) {
const oldVNode = oldChildren[i] const oldVNode = oldChildren[i]
const newVNode = newChildren[i] const newVNode = newChildren[i]
@ -711,7 +697,7 @@ function baseCreateRenderer<
} }
} }
function patchProps( const patchProps = (
el: HostElement, el: HostElement,
vnode: HostVNode, vnode: HostVNode,
oldProps: Data, oldProps: Data,
@ -719,7 +705,7 @@ function baseCreateRenderer<
parentComponent: ComponentInternalInstance | null, parentComponent: ComponentInternalInstance | null,
parentSuspense: HostSuspenseBoundary | null, parentSuspense: HostSuspenseBoundary | null,
isSVG: boolean isSVG: boolean
) { ) => {
if (oldProps !== newProps) { if (oldProps !== newProps) {
for (const key in newProps) { for (const key in newProps) {
if (isReservedProp(key)) continue if (isReservedProp(key)) continue
@ -761,7 +747,7 @@ function baseCreateRenderer<
let devFragmentID = 0 let devFragmentID = 0
function processFragment( const processFragment = (
n1: HostVNode | null, n1: HostVNode | null,
n2: HostVNode, n2: HostVNode,
container: HostElement, container: HostElement,
@ -770,7 +756,7 @@ function baseCreateRenderer<
parentSuspense: HostSuspenseBoundary | null, parentSuspense: HostSuspenseBoundary | null,
isSVG: boolean, isSVG: boolean,
optimized: boolean optimized: boolean
) { ) => {
const showID = __DEV__ && !__TEST__ const showID = __DEV__ && !__TEST__
const fragmentStartAnchor = (n2.el = n1 const fragmentStartAnchor = (n2.el = n1
? n1.el ? n1.el
@ -840,7 +826,7 @@ function baseCreateRenderer<
} }
} }
function processPortal( const processPortal = (
n1: HostVNode | null, n1: HostVNode | null,
n2: HostVNode, n2: HostVNode,
container: HostElement, container: HostElement,
@ -849,7 +835,7 @@ function baseCreateRenderer<
parentSuspense: HostSuspenseBoundary | null, parentSuspense: HostSuspenseBoundary | null,
isSVG: boolean, isSVG: boolean,
optimized: boolean optimized: boolean
) { ) => {
const targetSelector = n2.props && n2.props.target const targetSelector = n2.props && n2.props.target
const { patchFlag, shapeFlag, children } = n2 const { patchFlag, shapeFlag, children } = n2
if (n1 == null) { if (n1 == null) {
@ -934,7 +920,7 @@ function baseCreateRenderer<
processCommentNode(n1, n2, container, anchor) processCommentNode(n1, n2, container, anchor)
} }
function processComponent( const processComponent = (
n1: HostVNode | null, n1: HostVNode | null,
n2: HostVNode, n2: HostVNode,
container: HostElement, container: HostElement,
@ -943,7 +929,7 @@ function baseCreateRenderer<
parentSuspense: HostSuspenseBoundary | null, parentSuspense: HostSuspenseBoundary | null,
isSVG: boolean, isSVG: boolean,
optimized: boolean optimized: boolean
) { ) => {
if (n1 == null) { if (n1 == null) {
if (n2.shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE) { if (n2.shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE) {
;(parentComponent!.sink as KeepAliveSink).activate( ;(parentComponent!.sink as KeepAliveSink).activate(
@ -1008,14 +994,14 @@ function baseCreateRenderer<
} }
} }
function mountComponent( const mountComponent = (
initialVNode: HostVNode, initialVNode: HostVNode,
container: HostElement | null, // only null during hydration container: HostElement | null, // only null during hydration
anchor: HostNode | null, anchor: HostNode | null,
parentComponent: ComponentInternalInstance | null, parentComponent: ComponentInternalInstance | null,
parentSuspense: HostSuspenseBoundary | null, parentSuspense: HostSuspenseBoundary | null,
isSVG: boolean isSVG: boolean
) { ) => {
const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance( const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(
initialVNode, initialVNode,
parentComponent parentComponent
@ -1070,14 +1056,14 @@ function baseCreateRenderer<
} }
} }
function setupRenderEffect( const setupRenderEffect = (
instance: ComponentInternalInstance, instance: ComponentInternalInstance,
initialVNode: HostVNode, initialVNode: HostVNode,
container: HostElement | null, // only null during hydration container: HostElement | null, // only null during hydration
anchor: HostNode | null, anchor: HostNode | null,
parentSuspense: HostSuspenseBoundary | null, parentSuspense: HostSuspenseBoundary | null,
isSVG: boolean isSVG: boolean
) { ) => {
// create reactive effect for rendering // create reactive effect for rendering
instance.update = effect(function componentEffect() { instance.update = effect(function componentEffect() {
if (!instance.isMounted) { if (!instance.isMounted) {
@ -1168,10 +1154,10 @@ function baseCreateRenderer<
}, __DEV__ ? createDevEffectOptions(instance) : prodEffectOptions) }, __DEV__ ? createDevEffectOptions(instance) : prodEffectOptions)
} }
function updateComponentPreRender( const updateComponentPreRender = (
instance: ComponentInternalInstance, instance: ComponentInternalInstance,
nextVNode: HostVNode nextVNode: HostVNode
) { ) => {
nextVNode.component = instance nextVNode.component = instance
instance.vnode = nextVNode instance.vnode = nextVNode
instance.next = null instance.next = null
@ -1179,7 +1165,7 @@ function baseCreateRenderer<
resolveSlots(instance, nextVNode.children) resolveSlots(instance, nextVNode.children)
} }
function patchChildren( const patchChildren = (
n1: HostVNode | null, n1: HostVNode | null,
n2: HostVNode, n2: HostVNode,
container: HostElement, container: HostElement,
@ -1188,7 +1174,7 @@ function baseCreateRenderer<
parentSuspense: HostSuspenseBoundary | null, parentSuspense: HostSuspenseBoundary | null,
isSVG: boolean, isSVG: boolean,
optimized: boolean = false optimized: boolean = false
) { ) => {
const c1 = n1 && n1.children const c1 = n1 && n1.children
const prevShapeFlag = n1 ? n1.shapeFlag : 0 const prevShapeFlag = n1 ? n1.shapeFlag : 0
const c2 = n2.children const c2 = n2.children
@ -1284,7 +1270,7 @@ function baseCreateRenderer<
} }
} }
function patchUnkeyedChildren( const patchUnkeyedChildren = (
c1: HostVNode[], c1: HostVNode[],
c2: HostVNodeChildren, c2: HostVNodeChildren,
container: HostElement, container: HostElement,
@ -1293,7 +1279,7 @@ function baseCreateRenderer<
parentSuspense: HostSuspenseBoundary | null, parentSuspense: HostSuspenseBoundary | null,
isSVG: boolean, isSVG: boolean,
optimized: boolean optimized: boolean
) { ) => {
c1 = c1 || EMPTY_ARR c1 = c1 || EMPTY_ARR
c2 = c2 || EMPTY_ARR c2 = c2 || EMPTY_ARR
const oldLength = c1.length const oldLength = c1.length
@ -1334,7 +1320,7 @@ function baseCreateRenderer<
} }
// can be all-keyed or mixed // can be all-keyed or mixed
function patchKeyedChildren( const patchKeyedChildren = (
c1: HostVNode[], c1: HostVNode[],
c2: HostVNodeChildren, c2: HostVNodeChildren,
container: HostElement, container: HostElement,
@ -1343,7 +1329,7 @@ function baseCreateRenderer<
parentSuspense: HostSuspenseBoundary | null, parentSuspense: HostSuspenseBoundary | null,
isSVG: boolean, isSVG: boolean,
optimized: boolean optimized: boolean
) { ) => {
let i = 0 let i = 0
const l2 = c2.length const l2 = c2.length
let e1 = c1.length - 1 // prev ending index let e1 = c1.length - 1 // prev ending index
@ -1569,13 +1555,13 @@ function baseCreateRenderer<
} }
} }
function move( const move = (
vnode: HostVNode, vnode: HostVNode,
container: HostElement, container: HostElement,
anchor: HostNode | null, anchor: HostNode | null,
type: MoveType, type: MoveType,
parentSuspense: HostSuspenseBoundary | null = null parentSuspense: HostSuspenseBoundary | null = null
) { ) => {
if (vnode.shapeFlag & ShapeFlags.COMPONENT) { if (vnode.shapeFlag & ShapeFlags.COMPONENT) {
move(vnode.component!.subTree, container, anchor, type) move(vnode.component!.subTree, container, anchor, type)
return return
@ -1624,12 +1610,12 @@ function baseCreateRenderer<
} }
} }
function unmount( const unmount = (
vnode: HostVNode, vnode: HostVNode,
parentComponent: ComponentInternalInstance | null, parentComponent: ComponentInternalInstance | null,
parentSuspense: HostSuspenseBoundary | null, parentSuspense: HostSuspenseBoundary | null,
doRemove?: boolean doRemove?: boolean
) { ) => {
const { props, ref, children, dynamicChildren, shapeFlag } = vnode const { props, ref, children, dynamicChildren, shapeFlag } = vnode
// unset ref // unset ref
@ -1673,7 +1659,7 @@ function baseCreateRenderer<
} }
} }
function remove(vnode: HostVNode) { const remove = (vnode: HostVNode) => {
const { type, el, anchor, transition } = vnode const { type, el, anchor, transition } = vnode
if (type === Fragment) { if (type === Fragment) {
removeFragment(el!, anchor!) removeFragment(el!, anchor!)
@ -1708,7 +1694,7 @@ function baseCreateRenderer<
} }
} }
function removeFragment(cur: HostNode, end: HostNode) { const removeFragment = (cur: HostNode, end: HostNode) => {
// For fragments, directly remove all contained DOM nodes. // For fragments, directly remove all contained DOM nodes.
// (fragment child nodes cannot have transition) // (fragment child nodes cannot have transition)
let next let next
@ -1720,11 +1706,11 @@ function baseCreateRenderer<
hostRemove(end) hostRemove(end)
} }
function unmountComponent( const unmountComponent = (
instance: ComponentInternalInstance, instance: ComponentInternalInstance,
parentSuspense: HostSuspenseBoundary | null, parentSuspense: HostSuspenseBoundary | null,
doRemove?: boolean doRemove?: boolean
) { ) => {
if (__HMR__ && instance.type.__hmrId != null) { if (__HMR__ && instance.type.__hmrId != null) {
unregisterHMR(instance) unregisterHMR(instance)
} }
@ -1779,19 +1765,19 @@ function baseCreateRenderer<
} }
} }
function unmountChildren( const unmountChildren = (
children: HostVNode[], children: HostVNode[],
parentComponent: ComponentInternalInstance | null, parentComponent: ComponentInternalInstance | null,
parentSuspense: HostSuspenseBoundary | null, parentSuspense: HostSuspenseBoundary | null,
doRemove?: boolean, doRemove?: boolean,
start: number = 0 start: number = 0
) { ) => {
for (let i = start; i < children.length; i++) { for (let i = start; i < children.length; i++) {
unmount(children[i], parentComponent, parentSuspense, doRemove) unmount(children[i], parentComponent, parentSuspense, doRemove)
} }
} }
function getNextHostNode(vnode: HostVNode): HostNode | null { const getNextHostNode = (vnode: HostVNode): HostNode | null => {
if (vnode.shapeFlag & ShapeFlags.COMPONENT) { if (vnode.shapeFlag & ShapeFlags.COMPONENT) {
return getNextHostNode(vnode.component!.subTree) return getNextHostNode(vnode.component!.subTree)
} }
@ -1801,12 +1787,12 @@ function baseCreateRenderer<
return hostNextSibling((vnode.anchor || vnode.el)!) return hostNextSibling((vnode.anchor || vnode.el)!)
} }
function setRef( const setRef = (
ref: string | Function | Ref, ref: string | Function | Ref,
oldRef: string | Function | Ref | null, oldRef: string | Function | Ref | null,
parent: ComponentInternalInstance, parent: ComponentInternalInstance,
value: HostNode | ComponentPublicInstance | null value: HostNode | ComponentPublicInstance | null
) { ) => {
const refs = parent.refs === EMPTY_OBJ ? (parent.refs = {}) : parent.refs const refs = parent.refs === EMPTY_OBJ ? (parent.refs = {}) : parent.refs
const renderContext = toRaw(parent.renderContext) const renderContext = toRaw(parent.renderContext)
@ -1855,6 +1841,20 @@ function baseCreateRenderer<
container._vnode = vnode container._vnode = vnode
} }
const internals: RendererInternals<HostNode, HostElement> = {
patch,
unmount,
move,
next: getNextHostNode,
options
}
let hydrate: ReturnType<typeof createHydrationFunctions>[0] | undefined
let hydrateNode: ReturnType<typeof createHydrationFunctions>[1] | undefined
if (createHydrationFns) {
;[hydrate, hydrateNode] = createHydrationFns(mountComponent, hostPatchProp)
}
return { return {
render, render,
hydrate, hydrate,