Skip to content

Commit e93e426

Browse files
committed
feat(runtime-core): config.performance tracing support
1 parent a022b63 commit e93e426

File tree

5 files changed

+126
-24
lines changed

5 files changed

+126
-24
lines changed

jest.config.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ module.exports = {
1818
'packages/*/src/**/*.ts',
1919
'!packages/runtime-test/src/utils/**',
2020
'!packages/template-explorer/**',
21-
'!packages/size-check/**'
21+
'!packages/size-check/**',
22+
'!packages/runtime-core/src/profiling.ts'
2223
],
2324
watchPathIgnorePatterns: ['/node_modules/', '/dist/', '/.git/'],
2425
moduleFileExtensions: ['ts', 'tsx', 'js', 'json'],

packages/runtime-core/src/component.ts

+33-3
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import {
4141
currentRenderingInstance,
4242
markAttrsAccessed
4343
} from './componentRenderUtils'
44+
import { startMeasure, endMeasure } from './profiling'
4445

4546
export type Data = { [key: string]: unknown }
4647

@@ -108,6 +109,7 @@ export type RenderFunction = {
108109
}
109110

110111
export interface ComponentInternalInstance {
112+
uid: number
111113
type: Component
112114
parent: ComponentInternalInstance | null
113115
appContext: AppContext
@@ -176,6 +178,8 @@ export interface ComponentInternalInstance {
176178

177179
const emptyAppContext = createAppContext()
178180

181+
let uid = 0
182+
179183
export function createComponentInstance(
180184
vnode: VNode,
181185
parent: ComponentInternalInstance | null,
@@ -185,6 +189,7 @@ export function createComponentInstance(
185189
const appContext =
186190
(parent ? parent.appContext : vnode.appContext) || emptyAppContext
187191
const instance: ComponentInternalInstance = {
192+
uid: uid++,
188193
vnode,
189194
parent,
190195
appContext,
@@ -383,7 +388,7 @@ function setupStatefulComponent(
383388
handleSetupResult(instance, setupResult, parentSuspense, isSSR)
384389
}
385390
} else {
386-
finishComponentSetup(instance, parentSuspense, isSSR)
391+
finishComponentSetup(instance, isSSR)
387392
}
388393
}
389394

@@ -413,7 +418,7 @@ export function handleSetupResult(
413418
}`
414419
)
415420
}
416-
finishComponentSetup(instance, parentSuspense, isSSR)
421+
finishComponentSetup(instance, isSSR)
417422
}
418423

419424
type CompileFunction = (
@@ -430,7 +435,6 @@ export function registerRuntimeCompiler(_compile: any) {
430435

431436
function finishComponentSetup(
432437
instance: ComponentInternalInstance,
433-
parentSuspense: SuspenseBoundary | null,
434438
isSSR: boolean
435439
) {
436440
const Component = instance.type as ComponentOptions
@@ -442,9 +446,15 @@ function finishComponentSetup(
442446
}
443447
} else if (!instance.render) {
444448
if (compile && Component.template && !Component.render) {
449+
if (__DEV__) {
450+
startMeasure(instance, `compile`)
451+
}
445452
Component.render = compile(Component.template, {
446453
isCustomElement: instance.appContext.config.isCustomElement || NO
447454
})
455+
if (__DEV__ && instance.appContext.config.performance) {
456+
endMeasure(instance, `compile`)
457+
}
448458
// mark the function as runtime compiled
449459
;(Component.render as RenderFunction)._rc = true
450460
}
@@ -529,3 +539,23 @@ export function recordInstanceBoundEffect(effect: ReactiveEffect) {
529539
;(currentInstance.effects || (currentInstance.effects = [])).push(effect)
530540
}
531541
}
542+
543+
const classifyRE = /(?:^|[-_])(\w)/g
544+
const classify = (str: string): string =>
545+
str.replace(classifyRE, c => c.toUpperCase()).replace(/[-_]/g, '')
546+
547+
export function formatComponentName(
548+
Component: Component,
549+
file?: string
550+
): string {
551+
let name = isFunction(Component)
552+
? Component.displayName || Component.name
553+
: Component.name
554+
if (!name && file) {
555+
const match = file.match(/([^/\\]+)\.vue$/)
556+
if (match) {
557+
name = match[1]
558+
}
559+
}
560+
return name ? classify(name) : 'Anonymous'
561+
}
+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { ComponentInternalInstance, formatComponentName } from './component'
2+
3+
let supported: boolean
4+
let perf: any
5+
6+
export function startMeasure(
7+
instance: ComponentInternalInstance,
8+
type: string
9+
) {
10+
if (!instance.appContext) debugger
11+
if (instance.appContext.config.performance && isSupported()) {
12+
perf.mark(`vue-${type}-${instance.uid}`)
13+
}
14+
}
15+
16+
export function endMeasure(instance: ComponentInternalInstance, type: string) {
17+
if (instance.appContext.config.performance && isSupported()) {
18+
const startTag = `vue-${type}-${instance.uid}`
19+
const endTag = startTag + `:end`
20+
perf.mark(endTag)
21+
perf.measure(
22+
`<${formatComponentName(instance.type)}> ${type}`,
23+
startTag,
24+
endTag
25+
)
26+
perf.clearMarks(startTag)
27+
perf.clearMarks(endTag)
28+
}
29+
}
30+
31+
function isSupported() {
32+
if (supported !== undefined) {
33+
return supported
34+
}
35+
if (typeof window !== 'undefined' && window.performance) {
36+
supported = true
37+
perf = window.performance
38+
} else {
39+
supported = false
40+
}
41+
return supported
42+
}

packages/runtime-core/src/renderer.ts

+39
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ import {
6868
} from './errorHandling'
6969
import { createHydrationFunctions, RootHydrateFunction } from './hydration'
7070
import { invokeDirectiveHook } from './directives'
71+
import { startMeasure, endMeasure } from './profiling'
7172

7273
const __HMR__ = __BUNDLER__ && __DEV__
7374

@@ -1031,6 +1032,7 @@ function baseCreateRenderer(
10311032

10321033
if (__DEV__) {
10331034
pushWarningContext(initialVNode)
1035+
startMeasure(instance, `mount`)
10341036
}
10351037

10361038
// inject renderer internals for keepAlive
@@ -1041,7 +1043,13 @@ function baseCreateRenderer(
10411043
}
10421044

10431045
// resolve props and slots for setup context
1046+
if (__DEV__) {
1047+
startMeasure(instance, `init`)
1048+
}
10441049
setupComponent(instance, parentSuspense)
1050+
if (__DEV__) {
1051+
endMeasure(instance, `init`)
1052+
}
10451053

10461054
// setup() is async. This component relies on async logic to be resolved
10471055
// before proceeding
@@ -1072,6 +1080,7 @@ function baseCreateRenderer(
10721080

10731081
if (__DEV__) {
10741082
popWarningContext()
1083+
endMeasure(instance, `mount`)
10751084
}
10761085
}
10771086

@@ -1089,7 +1098,13 @@ function baseCreateRenderer(
10891098
let vnodeHook: VNodeHook | null | undefined
10901099
const { el, props } = initialVNode
10911100
const { bm, m, a, parent } = instance
1101+
if (__DEV__) {
1102+
startMeasure(instance, `render`)
1103+
}
10921104
const subTree = (instance.subTree = renderComponentRoot(instance))
1105+
if (__DEV__) {
1106+
endMeasure(instance, `render`)
1107+
}
10931108
// beforeMount hook
10941109
if (bm) {
10951110
invokeHooks(bm)
@@ -1099,14 +1114,23 @@ function baseCreateRenderer(
10991114
invokeVNodeHook(vnodeHook, parent, initialVNode)
11001115
}
11011116
if (el && hydrateNode) {
1117+
if (__DEV__) {
1118+
startMeasure(instance, `hydrate`)
1119+
}
11021120
// vnode has adopted host node - perform hydration instead of mount.
11031121
hydrateNode(
11041122
initialVNode.el as Node,
11051123
subTree,
11061124
instance,
11071125
parentSuspense
11081126
)
1127+
if (__DEV__) {
1128+
endMeasure(instance, `hydrate`)
1129+
}
11091130
} else {
1131+
if (__DEV__) {
1132+
startMeasure(instance, `patch`)
1133+
}
11101134
patch(
11111135
null,
11121136
subTree,
@@ -1116,6 +1140,9 @@ function baseCreateRenderer(
11161140
parentSuspense,
11171141
isSVG
11181142
)
1143+
if (__DEV__) {
1144+
endMeasure(instance, `patch`)
1145+
}
11191146
initialVNode.el = subTree.el
11201147
}
11211148
// mounted hook
@@ -1151,7 +1178,13 @@ function baseCreateRenderer(
11511178
} else {
11521179
next = vnode
11531180
}
1181+
if (__DEV__) {
1182+
startMeasure(instance, `render`)
1183+
}
11541184
const nextTree = renderComponentRoot(instance)
1185+
if (__DEV__) {
1186+
endMeasure(instance, `render`)
1187+
}
11551188
const prevTree = instance.subTree
11561189
instance.subTree = nextTree
11571190
next.el = vnode.el
@@ -1168,6 +1201,9 @@ function baseCreateRenderer(
11681201
if (instance.refs !== EMPTY_OBJ) {
11691202
instance.refs = {}
11701203
}
1204+
if (__DEV__) {
1205+
startMeasure(instance, `patch`)
1206+
}
11711207
patch(
11721208
prevTree,
11731209
nextTree,
@@ -1179,6 +1215,9 @@ function baseCreateRenderer(
11791215
parentSuspense,
11801216
isSVG
11811217
)
1218+
if (__DEV__) {
1219+
endMeasure(instance, `patch`)
1220+
}
11821221
next.el = nextTree.el
11831222
if (next === null) {
11841223
// self-triggered update. In case of HOC, update parent component

packages/runtime-core/src/warning.ts

+10-20
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import { VNode } from './vnode'
2-
import { Data, ComponentInternalInstance, Component } from './component'
2+
import {
3+
Data,
4+
ComponentInternalInstance,
5+
Component,
6+
formatComponentName
7+
} from './component'
38
import { isString, isFunction } from '@vue/shared'
49
import { toRaw, isRef, pauseTracking, resetTracking } from '@vue/reactivity'
510
import { callWithErrorHandling, ErrorCodes } from './errorHandling'
@@ -43,7 +48,10 @@ export function warn(msg: string, ...args: any[]) {
4348
msg + args.join(''),
4449
instance && instance.proxy,
4550
trace
46-
.map(({ vnode }) => `at <${formatComponentName(vnode)}>`)
51+
.map(
52+
({ vnode }) =>
53+
`at <${formatComponentName(vnode.type as Component)}>`
54+
)
4755
.join('\n'),
4856
trace
4957
]
@@ -111,24 +119,6 @@ function formatTraceEntry({ vnode, recurseCount }: TraceEntry): any[] {
111119
: [open + close, rootLabel]
112120
}
113121

114-
const classifyRE = /(?:^|[-_])(\w)/g
115-
const classify = (str: string): string =>
116-
str.replace(classifyRE, c => c.toUpperCase()).replace(/[-_]/g, '')
117-
118-
function formatComponentName(vnode: ComponentVNode, file?: string): string {
119-
const Component = vnode.type as Component
120-
let name = isFunction(Component)
121-
? Component.displayName || Component.name
122-
: Component.name
123-
if (!name && file) {
124-
const match = file.match(/([^/\\]+)\.vue$/)
125-
if (match) {
126-
name = match[1]
127-
}
128-
}
129-
return name ? classify(name) : 'Anonymous'
130-
}
131-
132122
function formatProps(props: Data): any[] {
133123
const res: any[] = []
134124
const keys = Object.keys(props)

0 commit comments

Comments
 (0)