Skip to content

wip: vite plugin devtools setup #85

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions packages/devtools-kit/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ import './extend'

export * from './rpc'
export * from './utils'
export * from './views'
export * from './vite'
14 changes: 13 additions & 1 deletion packages/devtools-kit/src/types/rpc.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import type { EntriesToObject, Thenable } from './utils'

/**
* To be extended
*/
export interface ViteDevtoolsRpcFunctions {}

/**
* Type of the RPC function,
* - static: A function that returns a static data (can be cached and dumped)
Expand All @@ -8,6 +13,12 @@ import type { EntriesToObject, Thenable } from './utils'
*/
export type RpcFunctionType = 'static' | 'action' | 'query'

export interface RpcFunctionsHost {
readonly functions: ViteDevtoolsRpcFunctions
readonly definitions: Map<string, RpcFunctionDefinition<string, any, any, any>>
register: (fn: RpcFunctionDefinition<string, any, any, any>) => void
}

export interface RpcFunctionSetupResult<
ARGS extends any[],
RETURN = void,
Expand All @@ -18,7 +29,7 @@ export interface RpcFunctionSetupResult<
export interface RpcFunctionDefinition<
NAME extends string,
TYPE extends RpcFunctionType,
ARGS extends any[],
ARGS extends any[] = [],
RETURN = void,
> {
name: NAME
Expand All @@ -31,6 +42,7 @@ export interface RpcFunctionDefinition<
export interface RpcContext {
cwd: string
mode: 'dev' | 'build'
functions: RpcFunctionsHost
meta?: any
}

Expand Down
29 changes: 29 additions & 0 deletions packages/devtools-kit/src/types/views.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
export interface DevtoolsViewHost {
register: (view: DevtoolsViewTab) => void
}

export interface DevtoolsViewTab {
name: string
icon: string
viewId: string
view: DevtoolsViewMeta
}

export interface DevtoolsViewIframe {
type: 'iframe'
url: string
/**
* The id of the iframe, if multiple tabs is assigned with the same id, the iframe will be shared.
*
* When not provided, it would be treated as a unique frame.
*/
frameId?: string
}

export interface DevtoolsViewWebComponent {
type: 'webcomponent'
from: string
import: string
}

export type DevtoolsViewMeta = DevtoolsViewIframe | DevtoolsViewWebComponent
17 changes: 14 additions & 3 deletions packages/devtools-kit/src/types/vite.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,20 @@
import type { RpcContext } from './rpc'
import type { ResolvedConfig } from 'vite'
import type { RpcFunctionsHost } from './rpc'
import type { DevtoolsViewHost } from './views'

export interface DevToolsPluginOptions {
setup: () => void | Promise<void>
setup: (context: DevToolsSetupContext) => void | Promise<void>
}

export interface DevToolsSetupContext {
rpc: RpcContext
readonly cwd: string
readonly mode: 'dev' | 'build'
readonly viteConfig: ResolvedConfig
rpc: RpcFunctionsHost
views: DevtoolsViewHost
}

export interface ConnectionMeta {
backend: 'websocket' | 'static'
websocket?: number | string
}
3 changes: 2 additions & 1 deletion packages/devtools-vite/src/modules/rpc/runtime/plugin.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { ConnectionMeta } from '@vitejs/devtools-kit'
import type { BirpcReturn } from 'birpc'
import type { ServerFunctions } from '../../../node/rpc'
import type { ClientFunctions, ConnectionMeta } from '../../../shared/types'
import type { ClientFunctions } from '../../../shared/types'
import { defineNuxtPlugin } from '#app'
import { useRuntimeConfig } from '#app/nuxt'
import { createRpcClient as _createRpcClient } from '@vitejs/devtools-rpc'
Expand Down
2 changes: 1 addition & 1 deletion packages/devtools-vite/src/node/ws.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { ConnectionMeta } from '@vitejs/devtools-kit'
import type { WebSocket } from 'ws'
import type { ConnectionMeta } from '../shared/types'
import type { CreateServerFunctionsOptions } from './functions'
import type { ServerFunctions } from './rpc'
import { createRpcServer } from '@vitejs/devtools-rpc'
Expand Down
21 changes: 21 additions & 0 deletions packages/devtools-vite/src/plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type { Plugin } from 'vite'
import '@vitejs/devtools-kit'

export function DevToolsVite(): Plugin {
return {
name: 'vite:devtools',
devtools: {
setup(ctx) {
ctx.views.register({
name: 'Vite',
icon: 'vite',
viewId: 'vite',
view: {
type: 'iframe',
url: 'http://localhost:3000',
},
})
},
},
}
}
5 changes: 0 additions & 5 deletions packages/devtools-vite/src/shared/types/vite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,3 @@ export interface ViteDevToolsConfig {
export type RemoveVoidKeysFromObject<T> = { [K in keyof T]: T[K] extends void ? never : K } extends { [_ in keyof T]: never } ? T : { [K in keyof T as T[K] extends void ? never : K]: T[K] }

export interface ClientFunctions {}

export interface ConnectionMeta {
backend: 'websocket' | 'static'
websocket?: number | string
}
4 changes: 4 additions & 0 deletions packages/devtools/bin.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/usr/bin/env node

// eslint-disable-next-line antfu/no-top-level-await
await import('./dist/cli.mjs')
6 changes: 5 additions & 1 deletion packages/devtools/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@
},
"main": "./dist/index.mjs",
"types": "./dist/index.d.mts",
"bin": {
"vite-devtools": "./dist/cli.mjs"
},
"files": [
"dist"
],
Expand All @@ -36,7 +39,8 @@
},
"dependencies": {
"@vitejs/devtools-kit": "workspace:*",
"@vitejs/devtools-rpc": "workspace:*"
"@vitejs/devtools-rpc": "workspace:*",
"ws": "catalog:deps"
},
"devDependencies": {
"tsdown": "catalog:build"
Expand Down
1 change: 1 addition & 0 deletions packages/devtools/src/cli.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './node/cli'
3 changes: 3 additions & 0 deletions packages/devtools/src/dirs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { fileURLToPath } from 'node:url'

export const distDir: string = fileURLToPath(new URL('../dist/public', import.meta.url))
4 changes: 3 additions & 1 deletion packages/devtools/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import type { Plugin } from 'vite'
import { startStandaloneServer } from './node/server'
import '@vitejs/devtools-kit'

export function ViteDevTools(): Plugin {
return {
name: 'vite:devtools',
enforce: 'post',
configureServer(_server) {
configureServer(server) {
startStandaloneServer(server.config)
// console.log(server)
},
}
Expand Down
125 changes: 125 additions & 0 deletions packages/devtools/src/node/cli.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import process from 'node:process'
import c from 'ansis'
import cac from 'cac'
import { getPort } from 'get-port-please'
import open from 'open'
import { MARK_NODE } from './constants'
import { startStandaloneServer } from './server'
import { startStandaloneDevTools } from './standalone'

const cli = cac('vite-devtools')

cli
.command('build', 'Build devtools with current config file for static hosting')
.option('--root <root>', 'Root directory', { default: process.cwd() })
.option('--config <config>', 'Config file')
.option('--depth <depth>', 'Max depth to list dependencies', { default: 8 })
// Build specific options
.option('--base <baseURL>', 'Base URL for deployment', { default: '/' })
.option('--outDir <dir>', 'Output directory', { default: '.vite-devtools' })
// Action
.action(async (_options) => {
console.log(c.cyan`${MARK_NODE} Building static Vite DevTools...`)

throw new Error('Not implemented')

// const cwd = process.cwd()
// const outDir = resolve(cwd, options.outDir)

// const rpc = await import('./functions')
// .then(async r => await r.createServerFunctions({
// cwd,
// mode: 'build',
// // TODO: redesign how the manager is passed
// meta: {
// manager: new RolldownLogsManager(join(cwd, '.rolldown')),
// },
// }))
// const rpcDump: ServerFunctionsDump = {
// 'vite:get-payload': await rpc['vite:get-payload'](),
// }

// let baseURL = options.base
// if (!baseURL.endsWith('/'))
// baseURL += '/'
// if (!baseURL.startsWith('/'))
// baseURL = `/${baseURL}`
// baseURL = baseURL.replace(/\/+/g, '/')

// if (existsSync(outDir))
// await fs.rm(outDir, { recursive: true })
// await fs.mkdir(outDir, { recursive: true })
// await fs.cp(distDir, outDir, { recursive: true })
// const htmlFiles = await glob('**/*.html', { cwd: distDir, onlyFiles: true, dot: true, expandDirectories: false })
// // Rewrite HTML files with base URL
// if (baseURL !== '/') {
// for (const file of htmlFiles) {
// const content = await fs.readFile(resolve(distDir, file), 'utf-8')
// const newContent = content
// .replaceAll(/\s(href|src)="\//g, ` $1="${baseURL}`)
// .replaceAll('baseURL:"/"', `baseURL:"${baseURL}"`)
// await fs.writeFile(resolve(outDir, file), newContent, 'utf-8')
// }
// }

// await fs.mkdir(resolve(outDir, 'api'), { recursive: true })
// await fs.writeFile(resolve(outDir, 'api/metadata.json'), JSON.stringify({ backend: 'static' }, null, 2), 'utf-8')
// await fs.writeFile(resolve(outDir, 'api/rpc-dump.json'), stringify(rpcDump), 'utf-8')

// console.log(c.green`${MARK_CHECK} Built to ${relative(cwd, outDir)}`)
// console.log(c.blue`${MARK_NODE} You can use static server like \`npx serve ${relative(cwd, outDir)}\` to serve the devtools`)
})

cli
.command('', 'Start devtools')
.option('--root <root>', 'Root directory', { default: process.cwd() })
.option('--config <config>', 'Config file')
.option('--depth <depth>', 'Max depth to list dependencies', { default: 8 })
// Dev specific options
.option('--host <host>', 'Host', { default: process.env.HOST || '127.0.0.1' })
.option('--port <port>', 'Port', { default: process.env.PORT || 9999 })
.option('--open', 'Open browser', { default: true })
// Action
.action(async (options) => {
const host = options.host
const port = await getPort({ port: options.port, portRange: [9999, 15000], host })

console.log(c.green`${MARK_NODE} Starting Vite DevTools at`, c.green(`http://${host === '127.0.0.1' ? 'localhost' : host}:${port}`), '\n')

const devtools = await startStandaloneDevTools()

const { server } = await startStandaloneServer({
cwd: devtools.config.root,
port,
context: devtools.context,
functions: devtools.context.rpc,
})

server.listen(port, host, async () => {
console.log(c.green`${MARK_NODE} Vite DevTools started at`, c.green(`http://${host === '127.0.0.1' ? 'localhost' : host}:${port}`), '\n')
if (options.open)
await open(`http://${host === '127.0.0.1' ? 'localhost' : host}:${port}`)
})

// const { server, rpc } = await createHostServer({
// cwd: options.root,
// mode: 'dev',
// // TODO: redesign how the manager is passed
// meta: {
// manager: new RolldownLogsManager(join(options.root, '.rolldown')),
// },
// })

// // Warm up the payload
// setTimeout(() => {
// rpc.functions['vite:get-payload']()
// }, 1)

// server.listen(port, host, async () => {
// if (options.open)
// await open(`http://${host === '127.0.0.1' ? 'localhost' : host}:${port}`)
// })
})

cli.help()
cli.parse()
6 changes: 6 additions & 0 deletions packages/devtools/src/node/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import c from 'ansis'

export const MARK_CHECK: string = c.green('✔')
export const MARK_INFO: string = c.blue('ℹ')
export const MARK_ERROR: string = c.red('✖')
export const MARK_NODE: string = '⬢'
19 changes: 19 additions & 0 deletions packages/devtools/src/node/functions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { RpcFunctionDefinition, RpcFunctionsHost as RpcFunctionsHostType, ViteDevtoolsRpcFunctions } from '@vitejs/devtools-kit'

export class RpcFunctionsHost implements RpcFunctionsHostType {
public readonly definitions: Map<string, RpcFunctionDefinition<string, any, any, any>> = new Map()

constructor() {
}

register(fn: RpcFunctionDefinition<string, any, any, any>): void {
this.definitions.set(fn.name, fn)
}

get functions(): ViteDevtoolsRpcFunctions {
return Object.fromEntries(
Array.from(this.definitions.entries())
.map(([name, fn]) => [name, fn.handler]),
) as ViteDevtoolsRpcFunctions
}
}
Empty file.
Loading