diff --git a/packages/client/package.json b/packages/client/package.json index bae4b6217a..268e05499a 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -38,6 +38,7 @@ }, "dependencies": { "@vue/devtools-api": "^7.7.2", + "@vue/devtools-kit": "^7.7.2", "@vuepress/shared": "workspace:*", "vue": "^3.5.13", "vue-router": "^4.5.0" diff --git a/packages/client/src/app.ts b/packages/client/src/app.ts index dfab9f56d2..fcacfbebb5 100644 --- a/packages/client/src/app.ts +++ b/packages/client/src/app.ts @@ -51,7 +51,7 @@ export const createVueApp: CreateVueAppFunction = async () => { // setup devtools in dev mode if (__VUEPRESS_DEV__ || __VUE_PROD_DEVTOOLS__) { - const { setupDevtools } = await import('./setupDevtools.js') + const { setupDevtools } = await import('./devtools/setupDevtools.js') setupDevtools(app, globalComputed) } diff --git a/packages/client/src/devtools/constants.ts b/packages/client/src/devtools/constants.ts new file mode 100644 index 0000000000..4e6cc168ff --- /dev/null +++ b/packages/client/src/devtools/constants.ts @@ -0,0 +1,50 @@ +import type { InspectorNodeConfig } from './types.js' + +export const PLUGIN_ID = 'org.vuejs.vuepress' +export const PLUGIN_LABEL = 'VuePress' + +export const COMPONENT_STATE_TYPE = PLUGIN_LABEL + +export const INSPECTOR_ID = PLUGIN_ID +export const INSPECTOR_LABEL = PLUGIN_LABEL + +const INSPECTOR_NODE_INTERNAL = { + id: 'INTERNAL', + label: 'Internal', + keys: ['layouts', 'routes', 'redirects'], +} as const satisfies InspectorNodeConfig + +const INSPECTOR_NODE_SITE = { + id: 'SITE', + label: 'Site', + keys: ['siteData', 'siteLocaleData'], +} as const satisfies InspectorNodeConfig + +const INSPECTOR_NODE_ROUTE = { + id: 'ROUTE', + label: 'Route', + keys: ['routePath', 'routeLocale'], +} as const satisfies InspectorNodeConfig + +const INSPECTOR_NODE_PAGE = { + id: 'PAGE', + label: 'Page', + keys: [ + 'pageData', + 'pageFrontmatter', + 'pageLang', + 'pageHead', + 'pageHeadTitle', + 'pageLayout', + 'pageComponent', + ], +} as const satisfies InspectorNodeConfig + +export const INSPECTOR_NODES = { + [INSPECTOR_NODE_INTERNAL.id]: INSPECTOR_NODE_INTERNAL, + [INSPECTOR_NODE_SITE.id]: INSPECTOR_NODE_SITE, + [INSPECTOR_NODE_ROUTE.id]: INSPECTOR_NODE_ROUTE, + [INSPECTOR_NODE_PAGE.id]: INSPECTOR_NODE_PAGE, +} + +export const INSPECTOR_STATE_SECTION_NAME = 'State' diff --git a/packages/client/src/devtools/index.ts b/packages/client/src/devtools/index.ts new file mode 100644 index 0000000000..596a5c9ef1 --- /dev/null +++ b/packages/client/src/devtools/index.ts @@ -0,0 +1 @@ +export * as DEVTOOLS from './constants.js' diff --git a/packages/client/src/setupDevtools.ts b/packages/client/src/devtools/setupDevtools.ts similarity index 55% rename from packages/client/src/setupDevtools.ts rename to packages/client/src/devtools/setupDevtools.ts index 31a8df9f49..dba3cdb4a0 100644 --- a/packages/client/src/setupDevtools.ts +++ b/packages/client/src/devtools/setupDevtools.ts @@ -1,31 +1,25 @@ import { setupDevtoolsPlugin } from '@vue/devtools-api' import type { App } from 'vue' import { watch } from 'vue' -import type { ClientData } from './types/index.js' - -const PLUGIN_ID = 'org.vuejs.vuepress' -const PLUGIN_LABEL = 'VuePress' -const PLUGIN_COMPONENT_STATE_TYPE = PLUGIN_LABEL - -const INSPECTOR_ID = PLUGIN_ID -const INSPECTOR_LABEL = PLUGIN_LABEL -const INSPECTOR_CLIENT_DATA_ID = 'client-data' -const INSPECTOR_CLIENT_DATA_LABEL = 'Client Data' - -type ClientDataKey = keyof ClientData -type ClientDataValue = ClientData[ClientDataKey] +import type { ClientData } from '../types/index.js' +import * as DEVTOOLS from './constants.js' +import type { + ClientDataKey, + ClientDataValue, + InspectorNodeConfig, +} from './types.js' export const setupDevtools = (app: App, clientData: ClientData): void => { setupDevtoolsPlugin( { // fix recursive reference app: app as never, - id: PLUGIN_ID, - label: PLUGIN_LABEL, + id: DEVTOOLS.PLUGIN_ID, + label: DEVTOOLS.PLUGIN_LABEL, packageName: '@vuepress/client', homepage: 'https://vuepress.vuejs.org', logo: 'https://vuepress.vuejs.org/images/hero.png', - componentStateTypes: [PLUGIN_COMPONENT_STATE_TYPE], + componentStateTypes: [DEVTOOLS.COMPONENT_STATE_TYPE], }, (api) => { const clientDataEntries = Object.entries(clientData) as [ @@ -39,7 +33,7 @@ export const setupDevtools = (app: App, clientData: ClientData): void => { api.on.inspectComponent((payload) => { payload.instanceData.state.push( ...clientDataEntries.map(([name, item]) => ({ - type: PLUGIN_COMPONENT_STATE_TYPE, + type: DEVTOOLS.COMPONENT_STATE_TYPE, editable: false, key: name, value: item.value, @@ -49,38 +43,47 @@ export const setupDevtools = (app: App, clientData: ClientData): void => { // setup custom inspector api.addInspector({ - id: INSPECTOR_ID, - label: INSPECTOR_LABEL, + id: DEVTOOLS.INSPECTOR_ID, + label: DEVTOOLS.INSPECTOR_LABEL, icon: 'article', }) + api.on.getInspectorTree((payload) => { - if (payload.inspectorId !== INSPECTOR_ID) return - payload.rootNodes = [ - { - id: INSPECTOR_CLIENT_DATA_ID, - label: INSPECTOR_CLIENT_DATA_LABEL, - children: clientDataKeys.map((name) => ({ - id: name, - label: name, + if (payload.inspectorId !== DEVTOOLS.INSPECTOR_ID) return + + payload.rootNodes = Object.values(DEVTOOLS.INSPECTOR_NODES).map( + (node) => ({ + id: node.id, + label: node.label, + children: node.keys.map((key: ClientDataKey) => ({ + id: key, + label: key, })), - }, - ] + }), + ) }) + api.on.getInspectorState((payload) => { - if (payload.inspectorId !== INSPECTOR_ID) return - if (payload.nodeId === INSPECTOR_CLIENT_DATA_ID) { + if (payload.inspectorId !== DEVTOOLS.INSPECTOR_ID) return + + // root nodes state + const inspectorNode = DEVTOOLS.INSPECTOR_NODES[payload.nodeId] as + | InspectorNodeConfig + | undefined + if (inspectorNode) { payload.state = { - [INSPECTOR_CLIENT_DATA_LABEL]: clientDataEntries.map( - ([name, item]) => ({ - key: name, - value: item.value, - }), - ), + [inspectorNode.label]: inspectorNode.keys.map((key) => ({ + key, + value: clientData[key].value, + })), } + return } + + // root nodes children state if (clientDataKeys.includes(payload.nodeId as ClientDataKey)) { payload.state = { - [INSPECTOR_CLIENT_DATA_LABEL]: [ + [DEVTOOLS.INSPECTOR_STATE_SECTION_NAME]: [ { key: payload.nodeId, value: clientData[payload.nodeId as ClientDataKey].value, @@ -93,7 +96,7 @@ export const setupDevtools = (app: App, clientData: ClientData): void => { // refresh the component state and inspector state watch(clientDataValues, () => { api.notifyComponentUpdate() - api.sendInspectorState(INSPECTOR_ID) + api.sendInspectorState(DEVTOOLS.INSPECTOR_ID) }) }, ) diff --git a/packages/client/src/devtools/types.ts b/packages/client/src/devtools/types.ts new file mode 100644 index 0000000000..bcc8b005fa --- /dev/null +++ b/packages/client/src/devtools/types.ts @@ -0,0 +1,10 @@ +import type { CustomInspectorNode } from '@vue/devtools-kit' +import type { ClientData } from '../types/index.js' + +export type ClientDataKey = keyof ClientData +export type ClientDataValue = ClientData[ClientDataKey] + +export interface InspectorNodeConfig + extends Pick { + keys: ClientDataKey[] +} diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index 8b0799706f..32ca026c74 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -1,5 +1,6 @@ export * from './components/index.js' export * from './composables/index.js' +export * from './devtools/index.js' export * from './router/index.js' export * from './resolvers.js' export type * from './types/index.js' diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 074b649394..ea281d0abc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -308,6 +308,9 @@ importers: '@vue/devtools-api': specifier: ^7.7.2 version: 7.7.2 + '@vue/devtools-kit': + specifier: ^7.7.2 + version: 7.7.2 '@vuepress/shared': specifier: workspace:* version: link:../shared