feat: initial setup for nuxt devtools
This commit is contained in:
3
src/constants/index.ts
Normal file
3
src/constants/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export const PATH = '/__nuxt_mongoose__'
|
||||
export const PATH_ENTRY = `${PATH}/entry`
|
||||
export const PATH_CLIENT = `${PATH}/client`
|
||||
@ -1,11 +1,13 @@
|
||||
import { addServerPlugin, addTemplate, createResolver, defineNuxtModule } from '@nuxt/kit'
|
||||
import { addServerPlugin, addTemplate, createResolver, defineNuxtModule, logger } from '@nuxt/kit'
|
||||
import { pathExists } from 'fs-extra'
|
||||
import { tinyws } from 'tinyws'
|
||||
import { defu } from 'defu'
|
||||
import type { ConnectOptions } from 'mongoose'
|
||||
import sirv from 'sirv'
|
||||
|
||||
export interface ModuleOptions {
|
||||
uri?: string
|
||||
options?: ConnectOptions
|
||||
}
|
||||
import { PATH_CLIENT, PATH_ENTRY } from './constants'
|
||||
import type { ModuleOptions } from './types'
|
||||
|
||||
import { setupRPC } from './server-rpc'
|
||||
|
||||
export default defineNuxtModule<ModuleOptions>({
|
||||
meta: {
|
||||
@ -14,6 +16,7 @@ export default defineNuxtModule<ModuleOptions>({
|
||||
},
|
||||
defaults: {
|
||||
uri: process.env.MONGODB_URI as string,
|
||||
devtools: true,
|
||||
options: {},
|
||||
},
|
||||
setup(options, nuxt) {
|
||||
@ -26,6 +29,39 @@ export default defineNuxtModule<ModuleOptions>({
|
||||
nuxt.options.runtimeConfig.public.mongoose = defu(nuxt.options.runtimeConfig.public.mongoose || {}, {
|
||||
uri: options.uri,
|
||||
options: options.options,
|
||||
devtools: options.devtools,
|
||||
})
|
||||
|
||||
// Setup devtools UI
|
||||
const distResolve = (p: string) => {
|
||||
const cwd = resolve('.')
|
||||
if (cwd.endsWith('/dist'))
|
||||
return resolve(p)
|
||||
return resolve(`../dist/${p}`)
|
||||
}
|
||||
|
||||
const clientPath = distResolve('./client')
|
||||
const { middleware: rpcMiddleware } = setupRPC(nuxt, options)
|
||||
|
||||
nuxt.hook('vite:serverCreated', async (server) => {
|
||||
server.middlewares.use(PATH_ENTRY, tinyws() as any)
|
||||
server.middlewares.use(PATH_ENTRY, rpcMiddleware as any)
|
||||
if (await pathExists(clientPath))
|
||||
server.middlewares.use(PATH_CLIENT, sirv(clientPath, { dev: true, single: true }))
|
||||
})
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-ts-expect-error
|
||||
// @ts-ignore runtime type
|
||||
nuxt.hook('devtools:customTabs', (iframeTabs) => {
|
||||
iframeTabs.push({
|
||||
name: 'mongoose',
|
||||
title: 'Mongoose',
|
||||
icon: 'skill-icons:mongodb',
|
||||
view: {
|
||||
type: 'iframe',
|
||||
src: PATH_CLIENT,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
// virtual imports
|
||||
@ -55,5 +91,7 @@ export default defineNuxtModule<ModuleOptions>({
|
||||
|
||||
// Add server-plugin for database connection
|
||||
addServerPlugin(resolve('./runtime/server/plugins/mongoose.db'))
|
||||
|
||||
logger.success('`nuxt-mongoose` is ready!')
|
||||
},
|
||||
})
|
||||
|
||||
100
src/server-rpc/index.ts
Normal file
100
src/server-rpc/index.ts
Normal file
@ -0,0 +1,100 @@
|
||||
import type { TinyWSRequest } from 'tinyws'
|
||||
import type { NodeIncomingMessage, NodeServerResponse } from 'h3'
|
||||
import type { WebSocket } from 'ws'
|
||||
import { createBirpcGroup } from 'birpc'
|
||||
import type { ChannelOptions } from 'birpc'
|
||||
|
||||
import { parse, stringify } from 'flatted'
|
||||
import type { Nuxt } from 'nuxt/schema'
|
||||
import type { ClientFunctions, ModuleOptions, NuxtDevtoolsServerContext, ServerFunctions } from '../types'
|
||||
|
||||
export function setupRPC(nuxt: Nuxt, options: ModuleOptions): any {
|
||||
const serverFunctions = {} as ServerFunctions
|
||||
const extendedRpcMap = new Map<string, any>()
|
||||
const rpc = createBirpcGroup<ClientFunctions, ServerFunctions>(
|
||||
serverFunctions,
|
||||
[],
|
||||
{
|
||||
resolver: (name, fn) => {
|
||||
if (fn)
|
||||
return fn
|
||||
|
||||
if (!name.includes(':'))
|
||||
return
|
||||
|
||||
const [namespace, fnName] = name.split(':')
|
||||
return extendedRpcMap.get(namespace)?.[fnName]
|
||||
},
|
||||
onError(error, name) {
|
||||
console.error(`[nuxt-devtools] RPC error on executing "${name}":`, error)
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
function refresh(event: keyof ServerFunctions) {
|
||||
rpc.broadcast.refresh.asEvent(event)
|
||||
}
|
||||
|
||||
function extendServerRpc(namespace: string, functions: any): any {
|
||||
extendedRpcMap.set(namespace, functions)
|
||||
|
||||
return {
|
||||
broadcast: new Proxy({}, {
|
||||
get: (_, key) => {
|
||||
if (typeof key !== 'string')
|
||||
return
|
||||
return (rpc.broadcast as any)[`${namespace}:${key}`]
|
||||
},
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
const ctx: NuxtDevtoolsServerContext = {
|
||||
nuxt,
|
||||
options,
|
||||
rpc,
|
||||
refresh,
|
||||
extendServerRpc,
|
||||
}
|
||||
|
||||
// @ts-expect-error untyped
|
||||
nuxt.devtools = ctx
|
||||
|
||||
Object.assign(serverFunctions, {
|
||||
// TODO: add rpc
|
||||
} satisfies Partial<ServerFunctions>)
|
||||
|
||||
const wsClients = new Set<WebSocket>()
|
||||
const middleware = async (req: NodeIncomingMessage & TinyWSRequest, _res: NodeServerResponse, next: Function) => {
|
||||
// Handle WebSocket
|
||||
if (req.ws) {
|
||||
const ws = await req.ws()
|
||||
wsClients.add(ws)
|
||||
const channel: ChannelOptions = {
|
||||
post: d => ws.send(d),
|
||||
on: fn => ws.on('message', fn),
|
||||
serialize: stringify,
|
||||
deserialize: parse,
|
||||
}
|
||||
rpc.updateChannels((c) => {
|
||||
c.push(channel)
|
||||
})
|
||||
ws.on('close', () => {
|
||||
wsClients.delete(ws)
|
||||
rpc.updateChannels((c) => {
|
||||
const index = c.indexOf(channel)
|
||||
if (index >= 0)
|
||||
c.splice(index, 1)
|
||||
})
|
||||
})
|
||||
}
|
||||
else {
|
||||
next()
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
middleware,
|
||||
...ctx,
|
||||
}
|
||||
}
|
||||
3
src/types/index.ts
Normal file
3
src/types/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export * from './rpc'
|
||||
export * from './server-ctx'
|
||||
export * from './module-options'
|
||||
7
src/types/module-options.ts
Normal file
7
src/types/module-options.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import type { ConnectOptions } from 'mongoose'
|
||||
|
||||
export interface ModuleOptions {
|
||||
uri: string
|
||||
devtools: boolean
|
||||
options?: ConnectOptions
|
||||
}
|
||||
7
src/types/rpc.ts
Normal file
7
src/types/rpc.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export interface ServerFunctions {
|
||||
listCollections(): Promise<any>
|
||||
}
|
||||
|
||||
export interface ClientFunctions {
|
||||
refresh(type: string): void
|
||||
}
|
||||
15
src/types/server-ctx.ts
Normal file
15
src/types/server-ctx.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import type { BirpcGroup } from 'birpc'
|
||||
import type { Nuxt } from 'nuxt/schema'
|
||||
import type { ClientFunctions, ServerFunctions } from './rpc'
|
||||
import type { ModuleOptions } from './module-options'
|
||||
|
||||
export interface NuxtDevtoolsServerContext {
|
||||
nuxt: Nuxt
|
||||
options: ModuleOptions
|
||||
|
||||
rpc: BirpcGroup<ClientFunctions, ServerFunctions>
|
||||
|
||||
refresh: (event: keyof ServerFunctions) => void
|
||||
|
||||
extendServerRpc: <ClientFunctions = {}, ServerFunctions = {}>(name: string, functions: ServerFunctions) => BirpcGroup<ClientFunctions, ServerFunctions>
|
||||
}
|
||||
Reference in New Issue
Block a user