feat(nitro): automatically type middleware/api routes (#708)

This commit is contained in:
Daniel Roe 2021-10-11 14:29:02 +01:00 committed by GitHub
parent 18cf0ba865
commit b005b2403f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 84 additions and 29 deletions

1
.gitignore vendored
View File

@ -22,6 +22,7 @@ dist
.nuxt*
.output
.gen
nuxt.d.ts
# Junit reports
reports

View File

@ -123,6 +123,11 @@ export function setupNitroBridge () {
nitroDevContext.middleware.push(...middleware)
})
// Add typed route responses
nuxt.hook('prepare:types', (opts) => {
opts.references.push({ path: resolve(nuxt.options.buildDir, 'nitro.d.ts') })
})
// nuxt build/dev
// @ts-ignore
nuxt.options.build._minifyServer = false

View File

@ -1,4 +1,4 @@
import { resolve, join } from 'pathe'
import { relative, resolve, join } from 'pathe'
import consola from 'consola'
import { rollup, watch as rollupWatch } from 'rollup'
import fse from 'fs-extra'
@ -55,9 +55,38 @@ export async function build (nitroContext: NitroContext) {
nitroContext.rollupConfig = getRollupConfig(nitroContext)
await nitroContext._internal.hooks.callHook('nitro:rollup:before', nitroContext)
await writeTypes(nitroContext)
return nitroContext._nuxt.dev ? _watch(nitroContext) : _build(nitroContext)
}
async function writeTypes (nitroContext: NitroContext) {
const routeTypes: Record<string, string[]> = {}
const middleware = [
...nitroContext.scannedMiddleware,
...nitroContext.middleware
]
for (const mw of middleware) {
if (typeof mw.handle !== 'string') { continue }
const relativePath = relative(nitroContext._nuxt.buildDir, mw.handle).replace(/\.[a-z]+$/, '')
routeTypes[mw.route] = routeTypes[mw.route] || []
routeTypes[mw.route].push(`ReturnType<typeof import('${relativePath}').default>`)
}
const lines = [
'declare module \'@nuxt/nitro\' {',
' interface InternalApi {',
...Object.entries(routeTypes).map(([path, types]) => ` '${path}': ${types.join(' | ')}`),
' }',
'}',
// Makes this a module for augmentation purposes
'export {}'
]
await writeFile(join(nitroContext._nuxt.buildDir, 'nitro.d.ts'), lines.join('\n'))
}
async function _build (nitroContext: NitroContext) {
nitroContext.scannedMiddleware = await scanMiddleware(nitroContext._nuxt.serverDir)
@ -117,6 +146,7 @@ async function _watch (nitroContext: NitroContext) {
nitroContext.scannedMiddleware = middleware
if (['add', 'addDir'].includes(event)) {
watcher.close()
writeTypes(nitroContext).catch(console.error)
watcher = startRollupWatcher(nitroContext)
}
}

41
packages/nitro/types/fetch.d.ts vendored Normal file
View File

@ -0,0 +1,41 @@
import type { FetchRequest, FetchOptions, FetchResponse } from 'ohmyfetch'
// An interface to extend in a local project
export declare interface InternalApi { }
export declare type ValueOf<C> = C extends Record<any, any> ? C[keyof C] : never
export declare type MatchedRoutes<Route extends string> = ValueOf<{
// exact match, prefix match or root middleware
[key in keyof InternalApi]: Route extends key | `${key}/${string}` | '/' ? key : never
}>
export declare type MiddlewareOf<Route extends string> = Exclude<InternalApi[MatchedRoutes<Route>], Error | void>
export declare type TypedInternalResponse<Route, Default> =
Default extends string | boolean | number | null | void | object
// Allow user overrides
? Default
: Route extends string
? MiddlewareOf<Route> extends never
// Bail if only types are Error or void (for example, from middleware)
? Default
: MiddlewareOf<Route>
: Default
export declare interface $Fetch {
<T = unknown, R extends FetchRequest = FetchRequest> (request: R, opts?: FetchOptions): Promise<TypedInternalResponse<R, T>>
raw<T = unknown, R extends FetchRequest = FetchRequest> (request: R, opts?: FetchOptions): Promise<FetchResponse<TypedInternalResponse<R, T>>>
}
declare global {
// eslint-disable-next-line no-var
var $fetch: $Fetch
namespace NodeJS {
interface Global {
$fetch: $Fetch
}
}
}
export default {}

View File

@ -7,4 +7,5 @@ declare module '@nuxt/kit' {
}
}
export * from './fetch'
export * from '../dist'

View File

@ -1,15 +1,3 @@
declare global {
import type { $Fetch } from 'ohmyfetch'
// eslint-disable-next-line no-var
var $fetch: $Fetch
namespace NodeJS {
interface Global {
$fetch: $Fetch
}
}
}
declare module '#storage' {
import type { Storage } from 'unstorage'
export const storage: Storage
@ -21,5 +9,3 @@ declare module '#assets' {
export function statAsset(id: string): Promise<AssetMeta>
export function getKeys() : Promise<string[]>
}
export default {}

View File

@ -1,15 +1,8 @@
import { Component } from '@vue/runtime-core'
import { $Fetch } from 'ohmyfetch'
import { NuxtApp } from '../nuxt'
declare global {
// eslint-disable-next-line no-var
var $fetch: $Fetch
namespace NodeJS {
interface Global {
$fetch: $Fetch
}
interface Process {
browser: boolean
client: boolean

View File

@ -26,6 +26,11 @@ export function initNitro (nuxt: Nuxt) {
nuxt.hook('close', () => nitroDevContext._internal.hooks.callHook('close'))
nitroDevContext._internal.hooks.hook('nitro:document', template => nuxt.callHook('nitro:document', template))
// Add typed route responses
nuxt.hook('prepare:types', (opts) => {
opts.references.push({ path: resolve(nuxt.options.buildDir, 'nitro.d.ts') })
})
// Add nitro client plugin (to inject $fetch helper)
nuxt.hook('app:resolve', (app) => {
app.plugins.push({ src: resolve(nitroContext._internal.runtimeDir, 'app/nitro.client.mjs') })

View File

@ -1,7 +0,0 @@
// This file is auto generated by `nuxt prepare`
// Please do not manually modify this file.
/// <reference types="nuxt3" />
/// <reference path=".nuxt/components.d.ts" />
/// <reference path=".nuxt/auto-imports.d.ts" />
export {}