mirror of https://github.com/vuejs/core.git
feat(runtime-vapor): implement app.config.performance (#230)
* feat(runtime-capor): add app.config.performance * refactor: move formatComponentName to component.ts * refactor: update import in warning.ts * fix * refactor * fix order --------- Co-authored-by: 三咲智子 Kevin Deng <sxzz@sxzz.moe>
This commit is contained in:
parent
ad3d8fa6b4
commit
3ac951b5b5
|
@ -172,6 +172,7 @@ export function createAppContext(): AppContext {
|
||||||
app: null as any,
|
app: null as any,
|
||||||
config: {
|
config: {
|
||||||
isNativeTag: NO,
|
isNativeTag: NO,
|
||||||
|
performance: false,
|
||||||
errorHandler: undefined,
|
errorHandler: undefined,
|
||||||
warnHandler: undefined,
|
warnHandler: undefined,
|
||||||
globalProperties: {},
|
globalProperties: {},
|
||||||
|
@ -227,6 +228,7 @@ export interface AppConfig {
|
||||||
// @private
|
// @private
|
||||||
readonly isNativeTag: (tag: string) => boolean
|
readonly isNativeTag: (tag: string) => boolean
|
||||||
|
|
||||||
|
performance: boolean
|
||||||
errorHandler?: (
|
errorHandler?: (
|
||||||
err: unknown,
|
err: unknown,
|
||||||
instance: ComponentInternalInstance | null,
|
instance: ComponentInternalInstance | null,
|
||||||
|
|
|
@ -18,6 +18,7 @@ import {
|
||||||
import { isArray, isFunction, isObject } from '@vue/shared'
|
import { isArray, isFunction, isObject } from '@vue/shared'
|
||||||
import { fallThroughAttrs } from './componentAttrs'
|
import { fallThroughAttrs } from './componentAttrs'
|
||||||
import { VaporErrorCodes, callWithErrorHandling } from './errorHandling'
|
import { VaporErrorCodes, callWithErrorHandling } from './errorHandling'
|
||||||
|
import { endMeasure, startMeasure } from './profiling'
|
||||||
|
|
||||||
export const fragmentKey = Symbol(__DEV__ ? `fragmentKey` : ``)
|
export const fragmentKey = Symbol(__DEV__ ? `fragmentKey` : ``)
|
||||||
|
|
||||||
|
@ -32,6 +33,9 @@ export function setupComponent(
|
||||||
instance: ComponentInternalInstance,
|
instance: ComponentInternalInstance,
|
||||||
singleRoot: boolean = false,
|
singleRoot: boolean = false,
|
||||||
): void {
|
): void {
|
||||||
|
if (__DEV__) {
|
||||||
|
startMeasure(instance, `init`)
|
||||||
|
}
|
||||||
const reset = setCurrentInstance(instance)
|
const reset = setCurrentInstance(instance)
|
||||||
instance.scope.run(() => {
|
instance.scope.run(() => {
|
||||||
const { component, props } = instance
|
const { component, props } = instance
|
||||||
|
@ -93,6 +97,9 @@ export function setupComponent(
|
||||||
return block
|
return block
|
||||||
})
|
})
|
||||||
reset()
|
reset()
|
||||||
|
if (__DEV__) {
|
||||||
|
endMeasure(instance, `init`)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function render(
|
export function render(
|
||||||
|
@ -115,6 +122,10 @@ function mountComponent(
|
||||||
) {
|
) {
|
||||||
instance.container = container
|
instance.container = container
|
||||||
|
|
||||||
|
if (__DEV__) {
|
||||||
|
startMeasure(instance, 'mount')
|
||||||
|
}
|
||||||
|
|
||||||
// hook: beforeMount
|
// hook: beforeMount
|
||||||
invokeLifecycle(instance, VaporLifecycleHooks.BEFORE_MOUNT, 'beforeMount')
|
invokeLifecycle(instance, VaporLifecycleHooks.BEFORE_MOUNT, 'beforeMount')
|
||||||
|
|
||||||
|
@ -128,6 +139,11 @@ function mountComponent(
|
||||||
instance => (instance.isMounted = true),
|
instance => (instance.isMounted = true),
|
||||||
true,
|
true,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (__DEV__) {
|
||||||
|
endMeasure(instance, 'mount')
|
||||||
|
}
|
||||||
|
|
||||||
return instance
|
return instance
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -427,3 +427,44 @@ function getSlotsProxy(instance: ComponentInternalInstance): Slots {
|
||||||
}))
|
}))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getComponentName(
|
||||||
|
Component: Component,
|
||||||
|
includeInferred = true,
|
||||||
|
): string | false | undefined {
|
||||||
|
return isFunction(Component)
|
||||||
|
? Component.displayName || Component.name
|
||||||
|
: Component.name || (includeInferred && Component.__name)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatComponentName(
|
||||||
|
instance: ComponentInternalInstance | null,
|
||||||
|
Component: Component,
|
||||||
|
isRoot = false,
|
||||||
|
): string {
|
||||||
|
let name = getComponentName(Component)
|
||||||
|
if (!name && Component.__file) {
|
||||||
|
const match = Component.__file.match(/([^/\\]+)\.\w+$/)
|
||||||
|
if (match) {
|
||||||
|
name = match[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!name && instance && instance.parent) {
|
||||||
|
// try to infer the name based on reverse resolution
|
||||||
|
const inferFromRegistry = (registry: Record<string, any> | undefined) => {
|
||||||
|
for (const key in registry) {
|
||||||
|
if (registry[key] === Component) {
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
name = inferFromRegistry(instance.appContext.components)
|
||||||
|
}
|
||||||
|
|
||||||
|
return name ? classify(name) : isRoot ? `App` : `Anonymous`
|
||||||
|
}
|
||||||
|
|
||||||
|
const classifyRE = /(?:^|[-_])(\w)/g
|
||||||
|
const classify = (str: string): string =>
|
||||||
|
str.replace(classifyRE, c => c.toUpperCase()).replace(/[-_]/g, '')
|
||||||
|
|
|
@ -0,0 +1,160 @@
|
||||||
|
/* eslint-disable no-restricted-globals */
|
||||||
|
import type { App } from './apiCreateVaporApp'
|
||||||
|
import type { ComponentInternalInstance } from './component'
|
||||||
|
|
||||||
|
interface AppRecord {
|
||||||
|
id: number
|
||||||
|
app: App
|
||||||
|
version: string
|
||||||
|
types: Record<string, string | Symbol>
|
||||||
|
}
|
||||||
|
|
||||||
|
enum DevtoolsHooks {
|
||||||
|
APP_INIT = 'app:init',
|
||||||
|
APP_UNMOUNT = 'app:unmount',
|
||||||
|
COMPONENT_UPDATED = 'component:updated',
|
||||||
|
COMPONENT_ADDED = 'component:added',
|
||||||
|
COMPONENT_REMOVED = 'component:removed',
|
||||||
|
COMPONENT_EMIT = 'component:emit',
|
||||||
|
PERFORMANCE_START = 'perf:start',
|
||||||
|
PERFORMANCE_END = 'perf:end',
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DevtoolsHook {
|
||||||
|
enabled?: boolean
|
||||||
|
emit: (event: string, ...payload: any[]) => void
|
||||||
|
on: (event: string, handler: Function) => void
|
||||||
|
once: (event: string, handler: Function) => void
|
||||||
|
off: (event: string, handler: Function) => void
|
||||||
|
appRecords: AppRecord[]
|
||||||
|
/**
|
||||||
|
* Added at https://github.com/vuejs/devtools/commit/f2ad51eea789006ab66942e5a27c0f0986a257f9
|
||||||
|
* Returns whether the arg was buffered or not
|
||||||
|
*/
|
||||||
|
cleanupBuffer?: (matchArg: unknown) => boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export let devtools: DevtoolsHook
|
||||||
|
|
||||||
|
let buffer: { event: string; args: any[] }[] = []
|
||||||
|
|
||||||
|
let devtoolsNotInstalled = false
|
||||||
|
|
||||||
|
function emit(event: string, ...args: any[]) {
|
||||||
|
if (devtools) {
|
||||||
|
devtools.emit(event, ...args)
|
||||||
|
} else if (!devtoolsNotInstalled) {
|
||||||
|
buffer.push({ event, args })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setDevtoolsHook(hook: DevtoolsHook, target: any) {
|
||||||
|
devtools = hook
|
||||||
|
if (devtools) {
|
||||||
|
devtools.enabled = true
|
||||||
|
buffer.forEach(({ event, args }) => devtools.emit(event, ...args))
|
||||||
|
buffer = []
|
||||||
|
} else if (
|
||||||
|
// handle late devtools injection - only do this if we are in an actual
|
||||||
|
// browser environment to avoid the timer handle stalling test runner exit
|
||||||
|
// (#4815)
|
||||||
|
typeof window !== 'undefined' &&
|
||||||
|
// some envs mock window but not fully
|
||||||
|
window.HTMLElement &&
|
||||||
|
// also exclude jsdom
|
||||||
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
|
!window.navigator?.userAgent?.includes('jsdom')
|
||||||
|
) {
|
||||||
|
const replay = (target.__VUE_DEVTOOLS_HOOK_REPLAY__ =
|
||||||
|
target.__VUE_DEVTOOLS_HOOK_REPLAY__ || [])
|
||||||
|
replay.push((newHook: DevtoolsHook) => {
|
||||||
|
setDevtoolsHook(newHook, target)
|
||||||
|
})
|
||||||
|
// clear buffer after 3s - the user probably doesn't have devtools installed
|
||||||
|
// at all, and keeping the buffer will cause memory leaks (#4738)
|
||||||
|
setTimeout(() => {
|
||||||
|
if (!devtools) {
|
||||||
|
target.__VUE_DEVTOOLS_HOOK_REPLAY__ = null
|
||||||
|
devtoolsNotInstalled = true
|
||||||
|
buffer = []
|
||||||
|
}
|
||||||
|
}, 3000)
|
||||||
|
} else {
|
||||||
|
// non-browser env, assume not installed
|
||||||
|
devtoolsNotInstalled = true
|
||||||
|
buffer = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function devtoolsInitApp(app: App, version: string) {
|
||||||
|
emit(DevtoolsHooks.APP_INIT, app, version, {})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function devtoolsUnmountApp(app: App) {
|
||||||
|
emit(DevtoolsHooks.APP_UNMOUNT, app)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const devtoolsComponentAdded = /*#__PURE__*/ createDevtoolsComponentHook(
|
||||||
|
DevtoolsHooks.COMPONENT_ADDED,
|
||||||
|
)
|
||||||
|
|
||||||
|
export const devtoolsComponentUpdated =
|
||||||
|
/*#__PURE__*/ createDevtoolsComponentHook(DevtoolsHooks.COMPONENT_UPDATED)
|
||||||
|
|
||||||
|
const _devtoolsComponentRemoved = /*#__PURE__*/ createDevtoolsComponentHook(
|
||||||
|
DevtoolsHooks.COMPONENT_REMOVED,
|
||||||
|
)
|
||||||
|
|
||||||
|
export const devtoolsComponentRemoved = (
|
||||||
|
component: ComponentInternalInstance,
|
||||||
|
) => {
|
||||||
|
if (
|
||||||
|
devtools &&
|
||||||
|
typeof devtools.cleanupBuffer === 'function' &&
|
||||||
|
// remove the component if it wasn't buffered
|
||||||
|
!devtools.cleanupBuffer(component)
|
||||||
|
) {
|
||||||
|
_devtoolsComponentRemoved(component)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! #__NO_SIDE_EFFECTS__ */
|
||||||
|
function createDevtoolsComponentHook(hook: DevtoolsHooks) {
|
||||||
|
return (component: ComponentInternalInstance) => {
|
||||||
|
emit(
|
||||||
|
hook,
|
||||||
|
component.appContext.app,
|
||||||
|
component.uid,
|
||||||
|
component.parent ? component.parent.uid : undefined,
|
||||||
|
component,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const devtoolsPerfStart = /*#__PURE__*/ createDevtoolsPerformanceHook(
|
||||||
|
DevtoolsHooks.PERFORMANCE_START,
|
||||||
|
)
|
||||||
|
|
||||||
|
export const devtoolsPerfEnd = /*#__PURE__*/ createDevtoolsPerformanceHook(
|
||||||
|
DevtoolsHooks.PERFORMANCE_END,
|
||||||
|
)
|
||||||
|
|
||||||
|
function createDevtoolsPerformanceHook(hook: DevtoolsHooks) {
|
||||||
|
return (component: ComponentInternalInstance, type: string, time: number) => {
|
||||||
|
emit(hook, component.appContext.app, component.uid, component, type, time)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function devtoolsComponentEmit(
|
||||||
|
component: ComponentInternalInstance,
|
||||||
|
event: string,
|
||||||
|
params: any[],
|
||||||
|
) {
|
||||||
|
emit(
|
||||||
|
DevtoolsHooks.COMPONENT_EMIT,
|
||||||
|
component.appContext.app,
|
||||||
|
component,
|
||||||
|
event,
|
||||||
|
params,
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
import { camelize, capitalize } from '@vue/shared'
|
import { camelize, capitalize } from '@vue/shared'
|
||||||
import { type Directive, warn } from '..'
|
import { type Directive, warn } from '..'
|
||||||
import { type Component, currentInstance } from '../component'
|
import { type Component, currentInstance } from '../component'
|
||||||
import { getComponentName } from '../warning'
|
import { getComponentName } from '../component'
|
||||||
|
|
||||||
export const COMPONENTS = 'components'
|
export const COMPONENTS = 'components'
|
||||||
export const DIRECTIVES = 'directives'
|
export const DIRECTIVES = 'directives'
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
/* eslint-disable no-restricted-globals */
|
||||||
|
import {
|
||||||
|
type ComponentInternalInstance,
|
||||||
|
formatComponentName,
|
||||||
|
} from './component'
|
||||||
|
import { devtoolsPerfEnd, devtoolsPerfStart } from './devtools'
|
||||||
|
|
||||||
|
let supported: boolean
|
||||||
|
let perf: Performance
|
||||||
|
|
||||||
|
export function startMeasure(
|
||||||
|
instance: ComponentInternalInstance,
|
||||||
|
type: string,
|
||||||
|
) {
|
||||||
|
if (instance.appContext.config.performance && isSupported()) {
|
||||||
|
perf.mark(`vue-${type}-${instance.uid}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
|
||||||
|
devtoolsPerfStart(instance, type, isSupported() ? perf.now() : Date.now())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function endMeasure(instance: ComponentInternalInstance, type: string) {
|
||||||
|
if (instance.appContext.config.performance && isSupported()) {
|
||||||
|
const startTag = `vue-${type}-${instance.uid}`
|
||||||
|
const endTag = startTag + `:end`
|
||||||
|
perf.mark(endTag)
|
||||||
|
perf.measure(
|
||||||
|
`<${formatComponentName(instance, instance.component)}> ${type}`,
|
||||||
|
startTag,
|
||||||
|
endTag,
|
||||||
|
)
|
||||||
|
perf.clearMarks(startTag)
|
||||||
|
perf.clearMarks(endTag)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
|
||||||
|
devtoolsPerfEnd(instance, type, isSupported() ? perf.now() : Date.now())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isSupported() {
|
||||||
|
if (supported !== undefined) {
|
||||||
|
return supported
|
||||||
|
}
|
||||||
|
if (typeof window !== 'undefined' && window.performance) {
|
||||||
|
supported = true
|
||||||
|
perf = window.performance
|
||||||
|
} else {
|
||||||
|
supported = false
|
||||||
|
}
|
||||||
|
return supported
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
import {
|
import {
|
||||||
type Component,
|
|
||||||
type ComponentInternalInstance,
|
type ComponentInternalInstance,
|
||||||
currentInstance,
|
currentInstance,
|
||||||
|
formatComponentName,
|
||||||
} from './component'
|
} from './component'
|
||||||
import { isFunction, isString } from '@vue/shared'
|
import { isFunction, isString } from '@vue/shared'
|
||||||
import { isRef, pauseTracking, resetTracking, toRaw } from '@vue/reactivity'
|
import { isRef, pauseTracking, resetTracking, toRaw } from '@vue/reactivity'
|
||||||
|
@ -155,44 +155,3 @@ function formatProp(key: string, value: unknown, raw?: boolean): any {
|
||||||
return raw ? value : [`${key}=`, value]
|
return raw ? value : [`${key}=`, value]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getComponentName(
|
|
||||||
Component: Component,
|
|
||||||
includeInferred = true,
|
|
||||||
): string | false | undefined {
|
|
||||||
return isFunction(Component)
|
|
||||||
? Component.displayName || Component.name
|
|
||||||
: Component.name || (includeInferred && Component.__name)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function formatComponentName(
|
|
||||||
instance: ComponentInternalInstance | null,
|
|
||||||
Component: Component,
|
|
||||||
isRoot = false,
|
|
||||||
): string {
|
|
||||||
let name = getComponentName(Component)
|
|
||||||
if (!name && Component.__file) {
|
|
||||||
const match = Component.__file.match(/([^/\\]+)\.\w+$/)
|
|
||||||
if (match) {
|
|
||||||
name = match[1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!name && instance && instance.parent) {
|
|
||||||
// try to infer the name based on reverse resolution
|
|
||||||
const inferFromRegistry = (registry: Record<string, any> | undefined) => {
|
|
||||||
for (const key in registry) {
|
|
||||||
if (registry[key] === Component) {
|
|
||||||
return key
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
name = inferFromRegistry(instance.appContext.components)
|
|
||||||
}
|
|
||||||
|
|
||||||
return name ? classify(name) : isRoot ? `App` : `Anonymous`
|
|
||||||
}
|
|
||||||
|
|
||||||
const classifyRE = /(?:^|[-_])(\w)/g
|
|
||||||
const classify = (str: string): string =>
|
|
||||||
str.replace(classifyRE, c => c.toUpperCase()).replace(/[-_]/g, '')
|
|
||||||
|
|
Loading…
Reference in New Issue