Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 20 additions & 2 deletions docs/guide/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ pnpm dev

Open your app in the browser; the floating docks appear in the corner.

The `visibility` option sets the starting mode. The default `'normal'` shows the docks immediately. `'passive'` keeps them out of the way and prints a console hint to reveal them with <kbd>Shift</kbd> + <kbd>Alt</kbd> + <kbd>D</kbd> (<kbd>⇧</kbd> <kbd>⌥</kbd> <kbd>D</kbd> on macOS); revealing once is remembered in the project's `node_modules`, so later dev sessions on this machine open straight into the docks, and the "Hide DevTools" command returns to passive mode. `'hidden'` also starts hidden but never remembers — the shortcut reveals the docks for the current session only.
The `embeddedVisibility` option sets the starting mode. The default `'normal'` shows the docks immediately. `'passive'` keeps them out of the way and prints a console hint to reveal them with <kbd>Shift</kbd> + <kbd>Alt</kbd> + <kbd>D</kbd> (<kbd>⇧</kbd> <kbd>⌥</kbd> <kbd>D</kbd> on macOS); revealing once persists per-origin in the browser, so later sessions on this browser open straight into the docks, and the "Hide DevTools" command returns to passive mode. `'hidden'` also starts hidden but never remembers — the shortcut reveals the docks for the current session only.

```ts [vite.config.ts] twoslash
import { DevTools } from '@vitejs/devtools'
Expand All @@ -108,7 +108,25 @@ import { defineConfig } from 'vite'
export default defineConfig({
plugins: [
DevTools({
visibility: 'passive',
embeddedVisibility: 'passive',
}),
],
})
```

The `dockPreferences` option seeds the dock bar's first-run layout — category ordering, the floating dock's inline-item capacity, and the default float/edge mode and position. Each is a user-overridable preference, so the visitor's own choice wins from then on.

