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,
|
||||
config: {
|
||||
isNativeTag: NO,
|
||||
performance: false,
|
||||
errorHandler: undefined,
|
||||
warnHandler: undefined,
|
||||
globalProperties: {},
|
||||
|
@ -227,6 +228,7 @@ export interface AppConfig {
|
|||
// @private
|
||||
readonly isNativeTag: (tag: string) => boolean
|
||||
|
||||
performance: boolean
|
||||
errorHandler?: (
|
||||
err: unknown,
|
||||
instance: ComponentInternalInstance | null,
|
||||
|
|
|
@ -18,6 +18,7 @@ import {
|
|||
import { isArray, isFunction, isObject } from '@vue/shared'
|
||||
import { fallThroughAttrs } from './componentAttrs'
|
||||
import { VaporErrorCodes, callWithErrorHandling } from './errorHandling'
|
||||
import { endMeasure, startMeasure } from './profiling'
|
||||
|
||||
export const fragmentKey = Symbol(__DEV__ ? `fragmentKey` : ``)
|
||||
|
||||
|
@ -32,6 +33,9 @@ export function setupComponent(
|
|||
instance: ComponentInternalInstance,
|
||||
singleRoot: boolean = false,
|
||||
): void {
|
||||
if (__DEV__) {
|
||||
startMeasure(instance, `init`)
|
||||
}
|
||||
const reset = setCurrentInstance(instance)
|
||||
instance.scope.run(() => {
|
||||
const { component, props } = instance
|
||||
|
@ -93,6 +97,9 @@ export function setupComponent(
|
|||
return block
|
||||
})
|
||||
reset()
|
||||
if (__DEV__) {
|
||||
endMeasure(instance, `init`)
|
||||
}
|
||||
}
|
||||
|
||||
export function render(
|
||||
|
@ -115,6 +122,10 @@ function mountComponent(
|
|||
) {
|
||||
instance.container = container
|
||||
|
||||
if (__DEV__) {
|
||||
startMeasure(instance, 'mount')
|
||||
}
|
||||
|
||||
// hook: beforeMount
|
||||
invokeLifecycle(instance, VaporLifecycleHooks.BEFORE_MOUNT, 'beforeMount')
|
||||
|
||||
|
@ -128,6 +139,11 @@ function mountComponent(
|
|||
instance => (instance.isMounted = true),
|
||||
true,
|
||||
)
|
||||
|
||||
if (__DEV__) {
|
||||
endMeasure(instance, 'mount')
|
||||
}
|
||||
|
||||
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 { type Directive, warn } from '..'
|
||||
import { type Component, currentInstance } from '../component'
|
||||
import { getComponentName } from '../warning'
|
||||
import { getComponentName } from '../component'
|
||||
|
||||
export const COMPONENTS = 'components'
|
||||
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 {
|
||||
type Component,
|
||||
type ComponentInternalInstance,
|
||||
currentInstance,
|
||||
formatComponentName,
|
||||
} from './component'
|
||||
import { isFunction, isString } from '@vue/shared'
|
||||
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]
|
||||
}
|
||||
}
|
||||
|
||||
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