Skip to content

Commit

Permalink
feat: add node tags for component tree (#655)
Browse files Browse the repository at this point in the history
  • Loading branch information
webfansplz authored Oct 30, 2024
1 parent b549a77 commit 82509c9
Show file tree
Hide file tree
Showing 9 changed files with 56 additions and 26 deletions.
29 changes: 27 additions & 2 deletions packages/applet/src/components/tree/TreeViewer.vue
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
<script setup lang="ts">
import type { ComponentTreeNode, InspectorTree } from '@vue/devtools-kit'
import { UNDEFINED } from '@vue/devtools-kit'
import { vTooltip } from '@vue/devtools-ui'
import NodeTag from '~/components/basic/NodeTag.vue'
import ToggleExpanded from '~/components/basic/ToggleExpanded.vue'
import ComponentTreeViewer from '~/components/tree/TreeViewer.vue'
import { useSelect } from '~/composables/select'
import { useToggleExpanded } from '~/composables/toggle-expanded'
withDefaults(defineProps<{
const props = withDefaults(defineProps<{
data: ComponentTreeNode[] | InspectorTree[]
depth: number
withTag: boolean
Expand Down Expand Up @@ -54,8 +55,32 @@ function select(id: string) {
<span font-state-field text-3.5>
<span v-if="withTag" class="text-gray-400 dark:text-gray-600 group-hover:(text-white op50) [.active_&]:(op50 text-white!)">&lt;</span>
<span group-hover:text-white class="ws-nowrap [.active_&]:(text-white)">{{ normalizeLabel(item) }}</span>
<!-- @vue-expect-error skip type check -->
<span
v-if="(item.renderKey === 0 || !!item.renderKey) && item.renderKey !== UNDEFINED"
class="text-xs opacity-50"
:class="{
'opacity-100': selectedNodeId === item.id,
}"
>
<span :class="[selectedNodeId === item.id ? 'text-purple-200' : 'text-purple-500']"> key</span>=<span>{{ (item as ComponentTreeNode).renderKey }}</span>
</span>
<span v-if="withTag" class="text-gray-400 dark:text-gray-600 group-hover:(text-white op50) [.active_&]:(op50 text-white!)">&gt;</span>
</span>
<span
v-if="(item as ComponentTreeNode).isFragment"
v-tooltip="'Has multiple root DOM nodes'"
class="ml-2 rounded-sm bg-blue-400 px-1 text-[0.75rem] leading-snug dark:bg-blue-800"
>
fragment
</span>
<span
v-if="(item as ComponentTreeNode).inactive"
v-tooltip="'Currently inactive but not destroyed'"
class="ml-2 rounded-sm bg-gray-500 px-1 text-[0.75rem] leading-snug"
>
inactive
</span>
<NodeTag v-for="(_item, _index) in item.tags" :key="_index" :tag="_item" />
</div>
<div
Expand Down
6 changes: 5 additions & 1 deletion packages/devtools-kit/src/api/v6/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { DevtoolsContext } from '../../ctx'
import type { App, ComponentBounds, ComponentInstance, CustomInspectorOptions, DevToolsPlugin, TimelineEventOptions, TimelineLayerOptions } from '../../types'
import { getPluginSettings, initPluginSettings } from '../../core/plugin/plugin-settings'

import { DevToolsContextHookKeys, DevToolsV6PluginAPIHookKeys, DevToolsV6PluginAPIHooks } from '../../ctx/hook'
import { DevToolsContextHookKeys, DevToolsV6PluginAPIHookKeys, DevToolsV6PluginAPIHookPayloads, DevToolsV6PluginAPIHooks } from '../../ctx/hook'
import { getActiveInspectors } from '../../ctx/inspector'
import { devtoolsHooks } from '../../hook'
import { DevToolsHooks } from '../../types'
Expand Down Expand Up @@ -96,6 +96,10 @@ export class DevToolsV6PluginAPI {
this.hooks.callHook(DevToolsContextHookKeys.CUSTOM_INSPECTOR_SELECT_NODE, { inspectorId, nodeId, plugin: this.plugin })
}

visitComponentTree(payload: DevToolsV6PluginAPIHookPayloads[DevToolsV6PluginAPIHookKeys.VISIT_COMPONENT_TREE]) {
return this.hooks.callHook(DevToolsV6PluginAPIHookKeys.VISIT_COMPONENT_TREE, payload)
}

// timeline
now(): number {
return Date.now()
Expand Down
3 changes: 2 additions & 1 deletion packages/devtools-kit/src/core/app/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ function getAppRecordId(app: VueAppInstance['appContext']['app'], defaultId?: st
return id
}

export function createAppRecord(app: VueAppInstance['appContext']['app']): AppRecord {
export function createAppRecord(app: VueAppInstance['appContext']['app'], types: Record<string, string | symbol>): AppRecord {
const rootInstance = getAppRootInstance(app)
if (rootInstance) {
appRecordInfo.id++
Expand All @@ -56,6 +56,7 @@ export function createAppRecord(app: VueAppInstance['appContext']['app']): AppRe
const record: AppRecord = {
id,
name,
types,
instanceMap: new Map(),
perfGroupIds: new Map(),
rootInstance,
Expand Down
19 changes: 11 additions & 8 deletions packages/devtools-kit/src/core/component/tree/walker.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { SuspenseBoundary, VNode } from 'vue'
import type { DevToolsPluginAPI } from '../../../api'
import type { ComponentTreeNode, VueAppInstance } from '../../../types'
import type { ComponentFilter } from './filter'
// import { devtoolsAppRecords, devtoolsContext } from '../../../state'
Expand All @@ -11,19 +12,22 @@ interface ComponentWalkerOptions {
filterText?: string
maxDepth: number | null
recursively: boolean
api: InstanceType<typeof DevToolsPluginAPI>
}

export class ComponentWalker {
maxDepth: number | null
recursively: boolean
componentFilter: InstanceType<typeof ComponentFilter>
api: InstanceType<typeof DevToolsPluginAPI>
// Dedupe instances (Some instances may be both on a component and on a child abstract/functional component)
private captureIds: Map<string, undefined> = new Map()
constructor(options: ComponentWalkerOptions) {
const { filterText = '', maxDepth, recursively } = options
const { filterText = '', maxDepth, recursively, api } = options
this.componentFilter = createComponentFilter(filterText)
this.maxDepth = maxDepth
this.recursively = recursively
this.api = api
}

public getComponentTree(instance: VueAppInstance): Promise<ComponentTreeNode[]> {
Expand Down Expand Up @@ -157,13 +161,12 @@ export class ComponentWalker {
this.mark(instance, true)
}

// @TODO: impl
// devtoolsContext.api.visitComponentTree({
// treeNode,
// componentInstance: instance,
// app: instance.appContext.app,
// filter: this.componentFilter.filter,
// })
this.api.visitComponentTree({
treeNode,
componentInstance: instance,
app: instance.appContext.app,
filter: this.componentFilter.filter,
})
return treeNode
}

Expand Down
14 changes: 5 additions & 9 deletions packages/devtools-kit/src/core/component/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type { AppRecord, VueAppInstance } from '../../../types'
import { basename, classify } from '@vue/devtools-shared'
import { Fragment } from '../../../shared/stub-vue'

function getComponentTypeName(options: VueAppInstance['type']) {
const name = options.name || options._componentTag || options.__VUE_DEVTOOLS_COMPONENT_GUSSED_NAME__ || options.__name
Expand Down Expand Up @@ -56,14 +55,11 @@ export async function getComponentId(options: { app: VueAppInstance, uid: number

export function isFragment(instance: VueAppInstance) {
const subTreeType = instance.subTree?.type
// TODO: resolve static type, the subTree.children of static type will be a string instead of children like Fragment
// return subTreeType === Fragment || (
// subTreeType === Static
// // @ts-expect-error vue internal type
// ? instance.subTree.staticCount > 1
// : false
// )
return subTreeType === Fragment
const appRecord = getAppRecord(instance)
if (appRecord) {
return appRecord?.types?.Fragment === subTreeType
}
return false
}

export function isBeingDestroyed(instance: VueAppInstance) {
Expand Down
4 changes: 2 additions & 2 deletions packages/devtools-kit/src/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,8 @@ export function initDevTools() {
})

// create app record
hook.on.vueAppInit(async (app, version) => {
const appRecord = createAppRecord(app)
hook.on.vueAppInit(async (app, version, types) => {
const appRecord = createAppRecord(app, types)
const normalizedAppRecord = {
...appRecord,
app,
Expand Down
1 change: 1 addition & 0 deletions packages/devtools-kit/src/core/plugin/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export function createComponentsDevToolsPlugin(app: App): [PluginDescriptor, Plu
// @TODO: should make this configurable?
maxDepth: 100,
recursively: false,
api,
})
// @ts-expect-error skip type @TODO
payload.rootNodes = await walker.getComponentTree(instance)
Expand Down
4 changes: 2 additions & 2 deletions packages/devtools-kit/src/hook/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,11 @@ export function createDevToolsHook(): DevToolsHook {
export function subscribeDevToolsHook() {
const hook = target.__VUE_DEVTOOLS_GLOBAL_HOOK__ as DevToolsHook
// app init hook
hook.on<DevToolsEvent[DevToolsHooks.APP_INIT]>(DevToolsHooks.APP_INIT, (app, version) => {
hook.on<DevToolsEvent[DevToolsHooks.APP_INIT]>(DevToolsHooks.APP_INIT, (app, version, types) => {
if (app?._instance?.type?.devtools?.hide)
return

devtoolsHooks.callHook(DevToolsHooks.APP_INIT, app, version)
devtoolsHooks.callHook(DevToolsHooks.APP_INIT, app, version, types)
})

hook.on<DevToolsEvent[DevToolsHooks.APP_UNMOUNT]>(DevToolsHooks.APP_UNMOUNT, (app) => {
Expand Down
2 changes: 1 addition & 1 deletion packages/devtools-kit/src/types/hook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export enum DevToolsHooks {
}

export interface DevToolsEvent {
[DevToolsHooks.APP_INIT]: (app: VueAppInstance['appContext']['app'], version: string) => void | Promise<void>
[DevToolsHooks.APP_INIT]: (app: VueAppInstance['appContext']['app'], version: string, types: Record<string, string | symbol>) => void | Promise<void>
[DevToolsHooks.APP_CONNECTED]: () => void
[DevToolsHooks.APP_UNMOUNT]: (app: VueAppInstance['appContext']['app']) => void | Promise<void>
[DevToolsHooks.COMPONENT_ADDED]: (app: HookAppInstance, uid: number, parentUid: number, component: VueAppInstance) => void | Promise<void>
Expand Down

0 comments on commit 82509c9

Please sign in to comment.