|
import type { AppRecord, BackendContext, DevtoolsApi } from '@vue-devtools/app-backend-api' |
|
import { classify, kebabize } from '@vue-devtools/shared-utils' |
|
import type { ComponentInstance, ComponentTreeNode } from '@vue/devtools-api' |
|
import { getRootElementsFromComponentInstance } from './el' |
|
import { applyPerfHooks } from './perf.js' |
|
import { applyTrackingUpdateHook } from './update-tracking.js' |
|
import { getInstanceName, getRenderKey, getUniqueId, isBeingDestroyed } from './util' |
|
|
|
let instanceMap: Map<any, any> = new Map() |
|
let functionalVnodeMap: Map<any, any> = new Map() |
|
|
|
export function getInstanceMap() { |
|
return instanceMap |
|
} |
|
|
|
export function getFunctionalVnodeMap() { |
|
return functionalVnodeMap |
|
} |
|
|
|
let appRecord: AppRecord |
|
let api: DevtoolsApi |
|
|
|
const consoleBoundInstances = Array(5) |
|
|
|
let filter = '' |
|
let recursively = false |
|
const functionalIds = new Map() |
|
|
|
|
|
|
|
const captureIds = new Map() |
|
|
|
export async function walkTree(instance, pFilter: string, pRecursively: boolean, api: DevtoolsApi, ctx: BackendContext): Promise<ComponentTreeNode[]> { |
|
initCtx(api, ctx) |
|
filter = pFilter |
|
recursively = pRecursively |
|
functionalIds.clear() |
|
captureIds.clear() |
|
const result: ComponentTreeNode[] = flatten(await findQualifiedChildren(instance)) |
|
return result |
|
} |
|
|
|
export function getComponentParents(instance, api: DevtoolsApi, ctx: BackendContext) { |
|
initCtx(api, ctx) |
|
const captureIds = new Map() |
|
|
|
const captureId = (vm) => { |
|
const id = vm.__VUE_DEVTOOLS_UID__ = getUniqueId(vm) |
|
if (captureIds.has(id)) { |
|
return |
|
} |
|
captureIds.set(id, undefined) |
|
if (vm.__VUE_DEVTOOLS_FUNCTIONAL_LEGACY__) { |
|
markFunctional(id, vm.vnode) |
|
} |
|
else { |
|
mark(vm) |
|
} |
|
} |
|
|
|
const parents = [] |
|
captureId(instance) |
|
let parent = instance |
|
|
|
while ((parent = parent.$parent)) { |
|
captureId(parent) |
|
parents.push(parent) |
|
} |
|
return parents |
|
} |
|
|
|
function initCtx(_api: DevtoolsApi, ctx: BackendContext) { |
|
appRecord = ctx.currentAppRecord |
|
api = _api |
|
if (!appRecord.meta) { |
|
appRecord.meta = {} |
|
} |
|
if (!appRecord.meta.instanceMap) { |
|
appRecord.meta.instanceMap = new Map() |
|
} |
|
instanceMap = appRecord.meta.instanceMap |
|
if (!appRecord.meta.functionalVnodeMap) { |
|
appRecord.meta.functionalVnodeMap = new Map() |
|
} |
|
functionalVnodeMap = appRecord.meta.functionalVnodeMap |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function findQualifiedChildrenFromList(instances: any[]): Promise<ComponentTreeNode[]> { |
|
instances = instances |
|
.filter(child => !isBeingDestroyed(child)) |
|
return Promise.all(!filter |
|
? instances.map(capture) |
|
: Array.prototype.concat.apply([], instances.map(findQualifiedChildren))) |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
async function findQualifiedChildren(instance): Promise<ComponentTreeNode[]> { |
|
if (isQualified(instance)) { |
|
return [await capture(instance)] |
|
} |
|
else { |
|
let children = await findQualifiedChildrenFromList(instance.$children) |
|
|
|
|
|
if (instance._vnode && instance._vnode.children) { |
|
const list = await Promise.all(flatten<Promise<ComponentTreeNode>>((instance._vnode.children as any[]).filter(child => !child.componentInstance).map(captureChild))) |
|
|
|
const additionalChildren = list.filter(instance => isQualified(instance)) |
|
children = children.concat(additionalChildren) |
|
} |
|
|
|
return children |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
function getInternalInstanceChildren(instance): any[] { |
|
if (instance.$children) { |
|
return instance.$children |
|
} |
|
return [] |
|
} |
|
|
|
|
|
|
|
|
|
function isQualified(instance): boolean { |
|
const name = getInstanceName(instance) |
|
return classify(name).toLowerCase().includes(filter) |
|
|| kebabize(name).toLowerCase().includes(filter) |
|
} |
|
|
|
function flatten<T>(items: any[]): T[] { |
|
const r = items.reduce((acc, item) => { |
|
if (Array.isArray(item)) { |
|
let children = [] |
|
for (const i of item) { |
|
if (Array.isArray(i)) { |
|
children = children.concat(flatten(i)) |
|
} |
|
else { |
|
children.push(i) |
|
} |
|
} |
|
acc.push(...children) |
|
} |
|
else if (item) { |
|
acc.push(item) |
|
} |
|
|
|
return acc |
|
}, [] as T[]) |
|
return r |
|
} |
|
|
|
function captureChild(child): Promise<ComponentTreeNode[] | ComponentTreeNode> { |
|
if (child.fnContext && !child.componentInstance) { |
|
return capture(child) |
|
} |
|
else if (child.componentInstance) { |
|
if (!isBeingDestroyed(child.componentInstance)) { |
|
return capture(child.componentInstance) |
|
} |
|
} |
|
else if (child.children) { |
|
return Promise.all(flatten<Promise<ComponentTreeNode>>(child.children.map(captureChild))) |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
async function capture(instance, _index?: number, _list?: any[]): Promise<ComponentTreeNode> { |
|
if (instance.__VUE_DEVTOOLS_FUNCTIONAL_LEGACY__) { |
|
instance = instance.vnode |
|
} |
|
|
|
if (instance.$options && instance.$options.abstract && instance._vnode && instance._vnode.componentInstance) { |
|
instance = instance._vnode.componentInstance |
|
} |
|
|
|
if (instance.$options?.devtools?.hide) { |
|
return |
|
} |
|
|
|
|
|
if (instance.fnContext && !instance.componentInstance) { |
|
const contextUid = instance.fnContext.__VUE_DEVTOOLS_UID__ |
|
let id = functionalIds.get(contextUid) |
|
if (id == null) { |
|
id = 0 |
|
} |
|
else { |
|
id++ |
|
} |
|
functionalIds.set(contextUid, id) |
|
const functionalId = `${contextUid}:functional:${id}` |
|
markFunctional(functionalId, instance) |
|
|
|
const childrenPromise = (instance.children |
|
? instance.children.map( |
|
child => child.fnContext |
|
? captureChild(child) |
|
: child.componentInstance |
|
? capture(child.componentInstance) |
|
: undefined, |
|
) |
|
|
|
: instance.componentInstance ? [capture(instance.componentInstance)] : []) |
|
|
|
|
|
const children = (await Promise.all(childrenPromise)).filter(Boolean) as ComponentTreeNode[] |
|
|
|
const treeNode = { |
|
uid: functionalId, |
|
id: functionalId, |
|
tags: [ |
|
{ |
|
label: 'functional', |
|
textColor: 0x555555, |
|
backgroundColor: 0xEEEEEE, |
|
}, |
|
], |
|
name: getInstanceName(instance), |
|
renderKey: getRenderKey(instance.key), |
|
children, |
|
hasChildren: !!children.length, |
|
inactive: false, |
|
isFragment: false, |
|
autoOpen: recursively, |
|
} |
|
return api.visitComponentTree( |
|
instance, |
|
treeNode, |
|
filter, |
|
appRecord?.options?.app, |
|
) |
|
} |
|
|
|
|
|
|
|
instance.__VUE_DEVTOOLS_UID__ = getUniqueId(instance, appRecord) |
|
|
|
|
|
if (captureIds.has(instance.__VUE_DEVTOOLS_UID__)) { |
|
return |
|
} |
|
else { |
|
captureIds.set(instance.__VUE_DEVTOOLS_UID__, undefined) |
|
} |
|
|
|
mark(instance) |
|
const name = getInstanceName(instance) |
|
|
|
const children = (await Promise.all((await getInternalInstanceChildren(instance)) |
|
.filter(child => !isBeingDestroyed(child)) |
|
.map(capture))).filter(Boolean) |
|
|
|
const ret: ComponentTreeNode = { |
|
uid: instance._uid, |
|
id: instance.__VUE_DEVTOOLS_UID__, |
|
name, |
|
renderKey: getRenderKey(instance.$vnode ? instance.$vnode.key : null), |
|
inactive: !!instance._inactive, |
|
isFragment: !!instance._isFragment, |
|
children, |
|
hasChildren: !!children.length, |
|
autoOpen: recursively, |
|
tags: [], |
|
meta: {}, |
|
} |
|
|
|
if (instance._vnode && instance._vnode.children) { |
|
const vnodeChildren = await Promise.all(flatten(instance._vnode.children.map(captureChild))) |
|
ret.children = ret.children.concat( |
|
flatten<any>(vnodeChildren).filter(Boolean), |
|
) |
|
ret.hasChildren = !!ret.children.length |
|
} |
|
|
|
|
|
const rootElements = getRootElementsFromComponentInstance(instance) |
|
const firstElement = rootElements[0] |
|
if (firstElement?.parentElement) { |
|
const parentInstance = instance.$parent |
|
const parentRootElements = parentInstance ? getRootElementsFromComponentInstance(parentInstance) : [] |
|
let el = firstElement |
|
const indexList = [] |
|
do { |
|
indexList.push(Array.from(el.parentElement.childNodes).indexOf(el)) |
|
el = el.parentElement |
|
} while (el.parentElement && parentRootElements.length && !parentRootElements.includes(el)) |
|
ret.domOrder = indexList.reverse() |
|
} |
|
else { |
|
ret.domOrder = [-1] |
|
} |
|
|
|
|
|
const consoleId = consoleBoundInstances.indexOf(instance.__VUE_DEVTOOLS_UID__) |
|
ret.consoleId = consoleId > -1 ? `$vm${consoleId}` : null |
|
|
|
|
|
const isRouterView2 = instance.$vnode?.data?.routerView |
|
if (instance._routerView || isRouterView2) { |
|
ret.isRouterView = true |
|
if (!instance._inactive && instance.$route) { |
|
const matched = instance.$route.matched |
|
const depth = isRouterView2 |
|
? instance.$vnode.data.routerViewDepth |
|
: instance._routerView.depth |
|
ret.meta.matchedRouteSegment |
|
= matched |
|
&& matched[depth] |
|
&& (isRouterView2 ? matched[depth].path : matched[depth].handler.path) |
|
} |
|
ret.tags.push({ |
|
label: `router-view${ret.meta.matchedRouteSegment ? `: ${ret.meta.matchedRouteSegment}` : ''}`, |
|
textColor: 0x000000, |
|
backgroundColor: 0xFF8344, |
|
}) |
|
} |
|
return api.visitComponentTree( |
|
instance, |
|
ret, |
|
filter, |
|
appRecord?.options?.app, |
|
) |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function mark(instance) { |
|
const refId = instance.__VUE_DEVTOOLS_UID__ |
|
if (!instanceMap.has(refId)) { |
|
instanceMap.set(refId, instance) |
|
appRecord.instanceMap.set(refId, instance) |
|
instance.$on('hook:beforeDestroy', () => { |
|
instanceMap.delete(refId) |
|
}) |
|
applyPerfHooks(api, instance, appRecord.options.app) |
|
applyTrackingUpdateHook(api, instance) |
|
} |
|
} |
|
|
|
function markFunctional(id, vnode) { |
|
const refId = vnode.fnContext.__VUE_DEVTOOLS_UID__ |
|
if (!functionalVnodeMap.has(refId)) { |
|
functionalVnodeMap.set(refId, {}) |
|
vnode.fnContext.$on('hook:beforeDestroy', () => { |
|
functionalVnodeMap.delete(refId) |
|
}) |
|
} |
|
|
|
functionalVnodeMap.get(refId)[id] = vnode |
|
|
|
appRecord.instanceMap.set(id, { |
|
__VUE_DEVTOOLS_UID__: id, |
|
__VUE_DEVTOOLS_FUNCTIONAL_LEGACY__: true, |
|
vnode, |
|
} as unknown as ComponentInstance) |
|
} |
|
|