Skip to content

Commit 5da5a6c

Browse files
authoredApr 22, 2024··
fix(kit): unregister app instance when instance is unmounted (#340)
1 parent 19a716c commit 5da5a6c

File tree

11 files changed

+87
-21
lines changed

11 files changed

+87
-21
lines changed
 

‎packages/devtools-kit/src/api/hook.ts

+7
Original file line numberDiff line numberDiff line change
@@ -67,3 +67,10 @@ export interface DevToolsEvent {
6767
export type DevToolsEventParams<T extends keyof DevToolsEvent> = Parameters<DevToolsEvent[T]>
6868

6969
export const apiHooks: Hookable<DevToolsEvent, HookKeys<DevToolsEvent>> = target.__VUE_DEVTOOLS_API_HOOK ??= createHooks<DevToolsEvent>()
70+
71+
export const instanceHooks: (() => void)[] = []
72+
73+
export const registerInstanceHook = (...args: Parameters<(typeof apiHooks)['hook']>) => {
74+
const unregister = apiHooks.hook(...args)
75+
instanceHooks.push(unregister)
76+
}

‎packages/devtools-kit/src/api/off.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { apiHooks } from './hook'
1+
import { instanceHooks } from './hook'
22

33
export function remove() {
4-
apiHooks.removeAllHooks()
4+
instanceHooks.forEach(unregister => unregister())
55
}

‎packages/devtools-kit/src/api/on.ts

+14-14
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,56 @@
1-
import { DevToolsEvents, apiHooks } from './hook'
1+
import { DevToolsEvents, apiHooks, registerInstanceHook } from './hook'
22
import type { DevToolsEvent } from './hook'
33

44
export const on = {
55
// #region compatible with old devtools
66
addTimelineEvent(fn: DevToolsEvent[DevToolsEvents.ADD_TIMELINE_EVENT]) {
7-
apiHooks.hook(DevToolsEvents.ADD_TIMELINE_EVENT, fn)
7+
registerInstanceHook(DevToolsEvents.ADD_TIMELINE_EVENT, fn)
88
},
99
inspectComponent(fn: DevToolsEvent[DevToolsEvents.COMPONENT_STATE_INSPECT]) {
10-
apiHooks.hook(DevToolsEvents.COMPONENT_STATE_INSPECT, fn)
10+
registerInstanceHook(DevToolsEvents.COMPONENT_STATE_INSPECT, fn)
1111
},
1212
visitComponentTree(fn: DevToolsEvent[DevToolsEvents.VISIT_COMPONENT_TREE]) {
13-
apiHooks.hook(DevToolsEvents.VISIT_COMPONENT_TREE, fn)
13+
registerInstanceHook(DevToolsEvents.VISIT_COMPONENT_TREE, fn)
1414
},
1515
getInspectorTree(fn: DevToolsEvent[DevToolsEvents.GET_INSPECTOR_TREE]) {
16-
apiHooks.hook(DevToolsEvents.GET_INSPECTOR_TREE, fn)
16+
registerInstanceHook(DevToolsEvents.GET_INSPECTOR_TREE, fn)
1717
},
1818
getInspectorState(fn: DevToolsEvent[DevToolsEvents.GET_INSPECTOR_STATE]) {
19-
apiHooks.hook(DevToolsEvents.GET_INSPECTOR_STATE, fn)
19+
registerInstanceHook(DevToolsEvents.GET_INSPECTOR_STATE, fn)
2020
},
2121
sendInspectorTree(fn: DevToolsEvent[DevToolsEvents.SEND_INSPECTOR_TREE]) {
22-
apiHooks.hook(DevToolsEvents.SEND_INSPECTOR_TREE, fn)
22+
registerInstanceHook(DevToolsEvents.SEND_INSPECTOR_TREE, fn)
2323
},
2424
sendInspectorState(fn: DevToolsEvent[DevToolsEvents.SEND_INSPECTOR_STATE]) {
25-
apiHooks.hook(DevToolsEvents.SEND_INSPECTOR_STATE, fn)
25+
registerInstanceHook(DevToolsEvents.SEND_INSPECTOR_STATE, fn)
2626
},
2727
editInspectorState(fn: DevToolsEvent[DevToolsEvents.EDIT_INSPECTOR_STATE]) {
28-
apiHooks.hook(DevToolsEvents.EDIT_INSPECTOR_STATE, fn)
28+
registerInstanceHook(DevToolsEvents.EDIT_INSPECTOR_STATE, fn)
2929
},
3030
editComponentState() {},
3131
componentUpdated(fn: DevToolsEvent[DevToolsEvents.COMPONENT_UPDATED]) {
32-
apiHooks.hook(DevToolsEvents.COMPONENT_UPDATED, fn)
32+
registerInstanceHook(DevToolsEvents.COMPONENT_UPDATED, fn)
3333
},
3434
// #endregion compatible with old devtools
3535

3636
// router
3737
routerInfoUpdated(fn: DevToolsEvent[DevToolsEvents.ROUTER_INFO_UPDATED]) {
38-
apiHooks.hook(DevToolsEvents.ROUTER_INFO_UPDATED, fn)
38+
registerInstanceHook(DevToolsEvents.ROUTER_INFO_UPDATED, fn)
3939
},
4040

4141
// component highlighter
4242
getComponentBoundingRect(fn: DevToolsEvent[DevToolsEvents.GET_COMPONENT_BOUNDING_RECT]) {
43-
apiHooks.hook(DevToolsEvents.GET_COMPONENT_BOUNDING_RECT, fn)
43+
registerInstanceHook(DevToolsEvents.GET_COMPONENT_BOUNDING_RECT, fn)
4444
},
4545

4646
// custom tabs
4747
customTabsUpdated(fn: DevToolsEvent[DevToolsEvents.CUSTOM_TABS_UPDATED]) {
48-
apiHooks.hook(DevToolsEvents.CUSTOM_TABS_UPDATED, fn)
48+
registerInstanceHook(DevToolsEvents.CUSTOM_TABS_UPDATED, fn)
4949
},
5050

5151
// custom commands
5252
customCommandsUpdated(fn: DevToolsEvent[DevToolsEvents.CUSTOM_COMMANDS_UPDATED]) {
53-
apiHooks.hook(DevToolsEvents.CUSTOM_COMMANDS_UPDATED, fn)
53+
registerInstanceHook(DevToolsEvents.CUSTOM_COMMANDS_UPDATED, fn)
5454
},
5555

5656
devtoolsStateUpdated(fn: DevToolsEvent[DevToolsEvents.DEVTOOLS_STATE_UPDATED]) {

‎packages/devtools-kit/src/core/index.ts

+7
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,13 @@ export function initDevTools() {
6363
}
6464
})
6565

66+
hook.on.vueAppUnmount(async (app) => {
67+
const activeRecords = devtoolsAppRecords.value.filter(appRecord => appRecord.app !== app)
68+
devtoolsAppRecords.value = activeRecords
69+
if (devtoolsAppRecords.active.app === app)
70+
await setActiveAppRecord(activeRecords[0])
71+
})
72+
6673
subscribeDevToolsHook()
6774
}
6875

‎packages/devtools-kit/src/core/router/index.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { RouteLocationNormalizedLoaded, RouteRecordRaw, Router } from 'vue-
22
import { deepClone, target as global } from '@vue/devtools-shared'
33
import { debounce } from 'perfect-debounce'
44
import { ROUTER_INFO_KEY, ROUTER_KEY } from '../../state'
5-
import type { AppRecord } from '../../types'
5+
import type { AppRecord, DevToolsState } from '../../types'
66
import { hook } from '../../hook'
77
import { DevToolsEvents, apiHooks } from '../../api/hook'
88

@@ -42,7 +42,7 @@ function filterCurrentRoute(route: RouteLocationNormalizedLoaded & { href?: stri
4242
return route
4343
}
4444

45-
export function normalizeRouterInfo(appRecord: AppRecord) {
45+
export function normalizeRouterInfo(appRecord: AppRecord, state: DevToolsState) {
4646
function init() {
4747
const router = appRecord.app?.config.globalProperties.$router as Router | undefined
4848
const currentRoute = filterCurrentRoute(router?.currentRoute.value)
@@ -61,6 +61,9 @@ export function normalizeRouterInfo(appRecord: AppRecord) {
6161

6262
// @TODO: use another way to watch router
6363
hook.on.componentUpdated(debounce(() => {
64+
if (state.activeAppRecord?.app !== appRecord.app)
65+
return
66+
6467
init()
6568
apiHooks.callHook(DevToolsEvents.ROUTER_INFO_UPDATED, global[ROUTER_INFO_KEY])
6669
}, 200))

‎packages/devtools-kit/src/hook/index.ts

+7
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ const on: VueHooks['on'] = {
1111
vueAppInit(fn) {
1212
devtoolsHooks.hook(DevToolsHooks.APP_INIT, fn)
1313
},
14+
vueAppUnmount(fn) {
15+
devtoolsHooks.hook(DevToolsHooks.APP_UNMOUNT, fn)
16+
},
1417
vueAppConnected(fn) {
1518
devtoolsHooks.hook(DevToolsHooks.APP_CONNECTED, fn)
1619
},
@@ -78,6 +81,10 @@ export function subscribeDevToolsHook() {
7881
devtoolsHooks.callHook(DevToolsHooks.APP_INIT, app, version)
7982
})
8083

84+
hook.on<DevToolsEvent[DevToolsHooks.APP_UNMOUNT]>(DevToolsHooks.APP_UNMOUNT, (app) => {
85+
devtoolsHooks.callHook(DevToolsHooks.APP_UNMOUNT, app)
86+
})
87+
8188
// component added hook
8289
hook.on<DevToolsEvent[DevToolsHooks.COMPONENT_ADDED]>(DevToolsHooks.COMPONENT_ADDED, async (app, uid, parentUid, component) => {
8390
if (app?._instance?.type?.devtools?.hide)

‎packages/devtools-kit/src/state/app-record.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export const devtoolsAppRecords = new Proxy<DevToolsAppRecords>(devtoolsState.ap
3636
devtoolsContext.api = _value.api!
3737
// @ts-expect-error expected type
3838
devtoolsContext.inspector = _value.inspector ?? []
39-
normalizeRouterInfo(value)
39+
normalizeRouterInfo(value, devtoolsState)
4040
devtoolsContext.routerInfo = devtoolsRouterInfo
4141
}
4242

‎packages/devtools-kit/src/types/hook.ts

+2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export enum DevToolsHooks {
2323
export interface DevToolsEvent {
2424
[DevToolsHooks.APP_INIT]: (app: VueAppInstance['appContext']['app'], version: string) => void
2525
[DevToolsHooks.APP_CONNECTED]: () => void
26+
[DevToolsHooks.APP_UNMOUNT]: (app: VueAppInstance['appContext']['app']) => void
2627
[DevToolsHooks.COMPONENT_ADDED]: (app: HookAppInstance, uid: number, parentUid: number, component: VueAppInstance) => void
2728
[DevToolsHooks.COMPONENT_UPDATED]: DevToolsEvent['component:added']
2829
[DevToolsHooks.COMPONENT_REMOVED]: DevToolsEvent['component:added']
@@ -46,6 +47,7 @@ export interface DevToolsHook {
4647
export interface VueHooks {
4748
on: {
4849
vueAppInit: (fn: DevToolsEvent[DevToolsHooks.APP_INIT]) => void
50+
vueAppUnmount: (fn: DevToolsEvent[DevToolsHooks.APP_UNMOUNT]) => void
4951
vueAppConnected: (fn: DevToolsEvent[DevToolsHooks.APP_CONNECTED]) => void
5052
componentAdded: (fn: DevToolsEvent[DevToolsHooks.COMPONENT_ADDED]) => () => void
5153
componentUpdated: (fn: DevToolsEvent[DevToolsHooks.COMPONENT_UPDATED]) => () => void
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<script setup lang="ts">
2+
import { App, createApp, shallowRef, triggerRef } from 'vue'
3+
import Foo from './Foo.vue'
4+
5+
const dynamicInstance = shallowRef<App | null>(null)
6+
7+
function registerApp() {
8+
const app = createApp(Foo)
9+
10+
app.mount('#dynamic-app')
11+
dynamicInstance.value = app
12+
triggerRef(dynamicInstance)
13+
}
14+
15+
function removeApp() {
16+
if (!dynamicInstance.value)
17+
return
18+
dynamicInstance.value.unmount()
19+
document.querySelector('#dynamic-app')!.innerHTML = ''
20+
dynamicInstance.value = null
21+
triggerRef(dynamicInstance)
22+
}
23+
</script>
24+
25+
<template>
26+
<div>
27+
<button v-if="!dynamicInstance" @click="registerApp">
28+
Register App
29+
</button>
30+
<button v-if="dynamicInstance" @click="removeApp">
31+
Remove App
32+
</button>
33+
<div id="dynamic-app" />
34+
</div>
35+
</template>
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
<script setup lang="ts">
2-
2+
const text = ref('Foo')
33
</script>
44

55
<template>
66
<div>
7-
Foo Component
7+
{{ text }} Component
88
</div>
99
</template>

‎packages/playground/basic/src/pages/Hello.vue

+5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<script setup lang="ts">
22
import { useAppStore } from '../stores'
3+
import DynamicApp from '../components/DynamicApp.vue'
34
45
const app = useAppStore()
56
</script>
@@ -10,6 +11,10 @@ const app = useAppStore()
1011
<button class="w-30 cursor-pointer" @click="app.increment()">
1112
Increment count
1213
</button>
14+
15+
<div>
16+
DynamicApp: <DynamicApp />
17+
</div>
1318
</div>
1419
</template>
1520

0 commit comments

Comments
 (0)
Please sign in to comment.