diff --git a/packages/devtools-api/package.json b/packages/devtools-api/package.json index 068f0d380..0c150c3cb 100644 --- a/packages/devtools-api/package.json +++ b/packages/devtools-api/package.json @@ -25,8 +25,12 @@ "prepare:type": "tsup --dts-only", "stub": "tsup --watch --onSuccess 'tsup --dts-only'" }, + "peerDependencies": { + "vue": ">=3.0.0" + }, "dependencies": { - "@vue/devtools-kit": "workspace:^" + "@vue/devtools-kit": "workspace:^", + "@vue/devtools-shared": "workspace:*" }, "publishConfig": { "tag": "next" diff --git a/packages/devtools-api/src/constants.ts b/packages/devtools-api/src/constants.ts new file mode 100644 index 000000000..41f6614fa --- /dev/null +++ b/packages/devtools-api/src/constants.ts @@ -0,0 +1,8 @@ +/** + * - https://vitejs.dev/guide/env-and-mode.html#node-env-and-modes + * - https://webpack.js.org/guides/production/#specify-the-mode + * - https://www.rspack.dev/config/mode + * + * Modern bundlers are support NODE_ENV environment variable out-of the box, so we can use it to determine the environment. + */ +export const __DEV__ = typeof process !== 'undefined' && process.env.NODE_ENV !== 'production' diff --git a/packages/devtools-api/src/index.ts b/packages/devtools-api/src/index.ts index f20639d3b..4d5c98deb 100644 --- a/packages/devtools-api/src/index.ts +++ b/packages/devtools-api/src/index.ts @@ -13,3 +13,5 @@ export type { CustomCommand, CustomTab, } from '@vue/devtools-kit' + +export * from './state' diff --git a/packages/devtools-api/src/state/index.ts b/packages/devtools-api/src/state/index.ts new file mode 100644 index 000000000..def32f788 --- /dev/null +++ b/packages/devtools-api/src/state/index.ts @@ -0,0 +1,39 @@ +import { getCurrentInstance } from 'vue' +import { DEVTOOLS_API_INSPECT_STATE_KEY } from '@vue/devtools-shared' +import { __DEV__ } from '../constants' + +/** + * Register a setup state on an instance, which will be displayed in the "Component" tab. + * This is very useful when using `defineComponent` with setup returning the render function. + * + * @param state any states you want to see in the vue devtool + * + * @example + * const Component = defineComponent({ + * setup() { + * const name = ref('foo') + * inspectSetupState({ + * name, + * }) + * return h('div', name.value) + * }, + * }) + * + */ +export const inspectSetupState = __DEV__ + ? function inspectSetupState(state: Record) { + const currentInstance = getCurrentInstance() + if (!currentInstance) { + throw new Error('[Vue Devtools API]: Please using `inspectSetupState()` inside `setup()`.') + } + // @ts-expect-error internal api + currentInstance.devtoolsRawSetupState ??= {} + // @ts-expect-error internal api + const devtoolsRawSetupState = currentInstance.devtoolsRawSetupState + Object.assign(devtoolsRawSetupState, state) + devtoolsRawSetupState[DEVTOOLS_API_INSPECT_STATE_KEY] ??= [] + devtoolsRawSetupState[DEVTOOLS_API_INSPECT_STATE_KEY].push(...Object.keys(state)) + } + : (state: Record) => { + // do nothing + } diff --git a/packages/devtools-kit/src/core/component/state/process.ts b/packages/devtools-kit/src/core/component/state/process.ts index aa070ee12..5f3f5bdcd 100644 --- a/packages/devtools-kit/src/core/component/state/process.ts +++ b/packages/devtools-kit/src/core/component/state/process.ts @@ -1,4 +1,4 @@ -import { camelize } from '@vue/devtools-shared' +import { DEVTOOLS_API_INSPECT_STATE_KEY, camelize } from '@vue/devtools-shared' import type { VueAppInstance } from '../../../types' import type { InspectorState } from '../types' import { ensurePropertyExists, returnError } from '../utils' @@ -132,10 +132,21 @@ function getStateTypeAndName(info: ReturnType) { function processSetupState(instance: VueAppInstance) { const raw = instance.devtoolsRawSetupState || {} - return Object.keys(instance.setupState) + const customInspectStateKeys = raw[DEVTOOLS_API_INSPECT_STATE_KEY] || [] + + // Shallow clone to prevent mutating the original + const setupState = { + ...instance.setupState, + ...customInspectStateKeys.reduce((map, key) => { + map[key] = raw[key] + return map + }, {}), + } + + return Object.keys(setupState) .filter(key => !vueBuiltins.has(key) && key.split(/(?=[A-Z])/)[0] !== 'use') .map((key) => { - const value = returnError(() => toRaw(instance.setupState[key])) as unknown as { + const value = returnError(() => toRaw(setupState[key])) as unknown as { render: Function __asyncLoader: Function diff --git a/packages/playground/basic/src/main.ts b/packages/playground/basic/src/main.ts index 6fa633cdb..9ecdbcd6f 100644 --- a/packages/playground/basic/src/main.ts +++ b/packages/playground/basic/src/main.ts @@ -66,6 +66,11 @@ const routes: RouteRecordRaw[] = [ component: () => import('./pages/IntervalUpdate.vue'), name: 'interval-update', }, + { + path: '/inspect-custom-state', + component: () => import('./pages/InspectCustomState'), + name: 'inspect-custom-state', + }, ] const router = createRouter({ diff --git a/packages/playground/basic/src/pages/InspectCustomState.ts b/packages/playground/basic/src/pages/InspectCustomState.ts new file mode 100644 index 000000000..690d995fd --- /dev/null +++ b/packages/playground/basic/src/pages/InspectCustomState.ts @@ -0,0 +1,28 @@ +import { defineComponent, h, reactive, ref } from 'vue' +import { inspectSetupState } from '@vue/devtools-api' + +export default defineComponent({ + name: 'InspectCustomState', + setup() { + const count = ref(1) + const state = reactive({ + name: 'foo', + age: 10, + }) + + inspectSetupState({ + count, + state, + }) + + return () => h('div', [ + h('button', { + onClick() { + count.value++ + }, + }, `count: ${count.value}`), + h('div', `name: ${state.name}`), + h('div', `age: ${state.age}`), + ]) + }, +}) diff --git a/packages/shared/src/constants.ts b/packages/shared/src/constants.ts index 2a62c99c8..9d38bf6ad 100644 --- a/packages/shared/src/constants.ts +++ b/packages/shared/src/constants.ts @@ -2,3 +2,6 @@ export const VIEW_MODE_STORAGE_KEY = '__vue-devtools-view-mode__' export const VITE_PLUGIN_DETECTED_STORAGE_KEY = '__vue-devtools-vite-plugin-detected__' export const VITE_PLUGIN_CLIENT_URL_STORAGE_KEY = '__vue-devtools-vite-plugin-client-url__' export const BROADCAST_CHANNEL_NAME = '__vue-devtools-broadcast-channel__' + +// [Devtools API] +export const DEVTOOLS_API_INSPECT_STATE_KEY = '__vue-devtools-inspect-custom-setup-state' diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8aa8eaada..f07e56e27 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -381,6 +381,12 @@ importers: '@vue/devtools-kit': specifier: workspace:^ version: link:../devtools-kit + '@vue/devtools-shared': + specifier: workspace:* + version: link:../shared + vue: + specifier: '>=3.0.0' + version: 3.4.38(typescript@5.5.4) packages/devtools-kit: dependencies: