Skip to content
Closed
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
3 changes: 3 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,6 @@ export default antfu({
'no-console': 'off',
},
})
.removeRules(
'vue/no-template-shadow',
)
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
"@types/ws": "catalog:types",
"@typescript-eslint/utils": "catalog:devtools",
"@unocss/eslint-config": "catalog:devtools",
"@vueuse/core": "catalog:frontend",
"ansis": "catalog:deps",
"bumpp": "catalog:devtools",
"esbuild": "catalog:build",
Expand Down
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,11 @@
"debug": "catalog:deps",
"launch-editor": "catalog:deps",
"mlly": "catalog:deps",
"nanoevents": "catalog:deps",
"open": "catalog:deps",
"pathe": "catalog:deps",
"perfect-debounce": "catalog:deps",
"sirv": "catalog:deps",
"tinyexec": "catalog:deps",
"ws": "catalog:deps"
},
"devDependencies": {
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/client/webcomponents/state/dock.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { DevToolsDockEntry } from '@vitejs/devtools-kit'
import type { ClientRpcReturn, DockEntryState, DockEntryStateEvents, DockPanelStorage, DocksContext } from '@vitejs/devtools-kit/client'
import type { Ref, ShallowRef } from 'vue'
import { createNanoEvents } from 'nanoevents'
import { createEventEmitter } from '@vitejs/devtools-kit/utils/events'
import { computed, markRaw, reactive, ref, shallowRef, watch, watchEffect } from 'vue'

export function DEFAULT_DOCK_PANEL_STORE(): DockPanelStorage {
Expand All @@ -20,7 +20,7 @@ function createDockEntryState(
entry: DevToolsDockEntry,
selected: Ref<DevToolsDockEntry | null>,
): DockEntryState {
const events = createNanoEvents<DockEntryStateEvents>()
const events = createEventEmitter<DockEntryStateEvents>()
const state: DockEntryState = reactive({
entryMeta: entry,
get isActive() {
Expand Down
10 changes: 10 additions & 0 deletions packages/core/src/node/context.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import type { DevToolsNodeContext } from '@vitejs/devtools-kit'
import type { ResolvedConfig, ViteDevServer } from 'vite'
import Debug from 'debug'
import { debounce } from 'perfect-debounce'
import { ContextUtils } from './context-utils'
import { DevToolsDockHost } from './host-docks'
import { RpcFunctionsHost } from './host-functions'
import { DevToolsTerminalHost } from './host-terminals'
import { DevToolsViewHost } from './host-views'
import { builtinRpcFunctions } from './rpc'

Expand All @@ -24,19 +26,27 @@ export async function createDevToolsContext(
docks: undefined!,
views: undefined!,
utils: ContextUtils,
terminals: undefined!,
}
const rpcHost = new RpcFunctionsHost(context)
const docksHost = new DevToolsDockHost(context)
const viewsHost = new DevToolsViewHost(context)
const terminalsHost = new DevToolsTerminalHost(context)
context.rpc = rpcHost
context.docks = docksHost
context.views = viewsHost
context.terminals = terminalsHost

// Build-in function to list all RPC functions
for (const fn of builtinRpcFunctions) {
rpcHost.register(fn)
}

// Register hosts side effects
docksHost.events.on('dock:entry:updated', debounce(() => {
rpcHost.boardcast.$callOptional('vite:core:list-dock-entries:updated')
}, 10))

// Register plugins
const plugins = viteConfig.plugins.filter(plugin => 'devtools' in plugin)

Expand Down
13 changes: 5 additions & 8 deletions packages/core/src/node/host-docks.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
import type { DevToolsDockEntry, DevToolsDockHost as DevToolsDockHostType, DevToolsNodeContext } from '@vitejs/devtools-kit'
import { debounce } from 'perfect-debounce'
import { createEventEmitter } from '@vitejs/devtools-kit/utils/events'

export class DevToolsDockHost implements DevToolsDockHostType {
public readonly views: Map<string, DevToolsDockEntry> = new Map()
private _sendOnChange: (() => void)
public readonly views: DevToolsDockHostType['views'] = new Map()
public readonly events: DevToolsDockHostType['events'] = createEventEmitter()

constructor(
public readonly context: DevToolsNodeContext,
) {
this._sendOnChange = debounce(() => {
context.rpc?.boardcast?.$callOptional('vite:core:list-dock-entries:updated')
}, 10)
}

values(): DevToolsDockEntry[] {
Expand All @@ -22,14 +19,14 @@ export class DevToolsDockHost implements DevToolsDockHostType {
throw new Error(`Dock with id "${view.id}" is already registered`)
}
this.views.set(view.id, view)
this._sendOnChange()
this.events.emit('dock:entry:updated', view)
}

update(view: DevToolsDockEntry): void {
if (!this.views.has(view.id)) {
throw new Error(`Dock with id "${view.id}" is not registered. Use register() to add new docks.`)
}
this.views.set(view.id, view)
this._sendOnChange()
this.events.emit('dock:entry:updated', view)
}
}
107 changes: 107 additions & 0 deletions packages/core/src/node/host-terminals.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import type { DevToolsChildProcessTerminalOptions, DevToolsChildProcessTerminalSession, DevToolsNodeContext, DevToolsTerminalHost as DevToolsTerminalHostType, DevToolsTerminalSession, DevToolsTerminalSessionSerializable } from '@vitejs/devtools-kit'
import type { Result as TinyExecResult } from 'tinyexec'
import process from 'node:process'

export class DevToolsTerminalHost implements DevToolsTerminalHostType {
constructor(
public readonly context: DevToolsNodeContext,
) {
}

readonly sessions: Map<string, DevToolsTerminalSession> = new Map()

serialize(session: DevToolsTerminalSession): DevToolsTerminalSessionSerializable {
return {
id: session.id,
title: session.title,
description: session.description,
status: session.status,
buffer: session.buffer ?? [],
}
}

register(session: DevToolsTerminalSession): DevToolsTerminalSession {
if (this.sessions.has(session.id)) {
throw new Error(`Terminal session with id "${session.id}" already registered`)
}
this.sessions.set(session.id, session)
return session
}

update(session: DevToolsTerminalSession): void {
if (!this.sessions.has(session.id)) {
throw new Error(`Terminal session with id "${session.id}" not registered`)
}
this.sessions.set(session.id, session)
}

async startChildProcess(options: DevToolsChildProcessTerminalOptions): Promise<DevToolsChildProcessTerminalSession> {
if (this.sessions.has(options.id)) {
throw new Error(`Terminal session with id "${options.id}" already registered`)
}
const { exec } = await import('tinyexec')

let controller: ReadableStreamDefaultController<string> | undefined
const buffer = new ReadableStream<string>({
async start(_controller) {
controller = _controller
},
})
const writer = new WritableStream<string>({
write(chunk) {
controller?.enqueue(chunk)
},
})

function createChildProcess() {
const cp = exec(
options.command,
options.args || [],
{
nodeOptions: {
env: {
COLORS: 'true',
FORCE_COLOR: 'true',
...(options.env || {}),
},
cwd: options.cwd ?? process.cwd(),
stdio: 'pipe',
},
},
)

const stream = new ReadableStream<string>({
async start(controller) {
for await (const chunk of cp) {
controller.enqueue(chunk)
}
},
})
stream.pipeTo(writer)
return cp
}

let cp: TinyExecResult | undefined = createChildProcess()

const restart = async () => {
cp?.kill()
cp = createChildProcess()
}
const terminate = async () => {
cp?.kill()
cp = undefined
}

const session: DevToolsChildProcessTerminalSession = {
...options,
status: 'running',
stream: buffer,
getChildProcess: () => cp?.process,
terminate,
restart,
}
this.sessions.set(session.id, session)

return Promise.resolve(session)
}
}
13 changes: 13 additions & 0 deletions packages/core/src/node/rpc/list-terminals.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { defineRpcFunction } from '@vitejs/devtools-kit'

export const listTerminals = defineRpcFunction({
name: 'vite:core:list-terminals',
type: 'action',
setup: (context) => {
return {
async handler() {
return Array.from(context.terminals.sessions.values())
},
}
},
})
4 changes: 2 additions & 2 deletions packages/kit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"exports": {
".": "./dist/index.mjs",
"./client": "./dist/client.mjs",
"./utils/events": "./dist/utils/events.mjs",
"./package.json": "./package.json"
},
"main": "./dist/index.mjs",
Expand All @@ -40,8 +41,7 @@
"dependencies": {
"@vitejs/devtools-rpc": "workspace:*",
"birpc": "catalog:deps",
"birpc-x": "catalog:deps",
"nanoevents": "catalog:deps"
"birpc-x": "catalog:deps"
},
"devDependencies": {
"tsdown": "catalog:build",
Expand Down
5 changes: 2 additions & 3 deletions packages/kit/src/client/docks.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import type { RpcFunctionsCollector } from 'birpc-x'
import type { Emitter as EventsEmitter } from 'nanoevents'
import type { Raw } from 'vue'
import type { DevToolsDockEntry, DevToolsRpcClientFunctions } from '../types'
import type { DevToolsDockEntry, DevToolsRpcClientFunctions, EventEmitter } from '../types'
import type { DevToolsRpcClient } from './rpc'

export interface DockPanelStorage {
Expand Down Expand Up @@ -78,7 +77,7 @@ export interface DockEntryState {
iframe?: HTMLIFrameElement | null
panel?: HTMLDivElement | null
}
events: Raw<EventsEmitter<DockEntryStateEvents>>
events: Raw<EventEmitter<DockEntryStateEvents>>
}

export interface DockEntryStateEvents {
Expand Down
2 changes: 1 addition & 1 deletion packages/kit/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export type * from './types'
export * from './utils/rpc'
export * from './utils/define'
7 changes: 6 additions & 1 deletion packages/kit/src/types/docks.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
// TODO: refine categories more clearly
import type { EventEmitter } from './events'

export interface DevToolsDockHost {
views: Map<string, DevToolsDockEntry>
events: EventEmitter<{
'dock:entry:updated': (entry: DevToolsDockEntry) => void
}>

register: (entry: DevToolsDockEntry) => void
update: (entry: DevToolsDockEntry) => void
values: () => DevToolsDockEntry[]
}

// TODO: refine categories more clearly
export type DevToolsDockEntryCategory = 'app' | 'framework' | 'web' | 'advanced' | 'default'

export type DevToolsDockEntryIcon = string | { light: string, dark: string }
Expand Down
51 changes: 51 additions & 0 deletions packages/kit/src/types/events.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
export interface EventsMap {
[event: string]: any
}

export interface EventUnsubscribe {
(): void
}

export interface EventEmitter<Events extends EventsMap> {
/**
* Calls each of the listeners registered for a given event.
*
* ```js
* ee.emit('tick', tickType, tickDuration)
* ```
*
* @param event The event name.
* @param args The arguments for listeners.
*/
emit: <K extends keyof Events>(
this: this,
event: K,
...args: Parameters<Events[K]>
) => void

/**
* Event names in keys and arrays with listeners in values.
*
* @internal
*/
_listeners: Partial<{ [E in keyof Events]: Events[E][] }>

/**
* Add a listener for a given event.
*
* ```js
* const unbind = ee.on('tick', (tickType, tickDuration) => {
* count += 1
* })
*
* disable () {
* unbind()
* }
* ```
*
* @param event The event name.
* @param cb The listener function.
* @returns Unbind listener from event.
*/
on: <K extends keyof Events>(this: this, event: K, cb: Events[K]) => EventUnsubscribe
}
2 changes: 2 additions & 0 deletions packages/kit/src/types/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
export * from './docks'
export * from './events'
export * from './rpc'
export * from './rpc-augments'
export * from './terminals'
export * from './utils'
export * from './views'
export * from './vite-augment'
Expand Down
Loading
Loading