Skip to content

Commit 3ac951b

Browse files
xiaodong2008sxzz
andauthored
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 <[email protected]>
1 parent ad3d8fa commit 3ac951b

File tree

7 files changed

+275
-43
lines changed

7 files changed

+275
-43
lines changed

packages/runtime-vapor/src/apiCreateVaporApp.ts

+2
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

+16
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(
@@ -115,6 +122,10 @@ function mountComponent(
115122
) {
116123
instance.container = container
117124

125+
if (__DEV__) {
126+
startMeasure(instance, 'mount')
127+
}
128+
118129
// hook: beforeMount
119130
invokeLifecycle(instance, VaporLifecycleHooks.BEFORE_MOUNT, 'beforeMount')
120131

@@ -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

+41
Original file line numberDiff line numberDiff line change
@@ -427,3 +427,44 @@ function getSlotsProxy(instance: ComponentInternalInstance): Slots {
427427
}))
428428
)
429429
}
430+
431+
export function getComponentName(
432+
Component: Component,
433+
includeInferred = true,
434+
): string | false | undefined {
435+
return isFunction(Component)
436+
? Component.displayName || Component.name
437+
: Component.name || (includeInferred && Component.__name)
438+
}
439+
440+
export function formatComponentName(
441+
instance: ComponentInternalInstance | null,
442+
Component: Component,
443+
isRoot = false,
444+
): string {
445+
let name = getComponentName(Component)
446+
if (!name && Component.__file) {
447+
const match = Component.__file.match(/([^/\\]+)\.\w+$/)
448+
if (match) {
449+
name = match[1]
450+
}
451+
}
452+
453+
if (!name && instance && instance.parent) {
454+
// try to infer the name based on reverse resolution
455+
const inferFromRegistry = (registry: Record<string, any> | undefined) => {
456+
for (const key in registry) {
457+
if (registry[key] === Component) {
458+
return key
459+
}
460+
}
461+
}
462+
name = inferFromRegistry(instance.appContext.components)
463+
}
464+
465+
return name ? classify(name) : isRoot ? `App` : `Anonymous`
466+
}
467+
468+
const classifyRE = /(?:^|[-_])(\w)/g
469+
const classify = (str: string): string =>
470+
str.replace(classifyRE, c => c.toUpperCase()).replace(/[-_]/g, '')
+160
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
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+
// eslint-disable-next-line no-restricted-syntax
66+
!window.navigator?.userAgent?.includes('jsdom')
67+
) {
68+
const replay = (target.__VUE_DEVTOOLS_HOOK_REPLAY__ =
69+
target.__VUE_DEVTOOLS_HOOK_REPLAY__ || [])
70+
replay.push((newHook: DevtoolsHook) => {
71+
setDevtoolsHook(newHook, target)
72+
})
73+
// clear buffer after 3s - the user probably doesn't have devtools installed
74+
// at all, and keeping the buffer will cause memory leaks (#4738)
75+
setTimeout(() => {
76+
if (!devtools) {
77+
target.__VUE_DEVTOOLS_HOOK_REPLAY__ = null
78+
devtoolsNotInstalled = true
79+
buffer = []
80+
}
81+
}, 3000)
82+
} else {
83+
// non-browser env, assume not installed
84+
devtoolsNotInstalled = true
85+
buffer = []
86+
}
87+
}
88+
89+
export function devtoolsInitApp(app: App, version: string) {
90+
emit(DevtoolsHooks.APP_INIT, app, version, {})
91+
}
92+
93+
export function devtoolsUnmountApp(app: App) {
94+
emit(DevtoolsHooks.APP_UNMOUNT, app)
95+
}
96+
97+
export const devtoolsComponentAdded = /*#__PURE__*/ createDevtoolsComponentHook(
98+
DevtoolsHooks.COMPONENT_ADDED,
99+
)
100+
101+
export const devtoolsComponentUpdated =
102+
/*#__PURE__*/ createDevtoolsComponentHook(DevtoolsHooks.COMPONENT_UPDATED)
103+
104+
const _devtoolsComponentRemoved = /*#__PURE__*/ createDevtoolsComponentHook(
105+
DevtoolsHooks.COMPONENT_REMOVED,
106+
)
107+
108+
export const devtoolsComponentRemoved = (
109+
component: ComponentInternalInstance,
110+
) => {
111+
if (
112+
devtools &&
113+
typeof devtools.cleanupBuffer === 'function' &&
114+
// remove the component if it wasn't buffered
115+
!devtools.cleanupBuffer(component)
116+
) {
117+
_devtoolsComponentRemoved(component)
118+
}
119+
}
120+
121+
/*! #__NO_SIDE_EFFECTS__ */
122+
function createDevtoolsComponentHook(hook: DevtoolsHooks) {
123+
return (component: ComponentInternalInstance) => {
124+
emit(
125+
hook,
126+
component.appContext.app,
127+
component.uid,
128+
component.parent ? component.parent.uid : undefined,
129+
component,
130+
)
131+
}
132+
}
133+
134+
export const devtoolsPerfStart = /*#__PURE__*/ createDevtoolsPerformanceHook(
135+
DevtoolsHooks.PERFORMANCE_START,
136+
)
137+
138+
export const devtoolsPerfEnd = /*#__PURE__*/ createDevtoolsPerformanceHook(
139+
DevtoolsHooks.PERFORMANCE_END,
140+
)
141+
142+
function createDevtoolsPerformanceHook(hook: DevtoolsHooks) {
143+
return (component: ComponentInternalInstance, type: string, time: number) => {
144+
emit(hook, component.appContext.app, component.uid, component, type, time)
145+
}
146+
}
147+
148+
export function devtoolsComponentEmit(
149+
component: ComponentInternalInstance,
150+
event: string,
151+
params: any[],
152+
) {
153+
emit(
154+
DevtoolsHooks.COMPONENT_EMIT,
155+
component.appContext.app,
156+
component,
157+
event,
158+
params,
159+
)
160+
}

