|
| 1 | +--- |
| 2 | +outline: deep |
| 3 | +--- |
| 4 | + |
| 5 | +# Dock System |
| 6 | + |
| 7 | +Dock entries are the primary way for users to interact with your DevTools integration. They appear as clickable items in the DevTools dock (similar to macOS Dock), and when activated, they can display: |
| 8 | +- An iframe panel with your custom UI |
| 9 | +- A custom-rendered panel directly in the user's app |
| 10 | +- An action button that triggers client-side scripts |
| 11 | + |
| 12 | +The dock can be presented as a floating panel inside the user's app, a sidebar in browser extension mode, or in standalone mode. "Dock" refers to macOS's Dock, where you switch between different applications by clicking on them. |
| 13 | + |
| 14 | +## Register A Dock Entry |
| 15 | + |
| 16 | +To register a dock entry, you can use the `ctx.docks.register` method to add a new dock entry. The easiest approach is to register a iframe-based dock entry. Here we use VueUse's docs as an example: |
| 17 | + |
| 18 | +```ts {6-12} |
| 19 | +export default function VueUseDevToolsDocs(): Plugin { |
| 20 | + return { |
| 21 | + name: 'vueuse:devtools:docs', |
| 22 | + devtools: { |
| 23 | + setup(ctx) { |
| 24 | + ctx.docks.register({ |
| 25 | + id: 'vueuse:docs', |
| 26 | + title: 'VueUse', |
| 27 | + icon: 'https://vueuse.org/favicon.svg', |
| 28 | + type: 'iframe', |
| 29 | + url: 'https://vueuse.org', |
| 30 | + }) |
| 31 | + }, |
| 32 | + } |
| 33 | + } |
| 34 | +} |
| 35 | +``` |
| 36 | + |
| 37 | +The more practical usage is to build a local webpage to draw your own views and handle interactions. This allows you to use any frontend framework (Vue, React, Svelte, etc.) to build your DevTools UI. |
| 38 | + |
| 39 | +For example, if you host a local custom view at `/.my-app`, you can register it as a dock entry like this: |
| 40 | + |
| 41 | +```ts {6} |
| 42 | +ctx.docks.register({ |
| 43 | + id: 'my-app', |
| 44 | + title: 'My App', |
| 45 | + icon: 'https://my-app.com/logo.svg', |
| 46 | + type: 'iframe', |
| 47 | + url: '/.my-app', |
| 48 | +}) |
| 49 | +``` |
| 50 | + |
| 51 | +DevTools can also handle the page hosting for you. If you have your built SPA page under `./dist/client`, you can register it as a dock entry like this: |
| 52 | + |
| 53 | +```ts {2} |
| 54 | +const pathClientDist = fileURLToPath(new URL('../dist/client', import.meta.url)) |
| 55 | +ctx.views.hostStatic('/.my-app', pathClientDist) |
| 56 | +ctx.docks.register({ |
| 57 | + id: 'my-app', |
| 58 | + title: 'My App', |
| 59 | + icon: 'https://my-app.com/logo.svg', |
| 60 | + type: 'iframe', |
| 61 | + url: '/.my-app', |
| 62 | +}) |
| 63 | +``` |
| 64 | + |
| 65 | +This way DevTools will handle the dev server middleware to host the static files for you, and also copy the static files to the dist directory when in production build. |
| 66 | + |
| 67 | +## Register An Action |
| 68 | + |
| 69 | +Instead of an iframe panel, sometimes you might want to register an action button that triggers client-side scripts. This is useful when you want to: |
| 70 | +- Enable temporary inspector tools (e.g., DOM inspector, component inspector) |
| 71 | +- Toggle features in the user's app |
| 72 | +- Trigger one-time actions without showing a UI panel |
| 73 | + |
| 74 | +For example, you might want to enable a temporary inspector tool to inspect the DOM of the client app and have the button appear in the DevTools dock. |
| 75 | + |
| 76 | +```ts {6} |
| 77 | +ctx.docks.register({ |
| 78 | + id: 'dom-inspector', |
| 79 | + title: 'DOM Inspector', |
| 80 | + type: 'action', |
| 81 | + action: { |
| 82 | + importFrom: 'vite-plugin-my-inspector/vite-devtools-action', |
| 83 | + importName: 'default', |
| 84 | + }, |
| 85 | + icon: 'ph:cursor-duotone', |
| 86 | +}) |
| 87 | +``` |
| 88 | + |
| 89 | +In your package, export the sub entrypoint for the action. The action script runs in the user's app context, giving you access to the app's DOM, state, and APIs. |
| 90 | + |
| 91 | +```ts [src/vite-devtools-action.ts] |
| 92 | +import type { DevToolsClientScriptContext } from '@vitejs/devtools-kit/client' |
| 93 | + |
| 94 | +export default function setupDevToolsAction(ctx: DevToolsClientScriptContext) { |
| 95 | + // Setup action will only execute when the entry is activated the first time |
| 96 | + |
| 97 | + // Register listeners to handle events from the dock system |
| 98 | + ctx.current.events.on('entry:activated', () => { |
| 99 | + // Your action logic here |
| 100 | + console.log('DOM inspector started!') |
| 101 | + |
| 102 | + // Example: Enable DOM inspection |
| 103 | + document.body.style.cursor = 'crosshair' |
| 104 | + |
| 105 | + // You can also communicate with the server using [RPC](./rpc) |
| 106 | + }) |
| 107 | + |
| 108 | + ctx.current.events.on('entry:deactivated', () => { |
| 109 | + // Cleanup when the entry is deactivated |
| 110 | + document.body.style.cursor = '' |
| 111 | + }) |
| 112 | +} |
| 113 | +``` |
| 114 | + |
| 115 | +And in your package.json, you can export the sub entrypoint: |
| 116 | + |
| 117 | +```json [package.json] |
| 118 | +{ |
| 119 | + "name": "vite-plugin-my-inspector", |
| 120 | + "exports": { |
| 121 | + "./vite-devtools-action": "./dist/vite-devtools-action.mjs" |
| 122 | + } |
| 123 | +} |
| 124 | +``` |
| 125 | + |
| 126 | +That's it! When users install your plugin, they can click your action button in the dock. When activated for the first time, the action script will be loaded and executed in the user's app context, allowing you to interact with their application directly. |
| 127 | + |
| 128 | +## Register Custom Renderer |
| 129 | + |
| 130 | +If you want to render your own views directly in the user's app instead of using an iframe, you can register a dock entry with a custom renderer. This gives you full control over the DOM and allows you to use any framework or vanilla JavaScript to build your UI. |
| 131 | + |
| 132 | +```ts {6} |
| 133 | +ctx.docks.register({ |
| 134 | + id: 'my-custom-view', |
| 135 | + title: 'My Custom View', |
| 136 | + type: 'custom-render', |
| 137 | + renderer: { |
| 138 | + importFrom: 'vite-plugin-my-inspector/vite-devtools-renderer', |
| 139 | + importName: 'default', |
| 140 | + }, |
| 141 | + icon: 'ph:file-duotone' |
| 142 | +}) |
| 143 | +``` |
| 144 | + |
| 145 | +Similar to [Action](#register-an-action), we write the renderer logic in a client script that we export as a sub export. The renderer gives you a DOM element (`panel`) that you can mount your UI to: |
| 146 | + |
| 147 | +```ts [src/vite-devtools-renderer.ts] |
| 148 | +import type { DevToolsClientScriptContext } from '@vitejs/devtools-kit/client' |
| 149 | + |
| 150 | +export default function setupDevToolsCustomRenderer(ctx: DevToolsClientScriptContext) { |
| 151 | + // Setup will only execute when the entry is activated the first time |
| 152 | + |
| 153 | + // The 'dom:panel:mounted' event is called once when the panel DOM is ready |
| 154 | + // The DOM will be preserved when switching between dock entries |
| 155 | + ctx.current.events.on('dom:panel:mounted', (panel) => { |
| 156 | + // Render your custom DOM and mount to `panel` |
| 157 | + // You can use vanilla JavaScript or any framework you prefer |
| 158 | + const el = document.createElement('div') |
| 159 | + el.style.padding = '16px' |
| 160 | + el.innerHTML = '<h2>Hello from custom render dock!</h2>' |
| 161 | + |
| 162 | + const btn = document.createElement('button') |
| 163 | + btn.textContent = 'Click me' |
| 164 | + btn.onclick = async () => { |
| 165 | + // You can communicate with the server using [RPC](./rpc) |
| 166 | + // const rpc = ctx.current.rpc |
| 167 | + // const data = await rpc.call('my-plugin:get-data', 'some-id') |
| 168 | + console.log('Button clicked!') |
| 169 | + } |
| 170 | + el.appendChild(btn) |
| 171 | + panel.appendChild(el) |
| 172 | + |
| 173 | + // You can also use frameworks like Vue, React, etc. |
| 174 | + // import { createApp } from 'vue' |
| 175 | + // createApp(MyComponent).mount(panel) |
| 176 | + }) |
| 177 | + |
| 178 | + // Cleanup when the entry is deactivated (optional) |
| 179 | + ctx.current.events.on('entry:deactivated', () => { |
| 180 | + // Cleanup logic if needed |
| 181 | + }) |
| 182 | +} |
| 183 | +``` |
| 184 | + |
| 185 | +**Note**: The panel DOM is preserved when users switch between dock entries, so you only need to set up your UI once in the `dom:panel:mounted` event. If you need to update the UI based on server state, use [RPC calls](./rpc#call-server-functions-from-client) to fetch fresh data. |
0 commit comments