Skip to content

Commit

Permalink
fix(kit): unregister app instance when instance is unmounted (#340)
Browse files Browse the repository at this point in the history
  • Loading branch information
Azurewarth0920 committed Apr 22, 2024
1 parent 19a716c commit 5da5a6c
Show file tree
Hide file tree
Showing 11 changed files with 87 additions and 21 deletions.
7 changes: 7 additions & 0 deletions packages/devtools-kit/src/api/hook.ts
Expand Up @@ -67,3 +67,10 @@ export interface DevToolsEvent {
export type DevToolsEventParams<T extends keyof DevToolsEvent> = Parameters<DevToolsEvent[T]>

export const apiHooks: Hookable<DevToolsEvent, HookKeys<DevToolsEvent>> = target.__VUE_DEVTOOLS_API_HOOK ??= createHooks<DevToolsEvent>()

export const instanceHooks: (() => void)[] = []

export const registerInstanceHook = (...args: Parameters<(typeof apiHooks)['hook']>) => {
const unregister = apiHooks.hook(...args)
instanceHooks.push(unregister)
}
4 changes: 2 additions & 2 deletions packages/devtools-kit/src/api/off.ts
@@ -1,5 +1,5 @@
import { apiHooks } from './hook'
import { instanceHooks } from './hook'

export function remove() {
apiHooks.removeAllHooks()
instanceHooks.forEach(unregister => unregister())
}
28 changes: 14 additions & 14 deletions packages/devtools-kit/src/api/on.ts
@@ -1,56 +1,56 @@
import { DevToolsEvents, apiHooks } from './hook'
import { DevToolsEvents, apiHooks, registerInstanceHook } from './hook'
import type { DevToolsEvent } from './hook'

export const on = {
// #region compatible with old devtools
addTimelineEvent(fn: DevToolsEvent[DevToolsEvents.ADD_TIMELINE_EVENT]) {
apiHooks.hook(DevToolsEvents.ADD_TIMELINE_EVENT, fn)
registerInstanceHook(DevToolsEvents.ADD_TIMELINE_EVENT, fn)
},
inspectComponent(fn: DevToolsEvent[DevToolsEvents.COMPONENT_STATE_INSPECT]) {
apiHooks.hook(DevToolsEvents.COMPONENT_STATE_INSPECT, fn)
registerInstanceHook(DevToolsEvents.COMPONENT_STATE_INSPECT, fn)
},
visitComponentTree(fn: DevToolsEvent[DevToolsEvents.VISIT_COMPONENT_TREE]) {
apiHooks.hook(DevToolsEvents.VISIT_COMPONENT_TREE, fn)
registerInstanceHook(DevToolsEvents.VISIT_COMPONENT_TREE, fn)
},
getInspectorTree(fn: DevToolsEvent[DevToolsEvents.GET_INSPECTOR_TREE]) {
apiHooks.hook(DevToolsEvents.GET_INSPECTOR_TREE, fn)
registerInstanceHook(DevToolsEvents.GET_INSPECTOR_TREE, fn)
},
getInspectorState(fn: DevToolsEvent[DevToolsEvents.GET_INSPECTOR_STATE]) {
apiHooks.hook(DevToolsEvents.GET_INSPECTOR_STATE, fn)
registerInstanceHook(DevToolsEvents.GET_INSPECTOR_STATE, fn)
},
sendInspectorTree(fn: DevToolsEvent[DevToolsEvents.SEND_INSPECTOR_TREE]) {
apiHooks.hook(DevToolsEvents.SEND_INSPECTOR_TREE, fn)
registerInstanceHook(DevToolsEvents.SEND_INSPECTOR_TREE, fn)
},
sendInspectorState(fn: DevToolsEvent[DevToolsEvents.SEND_INSPECTOR_STATE]) {
apiHooks.hook(DevToolsEvents.SEND_INSPECTOR_STATE, fn)
registerInstanceHook(DevToolsEvents.SEND_INSPECTOR_STATE, fn)
},
editInspectorState(fn: DevToolsEvent[DevToolsEvents.EDIT_INSPECTOR_STATE]) {
apiHooks.hook(DevToolsEvents.EDIT_INSPECTOR_STATE, fn)
registerInstanceHook(DevToolsEvents.EDIT_INSPECTOR_STATE, fn)
},
editComponentState() {},
componentUpdated(fn: DevToolsEvent[DevToolsEvents.COMPONENT_UPDATED]) {
apiHooks.hook(DevToolsEvents.COMPONENT_UPDATED, fn)
registerInstanceHook(DevToolsEvents.COMPONENT_UPDATED, fn)
},
// #endregion compatible with old devtools

// router
routerInfoUpdated(fn: DevToolsEvent[DevToolsEvents.ROUTER_INFO_UPDATED]) {
apiHooks.hook(DevToolsEvents.ROUTER_INFO_UPDATED, fn)
registerInstanceHook(DevToolsEvents.ROUTER_INFO_UPDATED, fn)
},

// component highlighter
getComponentBoundingRect(fn: DevToolsEvent[DevToolsEvents.GET_COMPONENT_BOUNDING_RECT]) {
apiHooks.hook(DevToolsEvents.GET_COMPONENT_BOUNDING_RECT, fn)
registerInstanceHook(DevToolsEvents.GET_COMPONENT_BOUNDING_RECT, fn)
},

// custom tabs
customTabsUpdated(fn: DevToolsEvent[DevToolsEvents.CUSTOM_TABS_UPDATED]) {
apiHooks.hook(DevToolsEvents.CUSTOM_TABS_UPDATED, fn)
registerInstanceHook(DevToolsEvents.CUSTOM_TABS_UPDATED, fn)
},

// custom commands
customCommandsUpdated(fn: DevToolsEvent[DevToolsEvents.CUSTOM_COMMANDS_UPDATED]) {
apiHooks.hook(DevToolsEvents.CUSTOM_COMMANDS_UPDATED, fn)
registerInstanceHook(DevToolsEvents.CUSTOM_COMMANDS_UPDATED, fn)
},

devtoolsStateUpdated(fn: DevToolsEvent[DevToolsEvents.DEVTOOLS_STATE_UPDATED]) {
Expand Down
7 changes: 7 additions & 0 deletions packages/devtools-kit/src/core/index.ts
Expand Up @@ -63,6 +63,13 @@ export function initDevTools() {
}
})

hook.on.vueAppUnmount(async (app) => {
const activeRecords = devtoolsAppRecords.value.filter(appRecord => appRecord.app !== app)
devtoolsAppRecords.value = activeRecords
if (devtoolsAppRecords.active.app === app)
await setActiveAppRecord(activeRecords[0])
})

subscribeDevToolsHook()
}

Expand Down
7 changes: 5 additions & 2 deletions packages/devtools-kit/src/core/router/index.ts
Expand Up @@ -2,7 +2,7 @@ import type { RouteLocationNormalizedLoaded, RouteRecordRaw, Router } from 'vue-
import { deepClone, target as global } from '@vue/devtools-shared'
import { debounce } from 'perfect-debounce'
import { ROUTER_INFO_KEY, ROUTER_KEY } from '../../state'
import type { AppRecord } from '../../types'
import type { AppRecord, DevToolsState } from '../../types'
import { hook } from '../../hook'
import { DevToolsEvents, apiHooks } from '../../api/hook'

Expand Down Expand Up @@ -42,7 +42,7 @@ function filterCurrentRoute(route: RouteLocationNormalizedLoaded & { href?: stri
return route
}

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

// @TODO: use another way to watch router
hook.on.componentUpdated(debounce(() => {
if (state.activeAppRecord?.app !== appRecord.app)
return

init()
apiHooks.callHook(DevToolsEvents.ROUTER_INFO_UPDATED, global[ROUTER_INFO_KEY])
}, 200))
Expand Down
7 changes: 7 additions & 0 deletions packages/devtools-kit/src/hook/index.ts
Expand Up @@ -11,6 +11,9 @@ const on: VueHooks['on'] = {
vueAppInit(fn) {
devtoolsHooks.hook(DevToolsHooks.APP_INIT, fn)
},
vueAppUnmount(fn) {
devtoolsHooks.hook(DevToolsHooks.APP_UNMOUNT, fn)
},
vueAppConnected(fn) {
devtoolsHooks.hook(DevToolsHooks.APP_CONNECTED, fn)
},
Expand Down Expand Up @@ -78,6 +81,10 @@ export function subscribeDevToolsHook() {
devtoolsHooks.callHook(DevToolsHooks.APP_INIT, app, version)
})

hook.on<DevToolsEvent[DevToolsHooks.APP_UNMOUNT]>(DevToolsHooks.APP_UNMOUNT, (app) => {
devtoolsHooks.callHook(DevToolsHooks.APP_UNMOUNT, app)
})

// component added hook
hook.on<DevToolsEvent[DevToolsHooks.COMPONENT_ADDED]>(DevToolsHooks.COMPONENT_ADDED, async (app, uid, parentUid, component) => {
if (app?._instance?.type?.devtools?.hide)
Expand Down
2 changes: 1 addition & 1 deletion packages/devtools-kit/src/state/app-record.ts
Expand Up @@ -36,7 +36,7 @@ export const devtoolsAppRecords = new Proxy<DevToolsAppRecords>(devtoolsState.ap
devtoolsContext.api = _value.api!
// @ts-expect-error expected type
devtoolsContext.inspector = _value.inspector ?? []
normalizeRouterInfo(value)
normalizeRouterInfo(value, devtoolsState)
devtoolsContext.routerInfo = devtoolsRouterInfo
}

Expand Down
2 changes: 2 additions & 0 deletions packages/devtools-kit/src/types/hook.ts
Expand Up @@ -23,6 +23,7 @@ export enum DevToolsHooks {
export interface DevToolsEvent {
[DevToolsHooks.APP_INIT]: (app: VueAppInstance['appContext']['app'], version: string) => void
[DevToolsHooks.APP_CONNECTED]: () => void
[DevToolsHooks.APP_UNMOUNT]: (app: VueAppInstance['appContext']['app']) => void
[DevToolsHooks.COMPONENT_ADDED]: (app: HookAppInstance, uid: number, parentUid: number, component: VueAppInstance) => void
[DevToolsHooks.COMPONENT_UPDATED]: DevToolsEvent['component:added']
[DevToolsHooks.COMPONENT_REMOVED]: DevToolsEvent['component:added']
Expand All @@ -46,6 +47,7 @@ export interface DevToolsHook {
export interface VueHooks {
on: {
vueAppInit: (fn: DevToolsEvent[DevToolsHooks.APP_INIT]) => void
vueAppUnmount: (fn: DevToolsEvent[DevToolsHooks.APP_UNMOUNT]) => void
vueAppConnected: (fn: DevToolsEvent[DevToolsHooks.APP_CONNECTED]) => void
componentAdded: (fn: DevToolsEvent[DevToolsHooks.COMPONENT_ADDED]) => () => void
componentUpdated: (fn: DevToolsEvent[DevToolsHooks.COMPONENT_UPDATED]) => () => void
Expand Down
35 changes: 35 additions & 0 deletions packages/playground/basic/src/components/DynamicApp.vue
@@ -0,0 +1,35 @@
<script setup lang="ts">
import { App, createApp, shallowRef, triggerRef } from 'vue'
import Foo from './Foo.vue'
const dynamicInstance = shallowRef<App | null>(null)
function registerApp() {
const app = createApp(Foo)
app.mount('#dynamic-app')
dynamicInstance.value = app
triggerRef(dynamicInstance)
}
function removeApp() {
if (!dynamicInstance.value)
return
dynamicInstance.value.unmount()
document.querySelector('#dynamic-app')!.innerHTML = ''
dynamicInstance.value = null
triggerRef(dynamicInstance)
}
</script>

<template>
<div>
<button v-if="!dynamicInstance" @click="registerApp">
Register App
</button>
<button v-if="dynamicInstance" @click="removeApp">
Remove App
</button>
<div id="dynamic-app" />
</div>
</template>
4 changes: 2 additions & 2 deletions packages/playground/basic/src/components/Foo.vue
@@ -1,9 +1,9 @@
<script setup lang="ts">
const text = ref('Foo')
</script>

<template>
<div>
Foo Component
{{ text }} Component
</div>
</template>
5 changes: 5 additions & 0 deletions packages/playground/basic/src/pages/Hello.vue
@@ -1,5 +1,6 @@
<script setup lang="ts">
import { useAppStore } from '../stores'
import DynamicApp from '../components/DynamicApp.vue'
const app = useAppStore()
</script>
Expand All @@ -10,6 +11,10 @@ const app = useAppStore()
<button class="w-30 cursor-pointer" @click="app.increment()">
Increment count
</button>

<div>
DynamicApp: <DynamicApp />
</div>
</div>
</template>

Expand Down

0 comments on commit 5da5a6c

Please sign in to comment.