feat: initial setup for nuxt devtools

This commit is contained in:
arashsheyda
2023-04-20 18:16:10 +03:00
parent 7301bb390c
commit 7df0af10f2
15 changed files with 1979 additions and 488 deletions

1
client/.nuxtrc Normal file
View File

@ -0,0 +1 @@
imports.autoImport=true

15
client/app.vue Normal file
View File

@ -0,0 +1,15 @@
<template>
<Html>
<Body h-screen>
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
</Body>
</Html>
</template>
<style>
#__nuxt {
height: 100%;
}
</style>

17
client/nuxt.config.ts Normal file
View File

@ -0,0 +1,17 @@
import { resolve } from 'pathe'
import { PATH_CLIENT } from '../src/constants'
export default defineNuxtConfig({
ssr: false,
modules: [
'@nuxt/devtools-ui-kit',
],
nitro: {
output: {
publicDir: resolve(__dirname, '../dist/client'),
},
},
app: {
baseURL: PATH_CLIENT,
},
})

4
client/package.json Normal file
View File

@ -0,0 +1,4 @@
{
"name": "nuxt-mongoose-client",
"private": true
}

View File

@ -21,11 +21,14 @@
"dist" "dist"
], ],
"scripts": { "scripts": {
"prepack": "nuxt-module-build", "prepack": "nuxt-module-build && npm run build:client",
"dev": "nuxi dev playground", "build:client": "nuxi generate client",
"dev:build": "nuxi build playground", "dev:client": "nuxi dev client --port 3300",
"dev:prepare": "nuxt-module-build --stub && nuxi prepare playground", "dev": "npm run play:dev",
"release": "npm run lint && npm run prepack && changelogen --release && npm publish && git push --follow-tags", "dev:prepare": "nuxt-module-build --stub && nuxi prepare client",
"play:dev": "nuxi dev playground",
"play:prod": "npm run prepack && nuxi dev playground",
"release": "npm run lint && npm run test && npm run prepack && changelogen --release && npm publish && git push --follow-tags",
"lint": "eslint .", "lint": "eslint .",
"test": "vitest run", "test": "vitest run",
"test:watch": "vitest watch" "test:watch": "vitest watch"
@ -35,17 +38,30 @@
"nuxt": "^3.4.1" "nuxt": "^3.4.1"
}, },
"dependencies": { "dependencies": {
"@nuxt/devtools-kit": "^0.4.1",
"@nuxt/kit": "^3.4.1", "@nuxt/kit": "^3.4.1",
"@types/fs-extra": "^11.0.1",
"birpc": "^0.2.11",
"defu": "^6.1.2", "defu": "^6.1.2",
"mongoose": "^7.0.3" "flatted": "^3.2.7",
"fs-extra": "^11.1.1",
"pluralize": "^8.0.0",
"sirv": "^2.0.2",
"tinyws": "^0.1.0",
"ws": "^8.13.0"
}, },
"devDependencies": { "devDependencies": {
"@antfu/eslint-config": "^0.38.4", "@antfu/eslint-config": "^0.38.4",
"@nuxt/devtools": "^0.4.1",
"@nuxt/devtools-ui-kit": "^0.4.1",
"@nuxt/module-builder": "^0.3.0", "@nuxt/module-builder": "^0.3.0",
"@nuxt/schema": "^3.4.1", "@nuxt/schema": "^3.4.1",
"@nuxt/test-utils": "^3.4.1", "@nuxt/test-utils": "^3.4.1",
"@types/pluralize": "^0.0.29",
"@types/ws": "^8.5.4",
"changelogen": "^0.5.3", "changelogen": "^0.5.3",
"eslint": "^8.38.0", "eslint": "^8.38.0",
"mongoose": "^7.0.4",
"nuxt": "^3.4.1", "nuxt": "^3.4.1",
"vitest": "^0.30.1" "vitest": "^0.30.1"
} }

View File

@ -1,7 +1,28 @@
export default defineNuxtConfig({ import { resolve } from 'node:path'
modules: ['../src/module'], import { defineNuxtModule } from '@nuxt/kit'
import { startSubprocess } from '@nuxt/devtools-kit'
mongoose: { export default defineNuxtConfig({
uri: 'mongodb://127.0.0.1/nuxt-mongoose', modules: [
}, '@nuxt/devtools',
'../src/module',
defineNuxtModule({
setup(_, nuxt) {
if (!nuxt.options.dev)
return
const process = startSubprocess(
{
command: 'npx',
args: ['nuxi', 'dev', '--port', '3300'],
cwd: resolve(__dirname, '../client'),
},
{
id: 'nuxt-mongoose:client',
name: 'Nuxt Mongoose Client Dev',
},
)
},
}),
],
}) })

2182
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

3
src/constants/index.ts Normal file
View File

@ -0,0 +1,3 @@
export const PATH = '/__nuxt_mongoose__'
export const PATH_ENTRY = `${PATH}/entry`
export const PATH_CLIENT = `${PATH}/client`

View File

@ -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 { defu } from 'defu'
import type { ConnectOptions } from 'mongoose' import sirv from 'sirv'
export interface ModuleOptions { import { PATH_CLIENT, PATH_ENTRY } from './constants'
uri?: string import type { ModuleOptions } from './types'
options?: ConnectOptions
} import { setupRPC } from './server-rpc'
export default defineNuxtModule<ModuleOptions>({ export default defineNuxtModule<ModuleOptions>({
meta: { meta: {
@ -14,6 +16,7 @@ export default defineNuxtModule<ModuleOptions>({
}, },
defaults: { defaults: {
uri: process.env.MONGODB_URI as string, uri: process.env.MONGODB_URI as string,
devtools: true,
options: {}, options: {},
}, },
setup(options, nuxt) { setup(options, nuxt) {
@ -26,6 +29,39 @@ export default defineNuxtModule<ModuleOptions>({
nuxt.options.runtimeConfig.public.mongoose = defu(nuxt.options.runtimeConfig.public.mongoose || {}, { nuxt.options.runtimeConfig.public.mongoose = defu(nuxt.options.runtimeConfig.public.mongoose || {}, {
uri: options.uri, uri: options.uri,
options: options.options, 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 // virtual imports
@ -55,5 +91,7 @@ export default defineNuxtModule<ModuleOptions>({
// Add server-plugin for database connection // Add server-plugin for database connection
addServerPlugin(resolve('./runtime/server/plugins/mongoose.db')) addServerPlugin(resolve('./runtime/server/plugins/mongoose.db'))
logger.success('`nuxt-mongoose` is ready!')
}, },
}) })

100
src/server-rpc/index.ts Normal file
View 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
View File

@ -0,0 +1,3 @@
export * from './rpc'
export * from './server-ctx'
export * from './module-options'

View 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
View 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
View 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>
}

View File

@ -1,3 +1,3 @@
{ {
"extends": "./playground/.nuxt/tsconfig.json" "extends": "./client/.nuxt/tsconfig.json"
} }