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 { warn } from './warning'
import { PatchFlags, ShapeFlags, isReservedProp, isOn } from '@vue/shared'
import { RendererOptions } from './renderer'
// Note: hydration is DOM-specific
// 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.
export function createHydrationFunctions(
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()) {
warn(`Attempting to hydrate existing markup but container is empty.`)
return
@ -34,11 +35,11 @@ export function createHydrationFunctions(
// TODO handle mismatches
// TODO SVG
// TODO Suspense
function hydrateNode(
const hydrateNode = (
node: Node,
vnode: VNode,
parentComponent: ComponentInternalInstance | null = null
): Node | null | undefined {
): Node | null | undefined => {
const { type, shapeFlag } = vnode
vnode.el = node
switch (type) {
@ -73,11 +74,11 @@ export function createHydrationFunctions(
}
}
function hydrateElement(
const hydrateElement = (
el: Element,
vnode: VNode,
parentComponent: ComponentInternalInstance | null
) {
) => {
const { props, patchFlag } = vnode
// skip props & children if this is hoisted static nodes
if (patchFlag !== PatchFlags.HOISTED) {
@ -124,11 +125,11 @@ export function createHydrationFunctions(
return el.nextSibling
}
function hydrateChildren(
const hydrateChildren = (
node: Node | null | undefined,
vnodes: VNode[],
parentComponent: ComponentInternalInstance | null
): Node | null | undefined {
): Node | null | undefined => {
for (let i = 0; node != null && i < vnodes.length; i++) {
// TODO can skip normalizeVNode in optimized mode
// (need hint on rendered markup?)

View File

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