diff --git a/packages/applet/src/modules/components/index.vue b/packages/applet/src/modules/components/index.vue index 929245e52..30f82370d 100644 --- a/packages/applet/src/modules/components/index.vue +++ b/packages/applet/src/modules/components/index.vue @@ -220,10 +220,11 @@ const devtoolsState = useDevToolsState() const appRecords = computed(() => devtoolsState.appRecords.value.map(app => ({ label: app.name + (app.version ? ` (${app.version})` : ''), value: app.id, + iframe: app.iframe, }))) const normalizedAppRecords = computed(() => appRecords.value.map(app => ({ - label: app.label, + label: app.label + (app.iframe ? ` (iframe: ${app.iframe})` : ''), id: app.value, }))) diff --git a/packages/core/src/rpc/global.ts b/packages/core/src/rpc/global.ts index 8fa6af68c..a37855509 100644 --- a/packages/core/src/rpc/global.ts +++ b/packages/core/src/rpc/global.ts @@ -30,6 +30,7 @@ function getDevToolsState() { name: item.name, version: item.version, routerId: item.routerId, + iframe: item.iframe, })), activeAppRecordId: state.activeAppRecordId, timelineLayersState: state.timelineLayersState, diff --git a/packages/devtools-kit/src/core/app/index.ts b/packages/devtools-kit/src/core/app/index.ts index 34224ea4a..f040afb83 100644 --- a/packages/devtools-kit/src/core/app/index.ts +++ b/packages/devtools-kit/src/core/app/index.ts @@ -1,6 +1,7 @@ -import { target } from '@vue/devtools-shared' +import { isBrowser, target } from '@vue/devtools-shared' import slug from 'speakingurl' import { AppRecord, VueAppInstance } from '../../types' +import { getRootElementsFromComponentInstance } from '../component/tree/el' const appRecordInfo = target.__VUE_DEVTOOLS_NEXT_APP_RECORD_INFO__ ??= { id: 0, @@ -52,6 +53,7 @@ export function createAppRecord(app: VueAppInstance['appContext']['app'], types: appRecordInfo.id++ const name = getAppRecordName(app, appRecordInfo.id.toString()) const id = getAppRecordId(app, slug(name)) + const [el] = getRootElementsFromComponentInstance(rootInstance) as /* type-compatible, this is returning VNode[] */ unknown as HTMLElement[] const record: AppRecord = { id, @@ -60,6 +62,7 @@ export function createAppRecord(app: VueAppInstance['appContext']['app'], types: instanceMap: new Map(), perfGroupIds: new Map(), rootInstance, + iframe: isBrowser && document !== el?.ownerDocument ? el?.ownerDocument?.location?.pathname : undefined, } app.__VUE_DEVTOOLS_NEXT_APP_RECORD__ = record diff --git a/packages/devtools-kit/src/core/iframe/index.ts b/packages/devtools-kit/src/core/iframe/index.ts new file mode 100644 index 000000000..853311af9 --- /dev/null +++ b/packages/devtools-kit/src/core/iframe/index.ts @@ -0,0 +1,98 @@ +export function detectIframeApp(target: Window | typeof globalThis, inIframe = false) { + if (inIframe) { + function sendEventToParent(cb) { + try { + // @ts-expect-error skip type check + const hook = window.parent.__VUE_DEVTOOLS_GLOBAL_HOOK__ + if (hook) { + cb(hook) + } + } + catch (e) { + // Ignore + } + } + + const hook = { + id: 'vue-devtools-next', + devtoolsVersion: '7.0', + on: (event, cb) => { + sendEventToParent((hook) => { + hook.on(event, cb) + }) + }, + once: (event, cb) => { + sendEventToParent((hook) => { + hook.once(event, cb) + }) + }, + off: (event, cb) => { + sendEventToParent((hook) => { + hook.off(event, cb) + }) + }, + emit: (event, ...payload) => { + sendEventToParent((hook) => { + hook.emit(event, ...payload) + }) + }, + } + + Object.defineProperty(target, '__VUE_DEVTOOLS_GLOBAL_HOOK__', { + get() { + return hook + }, + configurable: true, + }) + } + + function injectVueHookToIframe(iframe) { + if (iframe.__vdevtools__injected) { + return + } + try { + iframe.__vdevtools__injected = true + const inject = () => { + console.log('inject', iframe) + try { + iframe.contentWindow.__VUE_DEVTOOLS_IFRAME__ = iframe + const script = iframe.contentDocument.createElement('script') + script.textContent = `;(${detectIframeApp.toString()})(window, true)` + iframe.contentDocument.documentElement.appendChild(script) + script.parentNode.removeChild(script) + } + catch (e) { + // Ignore + } + } + inject() + iframe.addEventListener('load', () => inject()) + } + catch (e) { + // Ignore + } + } + + // detect iframe app to inject vue hook + function injectVueHookToIframes() { + if (typeof window === 'undefined') { + return + } + + const iframes = Array.from(document.querySelectorAll('iframe:not([data-vue-devtools-ignore])')) + for (const iframe of iframes) { + injectVueHookToIframe(iframe) + } + } + + injectVueHookToIframes() + + let iframeAppChecks = 0 + const iframeAppCheckTimer = setInterval(() => { + injectVueHookToIframes() + iframeAppChecks++ + if (iframeAppChecks >= 5) { + clearInterval(iframeAppCheckTimer) + } + }, 1000) +} diff --git a/packages/devtools-kit/src/core/index.ts b/packages/devtools-kit/src/core/index.ts index cde51056e..aabdd2118 100644 --- a/packages/devtools-kit/src/core/index.ts +++ b/packages/devtools-kit/src/core/index.ts @@ -18,11 +18,14 @@ import { import { createDevToolsHook, hook, subscribeDevToolsHook } from '../hook' import { DevToolsHooks } from '../types' import { createAppRecord, removeAppRecordId } from './app' +import { detectIframeApp } from './iframe' import { callDevToolsPluginSetupFn, createComponentsDevToolsPlugin, registerDevToolsPlugin, removeRegisteredPluginApp, setupDevToolsPlugin } from './plugin' import { initPluginSettings } from './plugin/plugin-settings' import { normalizeRouterInfo } from './router' export function initDevTools() { + detectIframeApp(target) + updateDevToolsState({ vitePluginDetected: getDevToolsEnv().vitePluginDetected, }) @@ -134,6 +137,7 @@ export function initDevTools() { get() { return _devtoolsHook }, + configurable: true, }) } else { diff --git a/packages/devtools-kit/src/types/app.ts b/packages/devtools-kit/src/types/app.ts index 236753655..1459b2b61 100644 --- a/packages/devtools-kit/src/types/app.ts +++ b/packages/devtools-kit/src/types/app.ts @@ -53,4 +53,5 @@ export interface AppRecord { perfGroupIds: Map rootInstance: VueAppInstance routerId?: string + iframe?: string } diff --git a/packages/playground/multi-app/index.html b/packages/playground/multi-app/index.html index 7e85ca8b3..726aa8519 100644 --- a/packages/playground/multi-app/index.html +++ b/packages/playground/multi-app/index.html @@ -9,6 +9,7 @@
+
diff --git a/packages/playground/multi-app/src/components/Iframe/index.vue b/packages/playground/multi-app/src/components/Iframe/index.vue new file mode 100644 index 000000000..1e37c5a31 --- /dev/null +++ b/packages/playground/multi-app/src/components/Iframe/index.vue @@ -0,0 +1,12 @@ + + +