Skip to content

Commit 64794fa

Browse files
committed
feat(kit)!: introduce events to client scripts, now client scripts would only be executed once
1 parent 7b4ff48 commit 64794fa

File tree

9 files changed

+155
-119
lines changed

9 files changed

+155
-119
lines changed

packages/core/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,13 @@
5656
"@vitejs/devtools-kit": "workspace:*",
5757
"@vitejs/devtools-rpc": "workspace:*",
5858
"@vitejs/devtools-vite": "workspace:*",
59+
"birpc": "catalog:deps",
5960
"birpc-x": "catalog:deps",
6061
"cac": "catalog:deps",
6162
"debug": "catalog:deps",
6263
"launch-editor": "catalog:deps",
6364
"mlly": "catalog:deps",
65+
"nanoevents": "catalog:deps",
6466
"open": "catalog:deps",
6567
"pathe": "catalog:deps",
6668
"sirv": "catalog:deps",

packages/core/playground/vite.config.ts

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,13 @@ export default defineConfig({
4949

5050
ctx.docks.register({
5151
type: 'action',
52-
action: ctx.utils.createSimpleClientScript(() => {
52+
action: ctx.utils.createSimpleClientScript((ctx) => {
5353
// eslint-disable-next-line no-alert
54-
alert('Hello, world!')
54+
alert('Hello, world! For the first time!')
55+
ctx.current.events.on('entry:activated', () => {
56+
// eslint-disable-next-line no-alert
57+
alert('Hello, world!')
58+
})
5559
}),
5660
id: 'local2',
5761
title: 'Local2',
@@ -61,22 +65,20 @@ export default defineConfig({
6165
ctx.docks.register({
6266
type: 'custom-render',
6367
renderer: ctx.utils.createSimpleClientScript((ctx) => {
64-
if (!ctx.current.domElements.panel) {
65-
// eslint-disable-next-line no-alert
66-
alert('No panel element found!')
67-
}
68-
const el = document.createElement('div')
69-
el.style.padding = '16px'
70-
el.textContent = 'Hello from custom render dock!'
68+
ctx.current.events.on('dom:panel:mounted', (panel) => {
69+
const el = document.createElement('div')
70+
el.style.padding = '16px'
71+
el.textContent = 'Hello from custom render dock!'
7172

72-
const btn = document.createElement('button')
73-
btn.textContent = 'Click me'
74-
btn.onclick = () => {
73+
const btn = document.createElement('button')
74+
btn.textContent = 'Click me'
75+
btn.onclick = () => {
7576
// eslint-disable-next-line no-alert
76-
alert('Button clicked in custom render dock!')
77-
}
78-
el.appendChild(btn)
79-
ctx.current.domElements.panel?.appendChild(el)
77+
alert('Button clicked in custom render dock!')
78+
}
79+
el.appendChild(btn)
80+
panel.appendChild(el)
81+
})
8082
}),
8183
id: 'custom-render',
8284
title: 'Custom',

packages/core/src/client/webcomponents/state/dock.ts

Lines changed: 43 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import type { DevToolsDockEntry } from '@vitejs/devtools-kit'
2-
import type { DevToolsRpcClient, DockEntryState, DockPanelStorage, DocksContext } from '@vitejs/devtools-kit/client'
2+
import type { DevToolsRpcClient, DockEntryState, DockEntryStateEvents, DockPanelStorage, DocksContext } from '@vitejs/devtools-kit/client'
33
import type { Ref } from 'vue'
4-
import { computed, reactive, ref, shallowRef } from 'vue'
4+
import { createNanoEvents } from 'nanoevents'
5+
import { computed, markRaw, reactive, ref, shallowRef, watch } from 'vue'
56

67
export function DEFAULT_DOCK_PANEL_STORE(): DockPanelStorage {
78
return {
@@ -15,6 +16,42 @@ export function DEFAULT_DOCK_PANEL_STORE(): DockPanelStorage {
1516
}
1617
}
1718

19+
function createDockEntryState(
20+
entry: DevToolsDockEntry,
21+
selected: Ref<DevToolsDockEntry | null>,
22+
): DockEntryState {
23+
const events = createNanoEvents<DockEntryStateEvents>()
24+
const state: DockEntryState = reactive({
25+
entryMeta: entry,
26+
get isActive() {
27+
return selected.value?.id === entry.id
28+
},
29+
domElements: {},
30+
events: markRaw(events),
31+
})
32+
33+
watch(() => selected.value?.id, (newSelectedId) => {
34+
if (newSelectedId === entry.id) {
35+
events.emit('entry:activated')
36+
}
37+
else {
38+
events.emit('entry:deactivated')
39+
}
40+
})
41+
42+
watch(() => state.domElements.iframe, (newIframe) => {
43+
if (newIframe)
44+
events.emit('dom:iframe:mounted', newIframe)
45+
})
46+
47+
watch(() => state.domElements.panel, (newPanel) => {
48+
if (newPanel)
49+
events.emit('dom:panel:mounted', newPanel)
50+
})
51+
52+
return state
53+
}
54+
1855
export async function createDocksContext(
1956
clientType: 'embedded' | 'standalone',
2057
rpc: DevToolsRpcClient,
@@ -25,17 +62,12 @@ export async function createDocksContext(
2562
// eslint-disable-next-line no-console
2663
console.log('[VITE DEVTOOLS] Docks Entries', [...dockEntries.value])
2764
// TODO: get board case from rpc when entries updates
28-
const dockEntryStateMap = reactive(new Map<string, DockEntryState>())
65+
const dockEntryStateMap: Map<string, DockEntryState> = reactive(new Map())
2966
for (const entry of dockEntries.value) {
67+
// TODO: handle update
3068
dockEntryStateMap.set(
3169
entry.id,
32-
reactive({
33-
entryMeta: entry,
34-
get isActive() {
35-
return selected.value?.id === entry.id
36-
},
37-
domElements: {},
38-
}),
70+
createDockEntryState(entry, selected),
3971
)
4072
}
4173

@@ -51,7 +83,7 @@ export async function createDocksContext(
5183
docks: {
5284
selected,
5385
entries: dockEntries.value,
54-
entryToStateMap: dockEntryStateMap,
86+
entryToStateMap: markRaw(dockEntryStateMap),
5587
getStateById: (id: string) => dockEntryStateMap.get(id),
5688
switchEntry: async (id: string | null) => {
5789
if (id === null) {

packages/core/src/client/webcomponents/state/state.ts

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,19 @@ import { reactive, toRefs } from 'vue'
55
export function useStateHandlers(
66
context: DocksContext,
77
) {
8-
function importScript(entry: DevToolsDockEntry): Promise<(context: DockClientScriptContext) => void | Promise<void>> {
8+
function _setupScript(
9+
entry: DevToolsDockEntry,
10+
context: DockClientScriptContext,
11+
): Promise<void> {
912
const id = `${entry.type}:${entry.id}`
1013
return import(/* @vite-ignore */ ['/.devtools', 'imports'].join('-'))
1114
.then((module) => {
12-
const importsMap = module.importsMap as Record<string, () => Promise<() => void>>
15+
const importsMap = module.importsMap as Record<string, () => Promise<(context: DockClientScriptContext) => void>>
1316
const importFn = importsMap[id]
1417
if (!importFn) {
1518
return Promise.reject(new Error(`[VITE DEVTOOLS] No import found for id: ${id}`))
1619
}
17-
return importFn()
20+
return importFn().then(fn => fn(context))
1821
})
1922
.catch((error) => {
2023
// TODO: maybe popup a error toast here?
@@ -24,6 +27,15 @@ export function useStateHandlers(
2427
})
2528
}
2629

30+
const setupPromises = new Map<string, Promise<void>>()
31+
function setupScript(entry: DevToolsDockEntry, context: DockClientScriptContext): Promise<void> {
32+
if (setupPromises.has(entry.id))
33+
return setupPromises.get(entry.id)!
34+
const promise = _setupScript(entry, context)
35+
setupPromises.set(entry.id, promise)
36+
return promise
37+
}
38+
2739
async function selectDockEntry(entry?: DevToolsDockEntry) {
2840
if (!entry) {
2941
context.panel.store.open = false
@@ -37,25 +49,21 @@ export function useStateHandlers(
3749
const current = context.docks.getStateById(entry.id)!
3850

3951
const scriptContext: DockClientScriptContext = reactive({
40-
...toRefs(context),
52+
...toRefs(context) as any,
4153
current,
4254
})
4355

44-
// If it's an action, run and return (early exit)
45-
if (entry?.type === 'action') {
46-
return await importScript(entry).then(fn => fn(scriptContext))
47-
}
48-
49-
context.docks.selected = entry
50-
context.panel.store.open = true
51-
5256
// If has import script, run it
5357
if (
54-
(entry.type === 'custom-render')
58+
(entry.type === 'action')
59+
|| (entry.type === 'custom-render')
5560
|| (entry.type === 'iframe' && entry.clientScript)
5661
) {
57-
await importScript(entry).then(fn => fn(scriptContext))
62+
await setupScript(entry, scriptContext)
5863
}
64+
65+
context.docks.selected = entry
66+
context.panel.store.open = true
5967
}
6068

6169
return {

packages/kit/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@
4040
"dependencies": {
4141
"@vitejs/devtools-rpc": "workspace:*",
4242
"birpc": "catalog:deps",
43-
"birpc-x": "catalog:deps"
43+
"birpc-x": "catalog:deps",
44+
"nanoevents": "catalog:deps"
4445
},
4546
"devDependencies": {
4647
"tsdown": "catalog:build",
Lines changed: 1 addition & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,11 @@
1-
import type { DevToolsDockEntry } from '../types'
21
import type { DockEntryState, DocksContext } from './docks'
32

43
/**
54
* Context for client scripts running in dock entries
65
*/
76
export interface DockClientScriptContext extends DocksContext {
87
/**
9-
* The state if the current dock entry
8+
* The state of the current dock entry
109
*/
1110
current: DockEntryState
1211
}
13-
14-
export interface DockClientScriptCurrent {
15-
/**
16-
* The dock entry info of the current dock item
17-
*/
18-
entryMeta: DevToolsDockEntry
19-
20-
/**
21-
* The current state of the dock
22-
*/
23-
state: 'active' | 'inactive'
24-
25-
/**
26-
* The panel element to mount into, when the entry type is `custom-render`
27-
*/
28-
elPanel?: HTMLDivElement | null
29-
30-
/**
31-
* The iframe element to mount into, when the entry type is `iframe`
32-
*/
33-
elIframe?: HTMLIFrameElement | null
34-
35-
/**
36-
* The dock icon element
37-
*/
38-
elDockIcon?: HTMLDivElement | null
39-
}

packages/kit/src/client/docks.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import type { Emitter as EventsEmitter } from 'nanoevents'
2+
import type { Raw } from 'vue'
13
import type { DevToolsDockEntry } from '../types'
24
import type { DevToolsRpcClient } from './rpc'
35

@@ -65,4 +67,13 @@ export interface DockEntryState {
6567
iframe?: HTMLIFrameElement | null
6668
panel?: HTMLDivElement | null
6769
}
70+
events: Raw<EventsEmitter<DockEntryStateEvents>>
71+
}
72+
73+
export interface DockEntryStateEvents {
74+
'entry:activated': () => void
75+
'entry:deactivated': () => void
76+
'entry:updated': (newMeta: DevToolsDockEntry) => void
77+
'dom:panel:mounted': (panel: HTMLDivElement) => void
78+
'dom:iframe:mounted': (iframe: HTMLIFrameElement) => void
6879
}

0 commit comments

Comments
 (0)