Skip to content

Commit 568b6db

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

File tree

15 files changed

+132
-17
lines changed

15 files changed

+132
-17
lines changed

packages/compiler-sfc/src/compileTemplate.ts

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

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

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

+12-1
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

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

packages/runtime-core/src/devtools.ts

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

+4-1
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

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

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

+16
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+
}

packages/vue/src/devCheck.ts

-7
This file was deleted.

packages/vue/src/index.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
// This entry is the "full-build" that includes both the runtime
22
// and the compiler, and supports on-the-fly compilation of the template option.
3-
import './devCheck'
3+
import { initDev } from './dev'
44
import { compile, CompilerOptions, CompilerError } from '@vue/compiler-dom'
55
import { registerRuntimeCompiler, RenderFunction, warn } from '@vue/runtime-dom'
66
import * as runtimeDom from '@vue/runtime-dom'
77
import { isString, NOOP, generateCodeFrame, extend } from '@vue/shared'
88

9+
__DEV__ && initDev()
10+
911
const compileCache: Record<string, RenderFunction> = Object.create(null)
1012

1113
function compileToFunction(

packages/vue/src/runtime.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
// This entry exports the runtime only, and is built as
22
// `dist/vue.esm-bundler.js` which is used by default for bundlers.
3-
import './devCheck'
3+
import { initDev } from './dev'
44
import { warn } from '@vue/runtime-dom'
55

6+
__DEV__ && initDev()
7+
68
export * from '@vue/runtime-dom'
79

810
export const compile = () => {

test-dts/defineComponent.test-d.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -622,7 +622,7 @@ describe('emits', () => {
622622
defineComponent({
623623
emits: {
624624
click: (n: number) => typeof n === 'number',
625-
input: (b: string) => null
625+
input: (b: string) => b.length > 1
626626
},
627627
setup(props, { emit }) {
628628
emit('click', 1)

test-dts/ref.test-d.ts

+2
Original file line numberDiff line numberDiff line change
@@ -76,11 +76,13 @@ function bailType(arg: HTMLElement | Ref<HTMLElement>) {
7676
expectType<HTMLElement>(unref(arg))
7777

7878
// ref inner type should be unwrapped
79+
// eslint-disable-next-line no-restricted-globals
7980
const nestedRef = ref({ foo: ref(document.createElement('DIV')) })
8081

8182
expectType<Ref<{ foo: HTMLElement }>>(nestedRef)
8283
expectType<{ foo: HTMLElement }>(nestedRef.value)
8384
}
85+
// eslint-disable-next-line no-restricted-globals
8486
const el = document.createElement('DIV')
8587
bailType(el)
8688

0 commit comments

Comments
 (0)