Skip to content

Commit 568b6db

Browse files
authored
feat: Initial devtools support (#1125)
1 parent 5ed73cd commit 568b6db

15 files changed

Lines changed: 132 additions & 17 deletions

File tree

packages/compiler-sfc/src/compileTemplate.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ export interface SFCTemplateCompileOptions {
5757
*/
5858
transformAssetUrls?: AssetURLOptions | AssetURLTagConfig | boolean
5959
}
60-
60+
6161
interface PreProcessor {
6262
render(
6363
source: string,

packages/runtime-core/__tests__/componentProps.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ describe('component props', () => {
4545
render(h(Comp, { 'foo-bar': 3, bar: 3, baz: 4, barBaz: 5 }), root)
4646
expect(proxy.fooBar).toBe(3)
4747
expect(proxy.barBaz).toBe(5)
48-
expect(props).toEqual({ fooBar: 3,barBaz: 5 })
48+
expect(props).toEqual({ fooBar: 3, barBaz: 5 })
4949
expect(attrs).toEqual({ bar: 3, baz: 4 })
5050

5151
render(h(Comp, { qux: 5 }), root)

packages/runtime-core/__tests__/helpers/renderSlot.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ describe('renderSlot', () => {
1919
})
2020

2121
it('should warn render ssr slot', () => {
22-
renderSlot({ default: (a, b, c) => [h('child')] }, 'default')
22+
renderSlot({ default: (_a, _b, _c) => [h('child')] }, 'default')
2323
expect('SSR-optimized slot function detected').toHaveBeenWarned()
2424
})
2525
})

packages/runtime-core/src/apiCreateApp.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { isFunction, NO, isObject } from '@vue/shared'
1313
import { warn } from './warning'
1414
import { createVNode, cloneVNode, VNode } from './vnode'
1515
import { RootHydrateFunction } from './hydration'
16+
import { initApp, appUnmounted } from './devtools'
1617
import { version } from '.'
1718

1819
export interface App<HostElement = any> {
@@ -31,7 +32,7 @@ export interface App<HostElement = any> {
3132
unmount(rootContainer: HostElement | string): void
3233
provide<T>(key: InjectionKey<T> | string, value: T): this
3334

34-
// internal. We need to expose these for the server-renderer
35+
// internal. We need to expose these for the server-renderer and devtools
3536
_component: Component
3637
_props: Data | null
3738
_container: HostElement | null
@@ -73,6 +74,9 @@ export interface AppContext {
7374
directives: Record<string, Directive>
7475
provides: Record<string | symbol, any>
7576
reload?: () => void // HMR only
77+
78+
// internal for devtools
79+
__app?: App
7680
}
7781

7882
type PluginInstallFunction = (app: App, ...options: any[]) => any
@@ -226,6 +230,9 @@ export function createAppAPI<HostElement>(
226230
}
227231
isMounted = true
228232
app._container = rootContainer
233+
234+
__DEV__ && initApp(app, version)
235+
229236
return vnode.component!.proxy
230237
} else if (__DEV__) {
231238
warn(
@@ -240,6 +247,8 @@ export function createAppAPI<HostElement>(
240247
unmount() {
241248
if (isMounted) {
242249
render(null, app._container)
250+
251+
__DEV__ && appUnmounted(app)
243252
} else if (__DEV__) {
244253
warn(`Cannot unmount an app that is not mounted.`)
245254
}
@@ -260,6 +269,8 @@ export function createAppAPI<HostElement>(
260269
}
261270
}
262271

272+
context.__app = app
273+
263274
return app
264275
}
265276
}

packages/runtime-core/src/component.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ import {
4949
markAttrsAccessed
5050
} from './componentRenderUtils'
5151
import { startMeasure, endMeasure } from './profiling'
52+
import { componentAdded } from './devtools'
5253

5354
export type Data = { [key: string]: unknown }
5455

@@ -408,6 +409,9 @@ export function createComponentInstance(
408409
}
409410
instance.root = parent ? parent.root : instance
410411
instance.emit = emit.bind(null, instance)
412+
413+
__DEV__ && componentAdded(instance)
414+
411415
return instance
412416
}
413417

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { App } from './apiCreateApp'
2+
import { Fragment, Text, Comment, Static } from './vnode'
3+
import { ComponentInternalInstance } from './component'
4+
5+
export interface AppRecord {
6+
id: number
7+
app: App
8+
version: string
9+
types: { [key: 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+
}
19+
20+
export interface DevtoolsHook {
21+
emit: (event: string, ...payload: any[]) => void
22+
on: (event: string, handler: Function) => void
23+
once: (event: string, handler: Function) => void
24+
off: (event: string, handler: Function) => void
25+
appRecords: AppRecord[]
26+
}
27+
28+
export let devtools: DevtoolsHook
29+
30+
export function setDevtoolsHook(hook: DevtoolsHook) {
31+
devtools = hook
32+
}
33+
34+
export function initApp(app: App, version: string) {
35+
// TODO queue if devtools is undefined
36+
if (!devtools) return
37+
devtools.emit(DevtoolsHooks.APP_INIT, app, version, {
38+
Fragment: Fragment,
39+
Text: Text,
40+
Comment: Comment,
41+
Static: Static
42+
})
43+
}
44+
45+
export function appUnmounted(app: App) {
46+
if (!devtools) return
47+
devtools.emit(DevtoolsHooks.APP_UNMOUNT, app)
48+
}
49+
50+
export function componentAdded(component: ComponentInternalInstance) {
51+
if (!devtools || !component.appContext.__app) return
52+
devtools.emit(
53+
DevtoolsHooks.COMPONENT_ADDED,
54+
component.appContext.__app,
55+
component.uid,
56+
component.parent ? component.parent.uid : undefined
57+
)
58+
}
59+
60+
export function componentUpdated(component: ComponentInternalInstance) {
61+
if (!devtools || !component.appContext.__app) return
62+
devtools.emit(
63+
DevtoolsHooks.COMPONENT_UPDATED,
64+
component.appContext.__app,
65+
component.uid,
66+
component.parent ? component.parent.uid : undefined
67+
)
68+
}
69+
70+
export function componentRemoved(component: ComponentInternalInstance) {
71+
if (!devtools || !component.appContext.__app) return
72+
devtools.emit(
73+
DevtoolsHooks.COMPONENT_REMOVED,
74+
component.appContext.__app,
75+
component.uid,
76+
component.parent ? component.parent.uid : undefined
77+
)
78+
}

packages/runtime-core/src/index.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,10 @@ export {
9393
getTransitionRawChildren
9494
} from './components/BaseTransition'
9595

96-
// Types -----------------------------------------------------------------------
96+
// For devtools
97+
export { devtools, setDevtoolsHook } from './devtools'
98+
99+
// Types -------------------------------------------------------------------------
97100

98101
import { VNode } from './vnode'
99102
import { ComponentInternalInstance } from './component'

packages/runtime-core/src/renderer.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ import { createHydrationFunctions, RootHydrateFunction } from './hydration'
6565
import { invokeDirectiveHook } from './directives'
6666
import { startMeasure, endMeasure } from './profiling'
6767
import { ComponentPublicInstance } from './componentProxy'
68+
import { componentRemoved, componentUpdated } from './devtools'
6869

6970
export interface Renderer<HostElement = RendererElement> {
7071
render: RootRenderFunction<HostElement>
@@ -1417,6 +1418,7 @@ function baseCreateRenderer(
14171418
}
14181419
if (__DEV__) {
14191420
popWarningContext()
1421+
componentUpdated(instance)
14201422
}
14211423
}
14221424
}, __DEV__ ? createDevEffectOptions(instance) : prodEffectOptions)
@@ -2068,6 +2070,8 @@ function baseCreateRenderer(
20682070
parentSuspense.resolve()
20692071
}
20702072
}
2073+
2074+
__DEV__ && componentRemoved(instance)
20712075
}
20722076

20732077
const unmountChildren: UnmountChildrenFn = (

packages/server-renderer/__tests__/renderToStream.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ describe('ssr: renderToStream', () => {
8484
expect(
8585
await renderToStream(
8686
createApp(
87-
defineComponent((props: {}) => {
87+
defineComponent(() => {
8888
const msg = ref('hello')
8989
return () => h('div', msg.value)
9090
})
@@ -266,7 +266,7 @@ describe('ssr: renderToStream', () => {
266266
{ msg: 'hello' },
267267
{
268268
// optimized slot using string push
269-
default: ({ msg }: any, push: any, p: any) => {
269+
default: ({ msg }: any, push: any) => {
270270
push(`<span>${msg}</span>`)
271271
},
272272
// important to avoid slots being normalized

packages/vue/src/dev.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { version, setDevtoolsHook } from '@vue/runtime-dom'
2+
3+
export function initDev() {
4+
const target: any = __BROWSER__ ? window : global
5+
6+
target.__VUE__ = version
7+
setDevtoolsHook(target.__VUE_DEVTOOLS_GLOBAL_HOOK__)
8+
9+
if (__BROWSER__) {
10+
// @ts-ignore `console.info` cannot be null error
11+
console[console.info ? 'info' : 'log'](
12+
`You are running a development build of Vue.\n` +
13+
`Make sure to use the production build (*.prod.js) when deploying for production.`
14+
)
15+
}
16+
}

0 commit comments

Comments
 (0)