packages/runtime-vapor/src/helpers/resolveAssets.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { camelize, capitalize } from '@vue/shared'
22
import { type Directive, warn } from '..'
33
import { type Component, currentInstance } from '../component'
4-
import { getComponentName } from '../warning'
4+
import { getComponentName } from '../component'
55

66
export const COMPONENTS = 'components'
77
export const DIRECTIVES = 'directives'
+54
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+
}

packages/runtime-vapor/src/warning.ts

+1-42
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import {
2-
type Component,
32
type ComponentInternalInstance,
43
currentInstance,
4+
formatComponentName,
55
} from './component'
66
import { isFunction, isString } from '@vue/shared'
77
import { isRef, pauseTracking, resetTracking, toRaw } from '@vue/reactivity'
@@ -155,44 +155,3 @@ function formatProp(key: string, value: unknown, raw?: boolean): any {
155155
return raw ? value : [`${key}=`, value]
156156
}
157157
}
158-
159-
export function getComponentName(
160-
Component: Component,
161-
includeInferred = true,
162-
): string | false | undefined {
163-
return isFunction(Component)
164-
? Component.displayName || Component.name
165-
: Component.name || (includeInferred && Component.__name)
166-
}
167-
168-
export function formatComponentName(
169-
instance: ComponentInternalInstance | null,
170-
Component: Component,
171-
isRoot = false,
172-
): string {
173-
let name = getComponentName(Component)
174-
if (!name && Component.__file) {
175-
const match = Component.__file.match(/([^/\\]+)\.\w+$/)
176-
if (match) {
177-
name = match[1]
178-
}
179-
}
180-
181-
if (!name && instance && instance.parent) {
182-
// try to infer the name based on reverse resolution
183-
const inferFromRegistry = (registry: Record<string, any> | undefined) => {
184-
for (const key in registry) {
185-
if (registry[key] === Component) {
186-
return key
187-
}
188-
}
189-
}
190-
name = inferFromRegistry(instance.appContext.components)
191-
}
192-
193-
return name ? classify(name) : isRoot ? `App` : `Anonymous`
194-
}
195-
196-
const classifyRE = /(?:^|[-_])(\w)/g
197-
const classify = (str: string): string =>
198-
str.replace(classifyRE, c => c.toUpperCase()).replace(/[-_]/g, '')

0 commit comments

Comments
 (0)