perf(vapor): improve component instantiation by using class
Mounting 10k components went from ~100ms to ~60ms with this change.
This commit is contained in:
parent
f0361bafbb
commit
842f94cc73
|
@ -1,7 +1,6 @@
|
||||||
import {
|
import {
|
||||||
type Component,
|
type Component,
|
||||||
type ComponentInternalInstance,
|
ComponentInternalInstance,
|
||||||
createComponentInstance,
|
|
||||||
currentInstance,
|
currentInstance,
|
||||||
} from './component'
|
} from './component'
|
||||||
import { setupComponent } from './apiRender'
|
import { setupComponent } from './apiRender'
|
||||||
|
@ -24,7 +23,7 @@ export function createComponent(
|
||||||
return fallbackComponent(comp, rawProps, slots, current, singleRoot)
|
return fallbackComponent(comp, rawProps, slots, current, singleRoot)
|
||||||
}
|
}
|
||||||
|
|
||||||
const instance = createComponentInstance(
|
const instance = new ComponentInternalInstance(
|
||||||
comp,
|
comp,
|
||||||
singleRoot ? withAttrs(rawProps) : rawProps,
|
singleRoot ? withAttrs(rawProps) : rawProps,
|
||||||
slots,
|
slots,
|
||||||
|
@ -42,7 +41,7 @@ export function createComponent(
|
||||||
setupComponent(instance)
|
setupComponent(instance)
|
||||||
|
|
||||||
// register sub-component with current component for lifecycle management
|
// register sub-component with current component for lifecycle management
|
||||||
current.comps.add(instance)
|
// current.comps.add(instance)
|
||||||
|
|
||||||
return instance
|
return instance
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,8 +13,7 @@ import {
|
||||||
} from './dom/element'
|
} from './dom/element'
|
||||||
import { type Block, type Fragment, fragmentKey } from './block'
|
import { type Block, type Fragment, fragmentKey } from './block'
|
||||||
import { warn } from './warning'
|
import { warn } from './warning'
|
||||||
import { currentInstance } from './component'
|
import { currentInstance, isVaporComponent } from './component'
|
||||||
import { componentKey } from './component'
|
|
||||||
import type { DynamicSlot } from './componentSlots'
|
import type { DynamicSlot } from './componentSlots'
|
||||||
import { renderEffect } from './renderEffect'
|
import { renderEffect } from './renderEffect'
|
||||||
|
|
||||||
|
@ -382,7 +381,7 @@ function normalizeAnchor(node: Block): Node {
|
||||||
return node
|
return node
|
||||||
} else if (isArray(node)) {
|
} else if (isArray(node)) {
|
||||||
return normalizeAnchor(node[0])
|
return normalizeAnchor(node[0])
|
||||||
} else if (componentKey in node) {
|
} else if (isVaporComponent(node)) {
|
||||||
return normalizeAnchor(node.block!)
|
return normalizeAnchor(node.block!)
|
||||||
} else {
|
} else {
|
||||||
return normalizeAnchor(node.nodes!)
|
return normalizeAnchor(node.nodes!)
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
import { NO, getGlobalThis, isFunction, isObject } from '@vue/shared'
|
import { NO, getGlobalThis, isFunction, isObject } from '@vue/shared'
|
||||||
import {
|
import {
|
||||||
type Component,
|
type Component,
|
||||||
type ComponentInternalInstance,
|
ComponentInternalInstance,
|
||||||
createComponentInstance,
|
|
||||||
validateComponentName,
|
validateComponentName,
|
||||||
} from './component'
|
} from './component'
|
||||||
import { warn } from './warning'
|
import { warn } from './warning'
|
||||||
|
@ -126,7 +125,7 @@ export function createVaporApp(
|
||||||
container.textContent = ''
|
container.textContent = ''
|
||||||
}
|
}
|
||||||
|
|
||||||
instance = createComponentInstance(
|
instance = new ComponentInternalInstance(
|
||||||
rootComponent,
|
rootComponent,
|
||||||
rootProps,
|
rootProps,
|
||||||
null,
|
null,
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import {
|
import {
|
||||||
type ComponentInternalInstance,
|
type ComponentInternalInstance,
|
||||||
componentKey,
|
|
||||||
createSetupContext,
|
createSetupContext,
|
||||||
getAttrsProxy,
|
getAttrsProxy,
|
||||||
getSlotsProxy,
|
getSlotsProxy,
|
||||||
|
isVaporComponent,
|
||||||
setCurrentInstance,
|
setCurrentInstance,
|
||||||
validateComponentName,
|
validateComponentName,
|
||||||
} from './component'
|
} from './component'
|
||||||
|
@ -60,9 +60,9 @@ export function setupComponent(instance: ComponentInternalInstance): void {
|
||||||
if (
|
if (
|
||||||
stateOrNode &&
|
stateOrNode &&
|
||||||
(stateOrNode instanceof Node ||
|
(stateOrNode instanceof Node ||
|
||||||
|
isVaporComponent(stateOrNode) ||
|
||||||
isArray(stateOrNode) ||
|
isArray(stateOrNode) ||
|
||||||
fragmentKey in stateOrNode ||
|
fragmentKey in stateOrNode)
|
||||||
componentKey in stateOrNode)
|
|
||||||
) {
|
) {
|
||||||
block = stateOrNode
|
block = stateOrNode
|
||||||
} else if (isObject(stateOrNode)) {
|
} else if (isObject(stateOrNode)) {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { isArray } from '@vue/shared'
|
import { isArray } from '@vue/shared'
|
||||||
import { type ComponentInternalInstance, componentKey } from './component'
|
import { type ComponentInternalInstance, isVaporComponent } from './component'
|
||||||
|
|
||||||
export const fragmentKey: unique symbol = Symbol(__DEV__ ? `fragmentKey` : ``)
|
export const fragmentKey: unique symbol = Symbol(__DEV__ ? `fragmentKey` : ``)
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ export function normalizeBlock(block: Block): Node[] {
|
||||||
nodes.push(block)
|
nodes.push(block)
|
||||||
} else if (isArray(block)) {
|
} else if (isArray(block)) {
|
||||||
block.forEach(child => nodes.push(...normalizeBlock(child)))
|
block.forEach(child => nodes.push(...normalizeBlock(child)))
|
||||||
} else if (componentKey in block) {
|
} else if (isVaporComponent(block)) {
|
||||||
nodes.push(...normalizeBlock(block.block!))
|
nodes.push(...normalizeBlock(block.block!))
|
||||||
} else if (block) {
|
} else if (block) {
|
||||||
nodes.push(...normalizeBlock(block.nodes))
|
nodes.push(...normalizeBlock(block.nodes))
|
||||||
|
@ -34,7 +34,7 @@ export function findFirstRootElement(
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getFirstNode(block: Block | null): Node | undefined {
|
export function getFirstNode(block: Block | null): Node | undefined {
|
||||||
if (!block || componentKey in block) return
|
if (!block || isVaporComponent(block)) return
|
||||||
if (block instanceof Node) return block
|
if (block instanceof Node) return block
|
||||||
if (isArray(block)) {
|
if (isArray(block)) {
|
||||||
if (block.length === 1) {
|
if (block.length === 1) {
|
||||||
|
|
|
@ -1,11 +1,5 @@
|
||||||
import { EffectScope, isRef } from '@vue/reactivity'
|
import { EffectScope, isRef } from '@vue/reactivity'
|
||||||
import {
|
import { EMPTY_OBJ, isArray, isBuiltInTag, isFunction } from '@vue/shared'
|
||||||
EMPTY_OBJ,
|
|
||||||
hasOwn,
|
|
||||||
isArray,
|
|
||||||
isBuiltInTag,
|
|
||||||
isFunction,
|
|
||||||
} from '@vue/shared'
|
|
||||||
import type { Block } from './block'
|
import type { Block } from './block'
|
||||||
import {
|
import {
|
||||||
type ComponentPropsOptions,
|
type ComponentPropsOptions,
|
||||||
|
@ -143,12 +137,31 @@ export interface ComponentInternalOptions {
|
||||||
|
|
||||||
type LifecycleHook<TFn = Function> = TFn[] | null
|
type LifecycleHook<TFn = Function> = TFn[] | null
|
||||||
|
|
||||||
export const componentKey: unique symbol = Symbol(__DEV__ ? `componentKey` : ``)
|
export let currentInstance: ComponentInternalInstance | null = null
|
||||||
|
|
||||||
|
export const getCurrentInstance: () => ComponentInternalInstance | null = () =>
|
||||||
|
currentInstance
|
||||||
|
|
||||||
|
export const setCurrentInstance = (instance: ComponentInternalInstance) => {
|
||||||
|
const prev = currentInstance
|
||||||
|
currentInstance = instance
|
||||||
|
return (): void => {
|
||||||
|
currentInstance = prev
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const unsetCurrentInstance = (): void => {
|
||||||
|
currentInstance && currentInstance.scope.off()
|
||||||
|
currentInstance = null
|
||||||
|
}
|
||||||
|
|
||||||
|
const emptyAppContext = createAppContext()
|
||||||
|
|
||||||
|
let uid = 0
|
||||||
|
export class ComponentInternalInstance {
|
||||||
|
vapor = true
|
||||||
|
|
||||||
export interface ComponentInternalInstance {
|
|
||||||
[componentKey]: true
|
|
||||||
uid: number
|
uid: number
|
||||||
vapor: true
|
|
||||||
appContext: AppContext
|
appContext: AppContext
|
||||||
|
|
||||||
type: Component
|
type: Component
|
||||||
|
@ -196,185 +209,121 @@ export interface ComponentInternalInstance {
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
[VaporLifecycleHooks.BEFORE_MOUNT]: LifecycleHook
|
// [VaporLifecycleHooks.BEFORE_MOUNT]: LifecycleHook;
|
||||||
|
bm: LifecycleHook
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
[VaporLifecycleHooks.MOUNTED]: LifecycleHook
|
// [VaporLifecycleHooks.MOUNTED]: LifecycleHook;
|
||||||
|
m: LifecycleHook
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
[VaporLifecycleHooks.BEFORE_UPDATE]: LifecycleHook
|
// [VaporLifecycleHooks.BEFORE_UPDATE]: LifecycleHook;
|
||||||
|
bu: LifecycleHook
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
[VaporLifecycleHooks.UPDATED]: LifecycleHook
|
// [VaporLifecycleHooks.UPDATED]: LifecycleHook;
|
||||||
|
u: LifecycleHook
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
[VaporLifecycleHooks.BEFORE_UNMOUNT]: LifecycleHook
|
// [VaporLifecycleHooks.BEFORE_UNMOUNT]: LifecycleHook;
|
||||||
|
bum: LifecycleHook
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
[VaporLifecycleHooks.UNMOUNTED]: LifecycleHook
|
// [VaporLifecycleHooks.UNMOUNTED]: LifecycleHook;
|
||||||
|
um: LifecycleHook
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
[VaporLifecycleHooks.RENDER_TRACKED]: LifecycleHook
|
// [VaporLifecycleHooks.RENDER_TRACKED]: LifecycleHook;
|
||||||
|
rtc: LifecycleHook
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
[VaporLifecycleHooks.RENDER_TRIGGERED]: LifecycleHook
|
// [VaporLifecycleHooks.RENDER_TRIGGERED]: LifecycleHook;
|
||||||
|
rtg: LifecycleHook
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
[VaporLifecycleHooks.ACTIVATED]: LifecycleHook
|
// [VaporLifecycleHooks.ACTIVATED]: LifecycleHook;
|
||||||
|
a: LifecycleHook
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
[VaporLifecycleHooks.DEACTIVATED]: LifecycleHook
|
// [VaporLifecycleHooks.DEACTIVATED]: LifecycleHook;
|
||||||
|
da: LifecycleHook
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
[VaporLifecycleHooks.ERROR_CAPTURED]: LifecycleHook
|
// [VaporLifecycleHooks.ERROR_CAPTURED]: LifecycleHook
|
||||||
/**
|
ec: LifecycleHook
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
// [VaporLifecycleHooks.SERVER_PREFETCH]: LifecycleHook<() => Promise<unknown>>
|
|
||||||
}
|
|
||||||
|
|
||||||
export let currentInstance: ComponentInternalInstance | null = null
|
constructor(
|
||||||
|
|
||||||
export const getCurrentInstance: () => ComponentInternalInstance | null = () =>
|
|
||||||
currentInstance
|
|
||||||
|
|
||||||
export const setCurrentInstance = (instance: ComponentInternalInstance) => {
|
|
||||||
const prev = currentInstance
|
|
||||||
currentInstance = instance
|
|
||||||
return (): void => {
|
|
||||||
currentInstance = prev
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const unsetCurrentInstance = (): void => {
|
|
||||||
currentInstance && currentInstance.scope.off()
|
|
||||||
currentInstance = null
|
|
||||||
}
|
|
||||||
|
|
||||||
const emptyAppContext = createAppContext()
|
|
||||||
|
|
||||||
let uid = 0
|
|
||||||
export function createComponentInstance(
|
|
||||||
component: Component,
|
component: Component,
|
||||||
rawProps: RawProps | null,
|
rawProps: RawProps | null,
|
||||||
slots: RawSlots | null,
|
slots: RawSlots | null,
|
||||||
once: boolean = false,
|
once: boolean = false,
|
||||||
// application root node only
|
// application root node only
|
||||||
appContext?: AppContext,
|
appContext?: AppContext,
|
||||||
): ComponentInternalInstance {
|
) {
|
||||||
const parent = getCurrentInstance()
|
this.uid = uid++
|
||||||
const _appContext =
|
const parent = (this.parent = currentInstance)
|
||||||
(parent ? parent.appContext : appContext) || emptyAppContext
|
this.root = parent ? parent.root : this
|
||||||
|
const _appContext = (this.appContext =
|
||||||
const instance: ComponentInternalInstance = {
|
(parent ? parent.appContext : appContext) || emptyAppContext)
|
||||||
[componentKey]: true,
|
this.block = null
|
||||||
uid: uid++,
|
this.container = null!
|
||||||
vapor: true,
|
this.root = null!
|
||||||
appContext: _appContext,
|
this.scope = new EffectScope(true)
|
||||||
|
this.provides = parent
|
||||||
block: null,
|
? parent.provides
|
||||||
container: null!,
|
: Object.create(_appContext.provides)
|
||||||
|
this.type = component
|
||||||
parent,
|
this.comps = new Set()
|
||||||
root: null!, // set later
|
this.scopeIds = []
|
||||||
|
this.rawProps = null!
|
||||||
scope: new EffectScope(true /* detached */)!,
|
this.propsOptions = normalizePropsOptions(component)
|
||||||
provides: parent ? parent.provides : Object.create(_appContext.provides),
|
this.emitsOptions = normalizeEmitsOptions(component)
|
||||||
type: component,
|
|
||||||
comps: new Set(),
|
|
||||||
scopeIds: [],
|
|
||||||
|
|
||||||
// resolved props and emits options
|
|
||||||
rawProps: null!, // set later
|
|
||||||
propsOptions: normalizePropsOptions(component),
|
|
||||||
emitsOptions: normalizeEmitsOptions(component),
|
|
||||||
|
|
||||||
// state
|
// state
|
||||||
setupState: EMPTY_OBJ,
|
this.setupState = EMPTY_OBJ
|
||||||
setupContext: null,
|
this.setupContext = null
|
||||||
props: EMPTY_OBJ,
|
this.props = EMPTY_OBJ
|
||||||
emit: null!,
|
this.emit = emit.bind(null, this)
|
||||||
emitted: null,
|
this.emitted = null
|
||||||
attrs: EMPTY_OBJ,
|
this.attrs = EMPTY_OBJ
|
||||||
slots: EMPTY_OBJ,
|
this.slots = EMPTY_OBJ
|
||||||
refs: EMPTY_OBJ,
|
this.refs = EMPTY_OBJ
|
||||||
|
|
||||||
// lifecycle
|
// lifecycle
|
||||||
isMounted: false,
|
this.isMounted = false
|
||||||
isUnmounted: false,
|
this.isUnmounted = false
|
||||||
isUpdating: false,
|
this.isUpdating = false
|
||||||
// TODO: registory of provides, appContext, lifecycles, ...
|
this[VaporLifecycleHooks.BEFORE_MOUNT] = null
|
||||||
/**
|
this[VaporLifecycleHooks.MOUNTED] = null
|
||||||
* @internal
|
this[VaporLifecycleHooks.BEFORE_UPDATE] = null
|
||||||
*/
|
this[VaporLifecycleHooks.UPDATED] = null
|
||||||
[VaporLifecycleHooks.BEFORE_MOUNT]: null,
|
this[VaporLifecycleHooks.BEFORE_UNMOUNT] = null
|
||||||
/**
|
this[VaporLifecycleHooks.UNMOUNTED] = null
|
||||||
* @internal
|
this[VaporLifecycleHooks.RENDER_TRACKED] = null
|
||||||
*/
|
this[VaporLifecycleHooks.RENDER_TRIGGERED] = null
|
||||||
[VaporLifecycleHooks.MOUNTED]: null,
|
this[VaporLifecycleHooks.ACTIVATED] = null
|
||||||
/**
|
this[VaporLifecycleHooks.DEACTIVATED] = null
|
||||||
* @internal
|
this[VaporLifecycleHooks.ERROR_CAPTURED] = null
|
||||||
*/
|
|
||||||
[VaporLifecycleHooks.BEFORE_UPDATE]: null,
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
[VaporLifecycleHooks.UPDATED]: null,
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
[VaporLifecycleHooks.BEFORE_UNMOUNT]: null,
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
[VaporLifecycleHooks.UNMOUNTED]: null,
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
[VaporLifecycleHooks.RENDER_TRACKED]: null,
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
[VaporLifecycleHooks.RENDER_TRIGGERED]: null,
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
[VaporLifecycleHooks.ACTIVATED]: null,
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
[VaporLifecycleHooks.DEACTIVATED]: null,
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
[VaporLifecycleHooks.ERROR_CAPTURED]: null,
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
// [VaporLifecycleHooks.SERVER_PREFETCH]: null,
|
|
||||||
}
|
|
||||||
instance.root = parent ? parent.root : instance
|
|
||||||
initProps(instance, rawProps, !isFunction(component), once)
|
|
||||||
initSlots(instance, slots)
|
|
||||||
instance.emit = emit.bind(null, instance)
|
|
||||||
|
|
||||||
return instance
|
initProps(this, rawProps, !isFunction(component), once)
|
||||||
|
initSlots(this, slots)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isVaporComponent(
|
export function isVaporComponent(
|
||||||
val: unknown,
|
val: unknown,
|
||||||
): val is ComponentInternalInstance {
|
): val is ComponentInternalInstance {
|
||||||
return !!val && hasOwn(val, componentKey)
|
return val instanceof ComponentInternalInstance
|
||||||
}
|
}
|
||||||
|
|
||||||
export function validateComponentName(
|
export function validateComponentName(
|
||||||
|
|
|
@ -25,6 +25,6 @@ export function invokeLifecycle(
|
||||||
}
|
}
|
||||||
|
|
||||||
function invokeSub() {
|
function invokeSub() {
|
||||||
instance.comps.forEach(comp => invokeLifecycle(comp, lifecycle, cb, post))
|
// instance.comps.forEach(comp => invokeLifecycle(comp, lifecycle, cb, post))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,7 +77,7 @@ export const warn = (__DEV__ ? _warn : NOOP) as typeof _warn
|
||||||
export { nextTick } from './scheduler'
|
export { nextTick } from './scheduler'
|
||||||
export {
|
export {
|
||||||
getCurrentInstance,
|
getCurrentInstance,
|
||||||
type ComponentInternalInstance,
|
type ComponentInternalInstance as ComponentInternalInstance,
|
||||||
type Component as Component,
|
type Component as Component,
|
||||||
type ObjectComponent,
|
type ObjectComponent,
|
||||||
type FunctionalComponent,
|
type FunctionalComponent,
|
||||||
|
|
Loading…
Reference in New Issue