From 629ee75588fc2ca4ab2b3786046f788d3547b6bc Mon Sep 17 00:00:00 2001 From: Evan You Date: Fri, 14 Feb 2020 03:22:52 -0500 Subject: [PATCH] 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. --- packages/runtime-core/src/hydration.ts | 17 ++-- packages/runtime-core/src/renderer.ts | 130 ++++++++++++------------- 2 files changed, 74 insertions(+), 73 deletions(-) diff --git a/packages/runtime-core/src/hydration.ts b/packages/runtime-core/src/hydration.ts index d451d752991..e8207cc0177 100644 --- a/packages/runtime-core/src/hydration.ts +++ b/packages/runtime-core/src/hydration.ts @@ -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?) diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts index c5b38b3b855..6a0fb55fd33 100644 --- a/packages/runtime-core/src/renderer.ts +++ b/packages/runtime-core/src/renderer.ts @@ -233,21 +233,7 @@ function baseCreateRenderer< insertStaticContent: hostInsertStaticContent } = options - const internals: RendererInternals = { - patch, - unmount, - move, - next: getNextHostNode, - options - } - - let hydrate: ReturnType[0] | undefined - let hydrateNode: ReturnType[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 = { + patch, + unmount, + move, + next: getNextHostNode, + options + } + + let hydrate: ReturnType[0] | undefined + let hydrateNode: ReturnType[1] | undefined + if (createHydrationFns) { + ;[hydrate, hydrateNode] = createHydrationFns(mountComponent, hostPatchProp) + } + return { render, hydrate,