Skip to content
This repository was archived by the owner on Jul 19, 2025. It is now read-only.

Commit 2f8838d

Browse files
committed
feat(runtime-capor): add app.config.performance
1 parent 325eb13 commit 2f8838d

File tree

5 files changed

+278
-0
lines changed

5 files changed

+278
-0
lines changed

packages/runtime-vapor/src/apiCreateVaporApp.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ export function createAppContext(): AppContext {
172172
app: null as any,
173173
config: {
174174
isNativeTag: NO,
175+
performance: false,
175176
errorHandler: undefined,
176177
warnHandler: undefined,
177178
globalProperties: {},
@@ -227,6 +228,7 @@ export interface AppConfig {
227228
// @private
228229
readonly isNativeTag: (tag: string) => boolean
229230

231+
performance: boolean
230232
errorHandler?: (
231233
err: unknown,
232234
instance: ComponentInternalInstance | null,

packages/runtime-vapor/src/apiRender.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
import { isArray, isFunction, isObject } from '@vue/shared'
1919
import { fallThroughAttrs } from './componentAttrs'
2020
import { VaporErrorCodes, callWithErrorHandling } from './errorHandling'
21+
import { endMeasure, startMeasure } from './profiling'
2122

2223
export const fragmentKey = Symbol(__DEV__ ? `fragmentKey` : ``)
2324

@@ -32,6 +33,9 @@ export function setupComponent(
3233
instance: ComponentInternalInstance,
3334
singleRoot: boolean = false,
3435
): void {
36+
if (__DEV__) {
37+
startMeasure(instance, `init`)
38+
}
3539
const reset = setCurrentInstance(instance)
3640
instance.scope.run(() => {
3741
const { component, props } = instance
@@ -93,6 +97,9 @@ export function setupComponent(
9397
return block
9498
})
9599
reset()
100+
if (__DEV__) {
101+
endMeasure(instance, `init`)
102+
}
96103
}
97104

98105
export function render(
@@ -118,6 +125,10 @@ function mountComponent(
118125
// hook: beforeMount
119126
invokeLifecycle(instance, VaporLifecycleHooks.BEFORE_MOUNT, 'beforeMount')
120127

128+
if (__DEV__) {
129+
startMeasure(instance, 'mount')
130+
}
131+
121132
insert(instance.block!, instance.container)
122133

123134
// hook: mounted
@@ -128,6 +139,11 @@ function mountComponent(
128139
instance => (instance.isMounted = true),
129140
true,
130141
)
142+
143+
if (__DEV__) {
144+
endMeasure(instance, 'mount')
145+
}
146+
131147
return instance
132148
}
133149

packages/runtime-vapor/src/component.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,50 @@ function getAttrsProxy(instance: ComponentInternalInstance): Data {
414414
)
415415
}
416416

417+
const classifyRE = /(?:^|[-_])(\w)/g
418+
const classify = (str: string): string =>
419+
str.replace(classifyRE, c => c.toUpperCase()).replace(/[-_]/g, '')
420+
421+
export function getComponentName(
422+
Component: Component,
423+
includeInferred = true,
424+
): string | false | undefined {
425+
return isFunction(Component)
426+
? Component.displayName || Component.name
427+
: Component.name || (includeInferred && Component.__name)
428+
}
429+
430+
/* istanbul ignore next */
431+
export function formatComponentName(
432+
instance: ComponentInternalInstance | null,
433+
Component: Component,
434+
isRoot = false,
435+
): string {
436+
let name = getComponentName(Component)
437+
if (!name && Component.__file) {
438+
const match = Component.__file.match(/([^/\\]+)\.\w+$/)
439+
if (match) {
440+
name = match[1]
441+
}
442+
}
443+
444+
if (!name && instance && instance.parent) {
445+
// try to infer the name based on reverse resolution
446+
const inferFromRegistry = (registry: Record<string, any> | undefined) => {
447+
for (const key in registry) {
448+
if (registry[key] === Component) {
449+
return key
450+
}
451+
}
452+
}
453+
name =
454+
inferFromRegistry(instance.comps) ||
455+
inferFromRegistry(instance.appContext.components)
456+
}
457+
458+
return name ? classify(name) : isRoot ? `App` : `Anonymous`
459+
}
460+
417461
/**
418462
* Dev-only
419463
*/
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
/* eslint-disable no-restricted-globals */
2+
import type { App } from './apiCreateVaporApp'
3+
import type { ComponentInternalInstance } from './component'
4+
5+
interface AppRecord {
6+
id: number
7+
app: App
8+
version: string
9+
types: Record<string, string | Symbol>
10+
}
11+
12+
enum DevtoolsHooks {
13+
APP_INIT = 'app:init',
14+
APP_UNMOUNT = 'app:unmount',
15+
COMPONENT_UPDATED = 'component:updated',
16+
COMPONENT_ADDED = 'component:added',
17+
COMPONENT_REMOVED = 'component:removed',
18+
COMPONENT_EMIT = 'component:emit',
19+
PERFORMANCE_START = 'perf:start',
20+
PERFORMANCE_END = 'perf:end',
21+
}
22+
23+
export interface DevtoolsHook {
24+
enabled?: boolean
25+
emit: (event: string, ...payload: any[]) => void
26+
on: (event: string, handler: Function) => void
27+
once: (event: string, handler: Function) => void
28+
off: (event: string, handler: Function) => void
29+
appRecords: AppRecord[]
30+
/**
31+
* Added at https://github.com/vuejs/devtools/commit/f2ad51eea789006ab66942e5a27c0f0986a257f9
32+
* Returns whether the arg was buffered or not
33+
*/
34+
cleanupBuffer?: (matchArg: unknown) => boolean
35+
}
36+
37+
export let devtools: DevtoolsHook
38+
39+
let buffer: { event: string; args: any[] }[] = []
40+
41+
let devtoolsNotInstalled = false
42+
43+
function emit(event: string, ...args: any[]) {
44+
if (devtools) {
45+
devtools.emit(event, ...args)
46+
} else if (!devtoolsNotInstalled) {
47+
buffer.push({ event, args })
48+
}
49+
}
50+
51+
export function setDevtoolsHook(hook: DevtoolsHook, target: any) {
52+
devtools = hook
53+
if (devtools) {
54+
devtools.enabled = true
55+
buffer.forEach(({ event, args }) => devtools.emit(event, ...args))
56+
buffer = []
57+
} else if (
58+
// handle late devtools injection - only do this if we are in an actual
59+
// browser environment to avoid the timer handle stalling test runner exit
60+
// (#4815)
61+
typeof window !== 'undefined' &&
62+
// some envs mock window but not fully
63+
window.HTMLElement // &&
64+
// also exclude jsdom
65+
// !window.navigator?.userAgent?.includes('jsdom')
66+
) {
67+
const replay = (target.__VUE_DEVTOOLS_HOOK_REPLAY__ =
68+
target.__VUE_DEVTOOLS_HOOK_REPLAY__ || [])
69+
replay.push((newHook: DevtoolsHook) => {
70+
setDevtoolsHook(newHook, target)
71+
})
72+
// clear buffer after 3s - the user probably doesn't have devtools installed
73+
// at all, and keeping the buffer will cause memory leaks (#4738)
74+
setTimeout(() => {
75+
if (!devtools) {
76+
target.__VUE_DEVTOOLS_HOOK_REPLAY__ = null
77+
devtoolsNotInstalled = true
78+
buffer = []
79+
}
80+
}, 3000)
81+
} else {
82+
// non-browser env, assume not installed
83+
devtoolsNotInstalled = true
84+
buffer = []
85+
}
86+
}
87+
88+
export function devtoolsInitApp(app: App, version: string) {
89+
emit(DevtoolsHooks.APP_INIT, app, version, {
90+
Text,
91+
Comment,
92+
})
93+
}
94+
95+
export function devtoolsUnmountApp(app: App) {
96+
emit(DevtoolsHooks.APP_UNMOUNT, app)
97+
}
98+
99+
export const devtoolsComponentAdded = /*#__PURE__*/ createDevtoolsComponentHook(
100+
DevtoolsHooks.COMPONENT_ADDED,
101+
)
102+
103+
export const devtoolsComponentUpdated =
104+
/*#__PURE__*/ createDevtoolsComponentHook(DevtoolsHooks.COMPONENT_UPDATED)
105+
106+
const _devtoolsComponentRemoved = /*#__PURE__*/ createDevtoolsComponentHook(
107+
DevtoolsHooks.COMPONENT_REMOVED,
108+
)
109+
110+
export const devtoolsComponentRemoved = (
111+
component: ComponentInternalInstance,
112+
) => {
113+
if (
114+
devtools &&
115+
typeof devtools.cleanupBuffer === 'function' &&
116+
// remove the component if it wasn't buffered
117+
!devtools.cleanupBuffer(component)
118+
) {
119+
_devtoolsComponentRemoved(component)
120+
}
121+
}
122+
123+
/*! #__NO_SIDE_EFFECTS__ */
124+
function createDevtoolsComponentHook(hook: DevtoolsHooks) {
125+
return (component: ComponentInternalInstance) => {
126+
emit(
127+
hook,
128+
component.appContext.app,
129+
component.uid,
130+
component.parent ? component.parent.uid : undefined,
131+
component,
132+
)
133+
}
134+
}
135+
136+
export const devtoolsPerfStart = /*#__PURE__*/ createDevtoolsPerformanceHook(
137+
DevtoolsHooks.PERFORMANCE_START,
138+
)
139+
140+
export const devtoolsPerfEnd = /*#__PURE__*/ createDevtoolsPerformanceHook(
141+
DevtoolsHooks.PERFORMANCE_END,
142+
)
143+
144+
function createDevtoolsPerformanceHook(hook: DevtoolsHooks) {
145+
return (component: ComponentInternalInstance, type: string, time: number) => {
146+
emit(hook, component.appContext.app, component.uid, component, type, time)
147+
}
148+
}
149+
150+
export function devtoolsComponentEmit(
151+
component: ComponentInternalInstance,
152+
event: string,
153+
params: any[],
154+
) {
155+
emit(
156+
DevtoolsHooks.COMPONENT_EMIT,
157+
component.appContext.app,
158+
component,
159+
event,
160+
params,
161+
)
162+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/* eslint-disable no-restricted-globals */
2+
import {
3+
type ComponentInternalInstance,
4+
formatComponentName,
5+
} from './component'
6+
import { devtoolsPerfEnd, devtoolsPerfStart } from './devtools'
7+
8+
let supported: boolean
9+
let perf: Performance
10+
11+
export function startMeasure(
12+
instance: ComponentInternalInstance,
13+
type: string,
14+
) {
15+
if (instance.appContext.config.performance && isSupported()) {
16+
perf.mark(`vue-${type}-${instance.uid}`)
17+
}
18+
19+
if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
20+
devtoolsPerfStart(instance, type, isSupported() ? perf.now() : Date.now())
21+
}
22+
}
23+
24+
export function endMeasure(instance: ComponentInternalInstance, type: string) {
25+
if (instance.appContext.config.performance && isSupported()) {
26+
const startTag = `vue-${type}-${instance.uid}`
27+
const endTag = startTag + `:end`
28+
perf.mark(endTag)
29+
perf.measure(
30+
`<${formatComponentName(instance, instance.component)}> ${type}`,
31+
startTag,
32+
endTag,
33+
)
34+
perf.clearMarks(startTag)
35+
perf.clearMarks(endTag)
36+
}
37+
38+
if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
39+
devtoolsPerfEnd(instance, type, isSupported() ? perf.now() : Date.now())
40+
}
41+
}
42+
43+
function isSupported() {
44+
if (supported !== undefined) {
45+
return supported
46+
}
47+
if (typeof window !== 'undefined' && window.performance) {
48+
supported = true
49+
perf = window.performance
50+
} else {
51+
supported = false
52+
}
53+
return supported
54+
}

0 commit comments

Comments
 (0)