Skip to content

Commit eb96a7c

Browse files
antfubotantfu
andauthored
feat(devtools)!: make embedded dock the only client mode (#1041)
Co-authored-by: Anthony Fu <github@antfu.me>
1 parent 3062113 commit eb96a7c

29 files changed

Lines changed: 312 additions & 1165 deletions

‎eslint.config.mjs‎

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,16 @@
1+
import { existsSync } from 'node:fs'
2+
import { fileURLToPath } from 'node:url'
13
import antfu from '@antfu/eslint-config'
24
import { extend } from 'eslint-flat-config-utils'
35

4-
export default antfu(
6+
// The client Nuxt app's generated flat config only exists once the app has been
7+
// prepared (`pnpm dev:prepare`/`pnpm build`). Skip it gracefully when it hasn't
8+
// been generated yet (e.g. a fresh `pnpm install && pnpm lint` in CI) so linting
9+
// doesn't hard-crash on the missing import.
10+
const clientConfigUrl = new URL('./packages/devtools/client/.nuxt/eslint.config.mjs', import.meta.url)
11+
const hasClientConfig = existsSync(fileURLToPath(clientConfigUrl))
12+
13+
const config = antfu(
514
{
615
formatters: true,
716
unocss: true,
@@ -22,12 +31,37 @@ export default antfu(
2231
},
2332
},
2433
)
25-
.append(
34+
.removeRules(
35+
'vue/no-multiple-template-root',
36+
)
37+
38+
// `FlatConfigComposer.append()` mutates the composer in place (and returns it),
39+
// so the export stays a `const`.
40+
if (hasClientConfig) {
41+
config.append(
2642
extend(
27-
import('./packages/devtools/client/.nuxt/eslint.config.mjs').then(mod => mod.default()),
43+
import(clientConfigUrl.href).then(mod => mod.default()),
2844
'packages/devtools/client',
2945
),
3046
)
31-
.removeRules(
32-
'vue/no-multiple-template-root',
47+
}
48+
else {
49+
// Before the client app is prepared, its generated flat config (which wires up
50+
// `eslint-plugin-unimport`) is absent, so the `unimport/auto-insert` rule the
51+
// client's inline `eslint-disable` directives reference is undefined. Register
52+
// the plugin so those directives resolve and linting doesn't error.
53+
config.append(
54+
import('eslint-plugin-unimport').then(mod => ({
55+
name: 'nuxt-devtools/client/unimport-fallback',
56+
files: ['packages/devtools/client/**/*.{ts,vue}'],
57+
plugins: { unimport: mod.default ?? mod },
58+
rules: { 'unimport/auto-insert': 'off' },
59+
// The rule is off in this fallback, so the client's inline disable
60+
// directives read as "unused"; don't report (or auto-fix away) them —
61+
// they are needed once the client app is prepared and the rule is active.
62+
linterOptions: { reportUnusedDisableDirectives: 'off' },
63+
})),
3364
)
65+
}
66+
67+
export default config

‎packages/devtools-kit/src/_types/options.ts‎

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -235,13 +235,10 @@ export interface NuxtDevToolsOptions {
235235
componentsView: 'list' | 'graph'
236236
hiddenTabCategories: string[]
237237
hiddenTabs: string[]
238-
interactionCloseOnOutsideClick: boolean
239238
pinnedTabs: string[]
240239
scale: number
241240
showExperimentalFeatures: boolean
242241
showHelpButtons: boolean
243-
sidebarExpanded: boolean
244-
sidebarScrollable: boolean
245242
}
246243
serverRoutes: {
247244
selectedRoute: ServerRouteInfo | null

‎packages/devtools/client/app.vue‎

Lines changed: 10 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
11
<script setup lang="ts">
22
import { useEventListener, useEyeDropper } from '@vueuse/core'
33
import { computed, onMounted, ref, watch, watchEffect } from 'vue'
4-
import { useRoute } from '#app/composables/router'
54
import { useHead } from '#imports'
65
import { getColorMode, showConnectionWarning, useClient, useInjectionClient } from '~/composables/client'
76
import { useCopy } from '~/composables/editor'
8-
import { isEmbedded } from '~/composables/embed'
97
import { setupFrameNav } from '~/composables/frame-nav'
108
import { WS_DEBOUNCE_TIME } from '~/composables/rpc'
119
import { registerCommands } from '~/composables/state-commands'
12-
import { splitScreenAvailable, splitScreenEnabled } from '~/composables/storage'
1310
import { wsConnectedOnce } from './composables/rpc'
1411
import { useSchemaInput } from './composables/state-schema'
1512
import { useDevToolsOptions } from './composables/storage-options'
@@ -43,12 +40,7 @@ useHead({
4340
setupClientRPC()
4441
4542
const client = useClient()
46-
const route = useRoute()
4743
const colorMode = getColorMode()
48-
// When embedded as the shared-frame anchor (`?embed=1`), hide the app shell
49-
// (SideNav + split pane) so the iframe shows only the current tab; the dock's
50-
// per-tab members drive navigation via the frame-nav shim.
51-
const isUtilityView = computed(() => isEmbedded.value || route.path.startsWith('/__') || route.path === '/')
5244
const waiting = computed(() => !client.value && !showConnectionWarning.value)
5345
const showDisconnectIndicator = ref(false)
5446
@@ -88,14 +80,14 @@ useEventListener('keydown', (e) => {
8880
}
8981
})
9082
91-
const { scale, sidebarExpanded } = useDevToolsOptions('ui')
83+
const { scale } = useDevToolsOptions('ui')
9284
const dataSchema = useSchemaInput()
9385
9486
onMounted(async () => {
95-
// As the shared-frame anchor, announce our tabs to the dock and answer
96-
// soft-navigation over postMessage.
97-
if (isEmbedded.value)
98-
setupFrameNav()
87+
// Nuxt DevTools always renders as the shared-frame anchor inside the Vite
88+
// DevTools dock: announce our tabs to the dock and answer soft-navigation
89+
// over postMessage.
90+
setupFrameNav()
9991
10092
const injectClient = useInjectionClient()
10193
watchEffect(() => {
@@ -111,16 +103,6 @@ const copy = useCopy()
111103
const eyeDropper = useEyeDropper({})
112104
113105
registerCommands(() => [
114-
...(splitScreenAvailable.value
115-
? [{
116-
id: 'action:split-screen',
117-
title: `${splitScreenEnabled.value ? 'Close' : 'Open'} Split Screen`,
118-
icon: 'i-carbon-split-screen',
119-
action: () => {
120-
splitScreenEnabled.value = !splitScreenEnabled.value
121-
},
122-
}]
123-
: []),
124106
...(eyeDropper.isSupported.value
125107
? [{
126108
id: 'action:eye-dropper',
@@ -158,21 +140,14 @@ registerCommands(() => [
158140
</NLoading>
159141
<div
160142
v-else
161-
:class="isEmbedded ? 'grid grid-cols-[1fr]' : isUtilityView ? 'flex' : sidebarExpanded ? 'grid grid-cols-[250px_1fr]' : 'grid grid-cols-[50px_1fr]'"
143+
id="nuxt-devtools-app"
144+
class="grid grid-cols-[1fr]"
162145
h-full h-screen of-hidden rounded-xl bg-base font-sans
163146
>
164-
<SideNav v-show="!isUtilityView" of-x-hidden of-y-auto />
147+
<!-- Single-tab view: the shared-frame anchor mounts the current tab
148+
directly; the Vite DevTools dock provides navigation between tabs. -->
165149
<NuxtLayout>
166-
<!-- Embedded single-tab view: mount the tab content directly, no split pane -->
167-
<NuxtPage v-if="isEmbedded" />
168-
<NSplitPane v-else storage-key="devtools:split-screen-mode" :min-size="20">
169-
<template #left>
170-
<NuxtPage />
171-
</template>
172-
<template v-if="!isUtilityView && splitScreenEnabled && splitScreenAvailable" #right>
173-
<SplitScreen />
174-
</template>
175-
</NSplitPane>
150+
<NuxtPage />
176151
</NuxtLayout>
177152
<CommandPalette />
178153
</div>

‎packages/devtools/client/components/DockingPanel.vue‎

Lines changed: 0 additions & 44 deletions
This file was deleted.

‎packages/devtools/client/components/SideNav.vue‎

Lines changed: 0 additions & 170 deletions
This file was deleted.

0 commit comments

Comments
 (0)