diff --git a/packages/devtools-kit/src/api/hook.ts b/packages/devtools-kit/src/api/hook.ts index d6af4400..3410ba50 100644 --- a/packages/devtools-kit/src/api/hook.ts +++ b/packages/devtools-kit/src/api/hook.ts @@ -67,3 +67,10 @@ export interface DevToolsEvent { export type DevToolsEventParams = Parameters export const apiHooks: Hookable> = target.__VUE_DEVTOOLS_API_HOOK ??= createHooks() + +export const instanceHooks: (() => void)[] = [] + +export const registerInstanceHook = (...args: Parameters<(typeof apiHooks)['hook']>) => { + const unregister = apiHooks.hook(...args) + instanceHooks.push(unregister) +} diff --git a/packages/devtools-kit/src/api/off.ts b/packages/devtools-kit/src/api/off.ts index f10322f9..80ff4c0b 100644 --- a/packages/devtools-kit/src/api/off.ts +++ b/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()) } diff --git a/packages/devtools-kit/src/api/on.ts b/packages/devtools-kit/src/api/on.ts index a16e787a..e25d92e0 100644 --- a/packages/devtools-kit/src/api/on.ts +++ b/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]) { diff --git a/packages/devtools-kit/src/core/index.ts b/packages/devtools-kit/src/core/index.ts index b3e86a0b..c85af3b1 100644 --- a/packages/devtools-kit/src/core/index.ts +++ b/packages/devtools-kit/src/core/index.ts @@ -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() } diff --git a/packages/devtools-kit/src/core/router/index.ts b/packages/devtools-kit/src/core/router/index.ts index 96d79fde..02e9c81a 100644 --- a/packages/devtools-kit/src/core/router/index.ts +++ b/packages/devtools-kit/src/core/router/index.ts @@ -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' @@ -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) @@ -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)) diff --git a/packages/devtools-kit/src/hook/index.ts b/packages/devtools-kit/src/hook/index.ts index 293c377f..ce67dbff 100644 --- a/packages/devtools-kit/src/hook/index.ts +++ b/packages/devtools-kit/src/hook/index.ts @@ -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) }, @@ -78,6 +81,10 @@ export function subscribeDevToolsHook() { devtoolsHooks.callHook(DevToolsHooks.APP_INIT, app, version) }) + hook.on(DevToolsHooks.APP_UNMOUNT, (app) => { + devtoolsHooks.callHook(DevToolsHooks.APP_UNMOUNT, app) + }) + // component added hook hook.on(DevToolsHooks.COMPONENT_ADDED, async (app, uid, parentUid, component) => { if (app?._instance?.type?.devtools?.hide) diff --git a/packages/devtools-kit/src/state/app-record.ts b/packages/devtools-kit/src/state/app-record.ts index 47f1d477..77fe1392 100644 --- a/packages/devtools-kit/src/state/app-record.ts +++ b/packages/devtools-kit/src/state/app-record.ts @@ -36,7 +36,7 @@ export const devtoolsAppRecords = new Proxy(devtoolsState.ap devtoolsContext.api = _value.api! // @ts-expect-error expected type devtoolsContext.inspector = _value.inspector ?? [] - normalizeRouterInfo(value) + normalizeRouterInfo(value, devtoolsState) devtoolsContext.routerInfo = devtoolsRouterInfo } diff --git a/packages/devtools-kit/src/types/hook.ts b/packages/devtools-kit/src/types/hook.ts index 78e6d55f..e7a6872b 100644 --- a/packages/devtools-kit/src/types/hook.ts +++ b/packages/devtools-kit/src/types/hook.ts @@ -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'] @@ -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 diff --git a/packages/playground/basic/src/components/DynamicApp.vue b/packages/playground/basic/src/components/DynamicApp.vue new file mode 100644 index 00000000..e71a444a --- /dev/null +++ b/packages/playground/basic/src/components/DynamicApp.vue @@ -0,0 +1,35 @@ + + + diff --git a/packages/playground/basic/src/components/Foo.vue b/packages/playground/basic/src/components/Foo.vue index c3e2c193..198a822f 100644 --- a/packages/playground/basic/src/components/Foo.vue +++ b/packages/playground/basic/src/components/Foo.vue @@ -1,9 +1,9 @@ diff --git a/packages/playground/basic/src/pages/Hello.vue b/packages/playground/basic/src/pages/Hello.vue index b115d2e7..5641272f 100644 --- a/packages/playground/basic/src/pages/Hello.vue +++ b/packages/playground/basic/src/pages/Hello.vue @@ -1,5 +1,6 @@ @@ -10,6 +11,10 @@ const app = useAppStore() + +
+ DynamicApp: +