```ts [vite.config.ts] twoslash
import { DevTools } from '@vitejs/devtools'
import { defineConfig } from 'vite'

export default defineConfig({
plugins: [
DevTools({
dockPreferences: {
defaultMode: 'edge',
defaultPosition: 'bottom',
},
}),
],
})
Expand Down
5 changes: 4 additions & 1 deletion packages/core/src/node/build-static.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/* eslint-disable no-console */

import type { ViteDevToolsNodeContext } from '@vitejs/devtools-kit'
import type { ViteDevToolsUiOptions } from './ui'
import { existsSync } from 'node:fs'
import fs from 'node:fs/promises'
import { DOCK_RENDERERS_STATE_KEY } from '@devframes/hub/constants'
Expand All @@ -22,6 +23,8 @@ export interface BuildStaticOptions {
context: ViteDevToolsNodeContext
outDir: string
withApp?: boolean
/** Reference-UI options forwarded to `createUi`. */
ui?: ViteDevToolsUiOptions
}

export async function buildStaticDevTools(options: BuildStaticOptions): Promise<void> {
Expand All @@ -36,7 +39,7 @@ export async function buildStaticDevTools(options: BuildStaticOptions): Promise<
// Bake the branded `@devframes/hub-ui` client into the snapshot: the
// standalone viewer SPA, its embedded bootstrap, and the UI-owned assets
// (e.g. `branding.json`) — the same `ui` slot the hub serves in dev.
const ui = createViteDevToolsUi()
const ui = createViteDevToolsUi(options.ui)
if (ui.viewer)
await fs.cp(ui.viewer.distDir, devToolsRoot, { recursive: true })
if (ui.embedded)
Expand Down
6 changes: 0 additions & 6 deletions packages/core/src/node/plugins/__tests__/injection.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,5 @@ describe('devToolsInjection', () => {
expect(tag.injectTo).toBe('body')
expect(tag.children).toContain(`${DEVTOOLS_MOUNT_PATH}embedded.js`)
expect(tag.children).toContain('document.body.appendChild(s)')
expect(tag.children).toContain(`s.dataset.visibility = "normal"`)
})

it('forwards the visibility hint to the embedded bootstrap', () => {
const tag = injectedTags(DevToolsInjection({ visibility: 'passive' }))[0]!
expect(tag.children).toContain(`s.dataset.visibility = "passive"`)
})
})
5 changes: 4 additions & 1 deletion packages/core/src/node/plugins/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@

import type { ViteDevToolsNodeContext } from '@vitejs/devtools-kit'
import type { Plugin, ResolvedConfig } from 'vite'
import type { ViteDevToolsUiOptions } from '../ui'
import { colors as c } from 'devframe/utils/colors'
import { resolve } from 'pathe'
import { MARK_NODE } from '../constants'

export interface DevToolsBuildOptions {
outDir?: string
/** Reference-UI options forwarded to the static snapshot's `createUi`. */
ui?: ViteDevToolsUiOptions
}

export function DevToolsBuild(options: DevToolsBuildOptions = {}): Plugin {
Expand Down Expand Up @@ -35,7 +38,7 @@ export function DevToolsBuild(options: DevToolsBuildOptions = {}): Plugin {
: resolve(resolvedConfig.root, resolvedConfig.build.outDir)

const { buildStaticDevTools } = await import('../build-static')
await buildStaticDevTools({ context, outDir, withApp: true })
await buildStaticDevTools({ context, outDir, withApp: true, ui: options.ui })
},
}
}
34 changes: 24 additions & 10 deletions packages/core/src/node/plugins/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Plugin } from 'vite'
import type { DevToolsVisibility } from './injection'
import type { ViteDevToolsUiOptions } from '../ui'
import { DevToolsBuild } from './build'
import { DevToolsBuiltin } from './builtin'
import { DevToolsInjection } from './injection'
Expand All @@ -16,19 +16,30 @@ export interface DevToolsOptions {
builtinDevTools?: boolean

/**
* Initial visibility of the injected overlay.
* How the embedded floating dock reveals itself on a fresh page.
*
* - `'normal'` — show the docks immediately.
* - `'passive'` — the floating docks stay hidden and a console hint invites
* the developer to reveal them with a keyboard shortcut. Activating once
* persists a flag in the project's `node_modules`, so later dev sessions on
* this machine boot straight into normal mode.
* the developer to reveal them with a keyboard shortcut. Revealing once
* persists per-origin, so later dev sessions on this browser start shown;
* the "Hide DevTools" command returns to passive mode.
* - `'hidden'` — always keep the docks hidden; the shortcut reveals them for
* the current session only, without remembering the choice.
*
* Seeds a user-overridable preference published as
* `ConnectionMeta.configs.ui.embeddedVisibility`.
*
* @default 'normal'
*/
visibility?: DevToolsVisibility
embeddedVisibility?: ViteDevToolsUiOptions['embeddedVisibility']

/**
* Dock-bar rendering preferences — category ordering, floating-dock
* inline-item capacity, and the first-run float/edge mode and position.
* Each seeds a user-overridable preference published as
* `ConnectionMeta.configs.ui.dockPreferences`.
*/
dockPreferences?: ViteDevToolsUiOptions['dockPreferences']

/**
* Options for building static DevTools output alongside `vite build`.
Expand All @@ -52,16 +63,19 @@ export async function DevTools(options: DevToolsOptions = {}): Promise<Plugin[]>
const {
builtinDevTools = true,
build,
visibility = 'normal',
embeddedVisibility = 'normal',
dockPreferences,
} = options

const ui = { embeddedVisibility, dockPreferences }

const plugins = [
DevToolsInjection({ visibility }),
DevToolsServer(),
DevToolsInjection(),
DevToolsServer(ui),
]

if (build?.withApp) {
plugins.push(DevToolsBuild({ outDir: build.outDir }))
plugins.push(DevToolsBuild({ outDir: build.outDir, ui }))
}

plugins.unshift(
Expand Down
21 changes: 5 additions & 16 deletions packages/core/src/node/plugins/injection.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,13 @@
import type { Plugin } from 'vite'
import { DEVTOOLS_MOUNT_PATH } from '@vitejs/devtools-kit/constants'

export type DevToolsVisibility = 'passive' | 'normal' | 'hidden'

export interface DevToolsInjectionOptions {
/**
* Initial visibility of the injected overlay, forwarded to the
* `@devframes/hub-ui` embedded bootstrap as a `data-visibility` hint.
*
* @default 'normal'
*/
visibility?: DevToolsVisibility
}

/**
* Inject the `@devframes/hub-ui` embedded bootstrap into the host app's HTML.
* The hub serves the prebuilt, self-contained module at `<base>embedded.js`
* (the `ui.embedded` slot); the client bundles its own framework and styles
* and owns its visibility policy, so the host app's build never processes it.
* and reads its reveal policy and dock preferences from the connection meta
* (`ConnectionMeta.configs.ui`, seeded by `createUi`), so the host app's build
* never processes it.
*
* The bootstrap is loaded by an **inline** module that creates the `<script>`
* element at runtime, rather than a static `<script type="module" src=…>`.
Expand All @@ -30,8 +20,7 @@ export interface DevToolsInjectionOptions {
* keeps `<base>embedded.js` out of Vite's graph entirely, so the browser
* fetches it straight from the hub with its real URL intact.
*/
export function DevToolsInjection(options: DevToolsInjectionOptions = {}): Plugin {
const visibility = options.visibility ?? 'normal'
export function DevToolsInjection(): Plugin {
const src = `${DEVTOOLS_MOUNT_PATH}embedded.js`

return {
Expand All @@ -47,7 +36,7 @@ export function DevToolsInjection(options: DevToolsInjectionOptions = {}): Plugi
{
tag: 'script',
attrs: { type: 'module' },
children: `const s = document.createElement('script'); s.type = 'module'; s.src = ${JSON.stringify(src)}; s.dataset.visibility = ${JSON.stringify(visibility)}; document.body.appendChild(s);`,
children: `const s = document.createElement('script'); s.type = 'module'; s.src = ${JSON.stringify(src)}; document.body.appendChild(s);`,
injectTo: 'body',
},
]
Expand Down
4 changes: 3 additions & 1 deletion packages/core/src/node/plugins/server.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { ClientScriptEntry, DevToolsDockEntry, ViteDevToolsNodeContext } from '@vitejs/devtools-kit'
import type { Server as NodeHttpServer } from 'node:http'
import type { Plugin } from 'vite'
import type { ViteDevToolsUiOptions } from '../ui'
import {
DEVTOOLS_DOCK_IMPORTS_VIRTUAL_ID,
DEVTOOLS_MOUNT_PATH,
Expand Down Expand Up @@ -36,7 +37,7 @@ export function renderDockImportsMap(docks: Iterable<DevToolsDockEntry>): string
].join('\n')
}

export function DevToolsServer(): Plugin {
export function DevToolsServer(options: ViteDevToolsUiOptions = {}): Plugin {
let context: ViteDevToolsNodeContext
let close: (() => Promise<void>) | undefined
return {
Expand All @@ -52,6 +53,7 @@ export function DevToolsServer(): Plugin {

const devtools = await createDevToolsHub({
context,
ui: options,
// Share Vite's HTTP server for a route-bound WS upgrade; fall back to a
// side-car when Vite runs in middleware mode without its own server.
// Vite types `httpServer` as a broader union (incl. http2); at dev
Expand Down
8 changes: 7 additions & 1 deletion packages/core/src/node/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { ConnectionMeta, ViteDevToolsNodeContext } from '@vitejs/devtools-k
import type { ViteDevToolsHost } from '@vitejs/devtools-kit/node'
import type { Server as NodeHttpServer } from 'node:http'
import type { DevToolsConfig } from './config'
import type { ViteDevToolsUiOptions } from './ui'
import process from 'node:process'
import { initHub } from '@devframes/hub/initiate'
import { jsonRenderUiRenderer } from '@devframes/json-render-ui/hub'
Expand All @@ -12,6 +13,11 @@ import { createViteDevToolsUi } from './ui'

export interface CreateDevToolsHubOptions {
context: ViteDevToolsNodeContext
/**
* Reference-UI options forwarded to `createUi` — the embedded dock's
* reveal policy and the dock-bar rendering preferences.
*/
ui?: ViteDevToolsUiOptions
/**
* Share this node HTTP server for the WebSocket upgrade (the embedded Vite
* dev server). The socket binds route-bound at `<base>__ws`, so no extra
Expand Down Expand Up @@ -58,7 +64,7 @@ export async function createDevToolsHub(options: CreateDevToolsHubOptions): Prom
const hub = initHub({
base: DEVTOOLS_MOUNT_PATH,
context,
ui: createViteDevToolsUi(),
ui: createViteDevToolsUi(options.ui),
// Serve + advertise the reference json-render frontend so `json-render`
// docks (kit's `createJsonRenderer`, the git/data-inspector devframes)
// render instead of hub-ui's missing-renderer fallback.
Expand Down
28 changes: 25 additions & 3 deletions packages/core/src/node/ui.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,26 @@
import type { DevframeBranding } from '@devframes/hub-ui'
import type { DevframeBranding, DevframeDockPreferences, EmbeddedVisibility } from '@devframes/hub-ui'
import type { DevframeHubUi } from '@devframes/hub/initiate'
import { createUi } from '@devframes/hub-ui'
import { DEVTOOLS_ASSETS_BASE } from '../dirs'

export interface ViteDevToolsUiOptions {
/**
* How the embedded floating dock reveals itself on a fresh page. Seeds a
* user-overridable preference published as
* `ConnectionMeta.configs.ui.embeddedVisibility`.
*
* @default 'normal'
*/
embeddedVisibility?: EmbeddedVisibility
/**
* Dock-bar rendering preferences — category ordering, floating-dock
* inline-item capacity, and the first-run float/edge mode and position.
* Each seeds a user-overridable preference published as
* `ConnectionMeta.configs.ui.dockPreferences`.
*/
dockPreferences?: DevframeDockPreferences
}

export function viteDevToolsBranding(): DevframeBranding {
return {
productName: 'Vite DevTools',
Expand All @@ -24,6 +42,10 @@ export function viteDevToolsBranding(): DevframeBranding {
* bootstrap to `initHub({ ui })` (dev serve) or is copied out by the static
* build.
*/
export function createViteDevToolsUi(): DevframeHubUi {
return createUi({ branding: viteDevToolsBranding() })
export function createViteDevToolsUi(options: ViteDevToolsUiOptions = {}): DevframeHubUi {
return createUi({
branding: viteDevToolsBranding(),
embeddedVisibility: options.embeddedVisibility,
dockPreferences: options.dockPreferences,
})
}
1 change: 1 addition & 0 deletions packages/oxc/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
"@humanwhocodes/momoa": "catalog:frontend",
"@nuxt/kit": "catalog:build",
"@types/picomatch": "catalog:types",
"@devframes/vite": "catalog:deps",
"@unocss/nuxt": "catalog:build",
"@vitejs/devtools-kit": "workspace:*",
"@vitejs/devtools-ui": "workspace:*",
Expand Down
6 changes: 2 additions & 4 deletions packages/oxc/src/nuxt.config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { fileURLToPath } from 'node:url'
import { viteDevBridge } from 'devframe/helpers/vite'
import { devframeViteBridge } from '@devframes/vite/dev-spa'
import { defineNuxtConfig } from 'nuxt/config'
import { alias } from '../../../alias'
import { oxcDevframe } from './node/devframe'
Expand Down Expand Up @@ -58,9 +58,7 @@ export default defineNuxtConfig({
},
vite: {
base: BASE,
plugins: [
viteDevBridge({ ...oxcDevframe, basePath: '/' }, { base: BASE, devMiddleware: true }),
],
plugins: [devframeViteBridge({ ...oxcDevframe, basePath: '/' }, { base: BASE })],
optimizeDeps: {
include: ['modern-monaco', 'floating-vue'],
},
Expand Down
Loading
Loading