From 156ab7c93c1cd86e55056676329ca514f5a28ad7 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Sat, 1 Mar 2025 08:11:23 +0000 Subject: [PATCH] feat(kit,nuxt): augment nitro types with `addTypeTemplate` (#31079) --- packages/kit/src/template.ts | 19 ++++++++--- packages/nuxt/src/core/templates.ts | 34 +++++++++++++++++-- packages/nuxt/src/pages/module.ts | 14 ++++++-- packages/schema/src/types/hooks.ts | 6 ++++ test/fixtures/basic-types/nuxt.config.ts | 2 +- .../basic-types/server/type-context.ts | 4 +++ 6 files changed, 69 insertions(+), 10 deletions(-) create mode 100644 test/fixtures/basic-types/server/type-context.ts diff --git a/packages/kit/src/template.ts b/packages/kit/src/template.ts index 22988897ab..d3249a7f2e 100644 --- a/packages/kit/src/template.ts +++ b/packages/kit/src/template.ts @@ -48,8 +48,12 @@ export function addServerTemplate (template: NuxtServerTemplate) { /** * Renders given types during build to disk in the project `buildDir` * and register them as types. + * + * You can pass a second context object to specify in which context the type should be added. + * + * If no context object is passed, then it will only be added to the nuxt context. */ -export function addTypeTemplate (_template: NuxtTypeTemplate) { +export function addTypeTemplate (_template: NuxtTypeTemplate, context?: { nitro?: boolean, nuxt?: boolean }) { const nuxt = useNuxt() const template = addTemplate(_template) @@ -59,9 +63,16 @@ export function addTypeTemplate (_template: NuxtTypeTemplate) { } // Add template to types reference - nuxt.hook('prepare:types', ({ references }) => { - references.push({ path: template.dst }) - }) + if (!context || context.nuxt) { + nuxt.hook('prepare:types', ({ references }) => { + references.push({ path: template.dst }) + }) + } + if (context?.nitro) { + nuxt.hook('nitro:prepare:types', ({ references }) => { + references.push({ path: template.dst }) + }) + } return template } diff --git a/packages/nuxt/src/core/templates.ts b/packages/nuxt/src/core/templates.ts index c96ca70458..ec174960b8 100644 --- a/packages/nuxt/src/core/templates.ts +++ b/packages/nuxt/src/core/templates.ts @@ -1,13 +1,13 @@ import { existsSync } from 'node:fs' import { genArrayFromRaw, genDynamicImport, genExport, genImport, genObjectFromRawEntries, genSafeVariableName, genString } from 'knitwork' -import { join, relative, resolve } from 'pathe' +import { isAbsolute, join, relative, resolve } from 'pathe' import type { JSValue } from 'untyped' import { generateTypes, resolveSchema } from 'untyped' import escapeRE from 'escape-string-regexp' import { hash } from 'ohash' import { camelCase } from 'scule' import { filename } from 'pathe/utils' -import type { NuxtOptions, NuxtTemplate, NuxtTypeTemplate } from 'nuxt/schema' +import type { NuxtOptions, NuxtTemplate, NuxtTypeTemplate, TSReference } from 'nuxt/schema' import type { Nitro } from 'nitropack' import { annotatePlugins, checkForCircularDependencies } from './app' @@ -331,10 +331,38 @@ export const middlewareTemplate: NuxtTemplate = { }, } +function renderAttr (key: string, value?: string) { + return value ? `${key}="${value}"` : '' +} + +function renderAttrs (obj: Record) { + const attrs: string[] = [] + for (const key in obj) { + attrs.push(renderAttr(key, obj[key])) + } + return attrs.join(' ') +} + export const nitroSchemaTemplate: NuxtTemplate = { filename: 'types/nitro-nuxt.d.ts', - getContents () { + async getContents ({ nuxt }) { + const references = [] as TSReference[] + const declarations = [] as string[] + await nuxt.callHook('nitro:prepare:types', { references, declarations }) + + const sourceDir = join(nuxt.options.buildDir, 'types') + const lines = [ + ...references.map((ref) => { + if ('path' in ref && isAbsolute(ref.path)) { + ref.path = relative(sourceDir, ref.path) + } + return `/// ` + }), + ...declarations, + ] + return /* typescript */` +${lines.join('\n')} /// import type { RuntimeConfig } from 'nuxt/schema' diff --git a/packages/nuxt/src/pages/module.ts b/packages/nuxt/src/pages/module.ts index 394abf9efa..3e5968bc95 100644 --- a/packages/nuxt/src/pages/module.ts +++ b/packages/nuxt/src/pages/module.ts @@ -158,7 +158,7 @@ export default defineNuxtModule({ '}', 'export {}', ].join('\n'), - }) + }, { nuxt: true, nitro: true }) addComponent({ name: 'NuxtPage', priority: 10, // built-in that we do not expect the user to override @@ -575,6 +575,16 @@ export default defineNuxtModule({ ' middleware?: MiddlewareKey | NavigationGuard | Array', ' }', '}', + ].join('\n') + }, + }) + + addTypeTemplate({ + filename: 'types/nitro-middleware.d.ts', + getContents: ({ app }) => { + const namedMiddleware = app.middleware.filter(mw => !mw.global) + return [ + `export type MiddlewareKey = ${namedMiddleware.map(mw => genString(mw.name)).join(' | ') || 'never'}`, 'declare module \'nitropack\' {', ' interface NitroRouteConfig {', ' appMiddleware?: MiddlewareKey | MiddlewareKey[] | Record', @@ -582,7 +592,7 @@ export default defineNuxtModule({ '}', ].join('\n') }, - }) + }, { nuxt: true, nitro: true }) addTypeTemplate({ filename: 'types/layouts.d.ts', diff --git a/packages/schema/src/types/hooks.ts b/packages/schema/src/types/hooks.ts index 633eb68ea7..7d06c6cf3a 100644 --- a/packages/schema/src/types/hooks.ts +++ b/packages/schema/src/types/hooks.ts @@ -257,6 +257,12 @@ export interface NuxtHooks { 'components:extend': (components: Component[]) => HookResult // Nitropack + /** + * Called before Nitro writes `.nuxt/tsconfig.server.json`, allowing addition of custom references and declarations. + * @param options Objects containing `references`, `declarations` + * @returns Promise + */ + 'nitro:prepare:types': (options: { references: TSReference[], declarations: string[] }) => HookResult /** * Called before initializing Nitro, allowing customization of Nitro's configuration. * @param nitroConfig The nitro config to be extended diff --git a/test/fixtures/basic-types/nuxt.config.ts b/test/fixtures/basic-types/nuxt.config.ts index d3f2f922f1..885603b5cc 100644 --- a/test/fixtures/basic-types/nuxt.config.ts +++ b/test/fixtures/basic-types/nuxt.config.ts @@ -10,7 +10,7 @@ export default defineNuxtConfig({ addTypeTemplate({ filename: 'test.d.ts', getContents: () => 'declare type Fromage = "cheese"', - }) + }, { nuxt: true, nitro: true }) function _test () { installModule('~/modules/example', { typeTest (val) { diff --git a/test/fixtures/basic-types/server/type-context.ts b/test/fixtures/basic-types/server/type-context.ts new file mode 100644 index 0000000000..8994c5a431 --- /dev/null +++ b/test/fixtures/basic-types/server/type-context.ts @@ -0,0 +1,4 @@ +// @ts-expect-error Fromage is 'cheese' +const _fake: Fromage = 'babybel' + +const _fromage: Fromage = 'cheese'