Skip to content
This repository was archived by the owner on Jan 6, 2024. It is now read-only.

Commit 3e730ae

Browse files
feat: add component inspector to support component tree navigable (#200)
--------- Co-authored-by: webfansplz <[email protected]>
1 parent cd9e534 commit 3e730ae

File tree

19 files changed

+265
-81
lines changed

19 files changed

+265
-81
lines changed

packages/client/components/ComponentTreeNode.vue

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
<script setup lang="ts">
2+
import scrollIntoView from 'scroll-into-view-if-needed'
3+
24
/* eslint-disable @typescript-eslint/consistent-type-imports */
35
import type { ComponentTreeNode } from '~/types'
46
@@ -11,6 +13,23 @@ const props = withDefaults(defineProps<{
1113
1214
const { isSelected, select, isExpanded, toggleExpand } = useComponent(props.data)
1315
const { highlight, unhighlight } = useHighlightComponent(props.data)
16+
17+
const toggleEl = ref<HTMLElement>()
18+
19+
function autoScroll() {
20+
if (isSelected.value && toggleEl.value) {
21+
const el = toggleEl.value
22+
scrollIntoView(el, {
23+
scrollMode: 'if-needed',
24+
block: 'center',
25+
behavior: 'smooth',
26+
inline: 'nearest',
27+
})
28+
}
29+
}
30+
31+
watch(isSelected, () => autoScroll())
32+
watch(toggleEl, () => autoScroll())
1433
</script>
1534

1635
<template>
@@ -24,7 +43,7 @@ const { highlight, unhighlight } = useHighlightComponent(props.data)
2443
@mouseover="highlight"
2544
@mouseleave="unhighlight"
2645
>
27-
<h3 vue-block-title @click="data.hasChildren ? toggleExpand(data.id) : () => {}">
46+
<h3 ref="toggleEl" vue-block-title @click="data.hasChildren ? toggleExpand(data.id) : () => {}">
2847
<VDExpandIcon v-if="data.hasChildren" :value="isExpanded" />
2948
<i v-else inline-block h-6 w-6 />
3049
<span

packages/client/composables/component.ts

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { ComponentInternalInstance } from 'vue'
2-
import { InstanceMap, getInstanceDetails, getInstanceName, getInstanceOrVnodeRect, getRootElementsFromComponentInstance } from '~/logic/components'
2+
import { getInstanceName } from '@vite-plugin-vue-devtools/core'
3+
import { InstanceMap, getInstanceDetails, getInstanceOrVnodeRect, getRootElementsFromComponentInstance } from '~/logic/components'
34
import { useDevToolsClient } from '~/logic/client'
45
import type { ComponentTreeNode } from '~/types'
56

@@ -13,23 +14,29 @@ const expandedMap = ref<Record<ComponentTreeNode['id'], boolean>>({
1314

1415
export const selectedComponent = ref<ComponentInternalInstance>()
1516
export const selectedComponentState = shallowRef<Record<string, any>[]>([])
17+
18+
export function selectComponentTreeNode(data: ComponentTreeNode) {
19+
selected.value = data.id
20+
selectedComponentName.value = data.name
21+
// TODO (Refactor): get instance state way
22+
selectedComponentState.value = InstanceMap.get(data.id)
23+
selectedComponentNode.value = data
24+
// selectedComponent.value = instance.instance
25+
// selectedComponentState.value = getInstanceState(instance.instance!)
26+
}
27+
28+
export function setExpanded(id: string, expanded: boolean) {
29+
expandedMap.value[id] = expanded
30+
}
31+
1632
export function useComponent(instance: ComponentTreeNode & { instance?: ComponentInternalInstance }) {
17-
function select(data: ComponentTreeNode) {
18-
selected.value = data.id
19-
selectedComponentName.value = data.name
20-
// TODO (Refactor): get instance state way
21-
selectedComponentState.value = InstanceMap.get(data.id)
22-
selectedComponentNode.value = data
23-
// selectedComponent.value = instance.instance
24-
// selectedComponentState.value = getInstanceState(instance.instance!)
25-
}
2633
function toggleExpand(id: string) {
2734
expandedMap.value[id] = !expandedMap.value[id]
2835
}
2936
const isSelected = computed(() => selected.value === instance.id)
3037
const isExpanded = computed(() => expandedMap.value[instance.id])
3138

32-
return { isSelected, select, isExpanded, toggleExpand }
39+
return { isSelected, select: selectComponentTreeNode, isExpanded, toggleExpand }
3340
}
3441

3542
export function useHighlightComponent(node: ComponentTreeNode): {

packages/client/logic/client.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ const client = ref<VueDevtoolsHostClient>({
99
componentInspector: {
1010
highlight: () => {},
1111
unHighlight: () => {},
12-
scrollToComponent: () => {},
12+
scrollToComponent: () => { },
13+
startInspect: () => { },
14+
stopInspect: () => { },
1315
},
1416
rerenderHighlight: {
1517
updateInfo: () => {},

packages/client/logic/components/data.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/* eslint-disable @typescript-eslint/indent */
22
import type { ComponentInternalInstance } from 'vue'
3-
import { camelize, getInstanceName, getUniqueComponentId, returnError } from './util'
3+
import { getInstanceName } from '@vite-plugin-vue-devtools/core'
4+
import { camelize, getUniqueComponentId, returnError } from './util'
45

56
const vueBuiltins = [
67
'nextTick',

packages/client/logic/components/filter.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { ComponentInternalInstance } from 'vue'
2-
import { classify, getInstanceName, kebabize } from './util'
2+
import { getInstanceName } from '@vite-plugin-vue-devtools/core'
3+
import { classify, kebabize } from './util'
34

45
export class ComponentFilter {
56
private filter: string
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
11
export { ComponentWalker, InstanceMap } from './tree'
22
export { getInstanceState, processSetupState, getInstanceDetails, getSetupStateInfo } from './data'
33
export { getInstanceOrVnodeRect, getRootElementsFromComponentInstance } from './el'
4-
export { getInstanceName } from './util'

packages/client/logic/components/tree.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
import type { ComponentInternalInstance, SuspenseBoundary } from 'vue'
2-
import { getInstanceName, getRenderKey, getUniqueComponentId, isBeingDestroyed, isFragment } from './util'
2+
import { getInstanceName } from '@vite-plugin-vue-devtools/core'
3+
import { getRenderKey, getUniqueComponentId, isBeingDestroyed, isFragment } from './util'
34
import { ComponentFilter } from './filter'
45
import { getRootElementsFromComponentInstance } from './el'
56
import { getInstanceState } from './data'
7+
import type { ComponentTreeNode } from '~/types'
68

79
export const InstanceMap = new Map()
10+
export const UidToTreeNodeMap = new Map<number, ComponentTreeNode>()
11+
812
export class ComponentWalker {
913
maxDepth: number
1014
recursively: boolean
@@ -27,7 +31,7 @@ export class ComponentWalker {
2731

2832
getComponentParents(instance: ComponentInternalInstance) {
2933
this.captureIds = new Map()
30-
const parents = []
34+
const parents: ComponentInternalInstance[] = []
3135
this.captureId(instance)
3236
let parent = instance
3337
// eslint-disable-next-line no-cond-assign
@@ -205,6 +209,7 @@ export class ComponentWalker {
205209
// }
206210

207211
InstanceMap.set(treeNode.id, getInstanceState(instance))
212+
UidToTreeNodeMap.set(treeNode.uid, treeNode)
208213
treeNode.instance = instance
209214

210215
return treeNode

packages/client/logic/components/util.ts

Lines changed: 0 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -9,53 +9,6 @@ export function isFragment(instance: ComponentInternalInstance) {
99
return Fragment === instance.subTree?.type
1010
}
1111

12-
/**
13-
* Get the appropriate display name for an instance.
14-
*
15-
* @param {Vue} instance
16-
* @return {String}
17-
*/
18-
export function getInstanceName(instance: any) {
19-
const name = getComponentTypeName(instance.type || {})
20-
if (name)
21-
return name
22-
if (instance.root === instance)
23-
return 'Root'
24-
for (const key in instance.parent?.type?.components) {
25-
if (instance.parent.type.components[key] === instance.type)
26-
return saveComponentName(instance, key)
27-
}
28-
29-
for (const key in instance.appContext?.components) {
30-
if (instance.appContext.components[key] === instance.type)
31-
return saveComponentName(instance, key)
32-
}
33-
34-
const fileName = getComponentFileName(instance.type || {})
35-
if (fileName)
36-
return fileName
37-
38-
return 'Anonymous Component'
39-
}
40-
41-
function saveComponentName(instance: ComponentInternalInstance, key: string) {
42-
return key
43-
}
44-
45-
function getComponentTypeName(options: any) {
46-
return options.name || options._componentTag || options.__vdevtools_guessedName || options.__name
47-
}
48-
49-
export function getComponentFileName(options: any) {
50-
const file = options.__file // injected by vite
51-
// TODO: classify
52-
if (file) {
53-
const filename = options.__file?.match(/\/?([^/]+?)(\.[^/.]+)?$/)?.[1]
54-
return filename ?? file
55-
}
56-
// return classify(basename(file, '.vue'))
57-
}
58-
5912
/**
6013
* Returns a devtools unique id for instance.
6114
* @param {Vue} instance

packages/client/logic/timeline.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { nanoid } from 'nanoid'
2-
import { getComponentFileName } from './components/util'
2+
import { getComponentFileName } from '@vite-plugin-vue-devtools/core'
33
import { useDevToolsClient } from './client'
44

55
interface TimelineLayer {

packages/client/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
"json-editor-vue": "^0.10.6",
3838
"minimatch": "^9.0.3",
3939
"nanoid": "^4.0.2",
40+
"scroll-into-view-if-needed": "^3.0.10",
4041
"splitpanes": "^3.1.5",
4142
"vanilla-jsoneditor": "^0.17.8",
4243
"vite-hot-client": "^0.2.1",

0 commit comments

Comments
 (0)