服务器兼容性

构建一个模块,同时支持 Nuxt 4 和 Nuxt 5,无论底层使用哪种服务器运行时。

Maschinelle Übersetzung ·0 % geprüft

Nuxt 支持多种服务器运行时。Nuxt 3 和 4 使用 Nitro v2(nitropack),它使用 h3 v1;Nuxt 5 默认使用 Nitro v3(nitro),它使用 h3 v2。也可以使用基于 Web API 的自定义服务器运行时,该运行时在任何环境中都兼容(nuxt/server)。

本指南介绍如何编写一个服务器代码可在所有这些环境中运行的模块。

如果你的模块没有服务器代码,则无需关注本文内容。

推荐迁移方式

nuxt/server 是基于 Web API 编写通用服务器代码的导入入口,并随 Nuxt 4.6+ 一同发布。使用它编写的代码可运行于 nitropack v2、Nitro v3 以及其他服务器构建器(例如 @nuxt/vite-server)之下。

事件处理器

如果你要注册事件处理器,请保留当前的处理器(以兼容 Nuxt <4.6),并使用 nuxt/server 添加一个新的可移植处理器,然后同时注册两者。Nuxt 会注册应用可以运行的实现:在 Nitro v3 和其他构建器下使用可移植文件,在仍运行 nitropack v2 的主机上使用 v2 文件。

module.ts
import { addServerHandler, createResolver, defineNuxtModule } from '@nuxt/kit'

export default defineNuxtModule({
  meta: { name: 'my-module' },
  setup () {
    const { resolve } = createResolver(import.meta.url)

    addServerHandler({
      route: '/api/my-module/status',
      handler: {
        nuxt: resolve('./runtime/server/status'),
        nitro2: resolve('./runtime/server/status.legacy'),
      },
    })
  },
})

两个文件的差异包括导入,以及处理程序中使用 h3 v1 辅助函数或 nuxt/server 行为不同的部分:

import { defineEventHandler, useRuntimeConfig } from 'nuxt/server'

export default defineEventHandler(() => ({
  version: useRuntimeConfig().public.myModule.version,
}))
查看 h3 v1 与 nuxt/server 的所有差异,以及对应的替代方案。

这些键指出了每个文件所依赖的服务器 API:

键文件导入的内容运行位置
nuxt仅 nuxt/serverNuxt 4.6 起支持任何服务器构建器
nitro2h3、nitropack/runtime、#importsnitropack v2
nitro3nitro、nitro/h3,可单独使用,也可与 nuxt/server 一起使用Nitro 服务器构建器 v3

Nuxt 会根据用户使用的服务器构建器,选择最合适的处理器。如果你注册的处理器无法由用户的服务器构建器运行,则会跳过该处理器并发出警告。

仅在代码需要 Nitro v3 独有的 API 时使用 nitro3。使用 nuxt/server 能让你的代码在未来保持更好的可移植性。

使用此模式意味着你将同时支持 Nuxt 的旧版本(<4.6)和新版本(包括 Nuxt 5)。

对象形式的 handler 需要在模块的 dependencies 中声明 @nuxt/kit@^4.6,模块 starter 会为你完成此设置。如果 @nuxt/kit 只是 devDependency 且打包时保留为外部依赖,或被设为 peerDependency,则会使用应用程序的版本;低于 4.6 的版本无法识别对象形式的 handler。

要对 nuxt 文件进行类型检查,请将 nuxt@>=4.6 安装为 devDependency。要对 nitro3 文件进行类型检查,还需安装 nitro。

如果模块自己的别名在 Nitro 两个主版本中都能解析到正确代码,可以为两个键注册同一个文件({ nitro2: file, nitro3: file }),或设置 meta.compatibility.server。

在使用 nitropack v2 的主机上,Nuxt 优先选择 nitro2 变体,因此只有在它是唯一注册项时才会运行 nuxt 变体。请在 Nuxt 5 上测试 nuxt 变体。

在 Nuxt 5 的 playground 和测试 fixture 中,h3 自动导入的 defineEventHandler 不可用;请从 nuxt/server 导入。

开发服务器处理程序

addDevServerHandler 使用相同的键,但每个变体都是模块代码中的函数,并在 Nuxt 进程中运行,而非服务器构建产物中运行。nuxt/server 仅供服务器代码使用,因此不要从模块代码中导入它,即使是延迟导入也不行。

请直接使用 Web Request 和 Response 编写处理程序,在 setup 中读取 nuxt.options 配置,再针对各服务器 API 适配:

module.ts
import { addDevServerHandler, defineNuxtModule } from '@nuxt/kit'
import { defineEventHandler, toWebRequest } from 'h3'

export default defineNuxtModule({
  meta: { name: 'my-module' },
  setup (_options, nuxt) {
    const greeting = nuxt.options.runtimeConfig.public.myModule.greeting

    function greet (request: Request) {
      const name = new URL(request.url).searchParams.get('name')
      if (!name) {
        return new Response('Missing name', { status: 400 })
      }
      return Response.json({ message: `${greeting}, ${name}!` })
    }

    addDevServerHandler({
      route: '/_my-module/greet',
      handler: {
        nuxt: event => greet(event.req),
        nitro2: defineEventHandler(event => greet(toWebRequest(event))),
      },
    })
  },
})

nuxt 变体接收 RequestEvent,并从 event.req 读取请求;nitro2 变体会使用 toWebRequest() 转换 h3 v1 event。由于模块导入了 h3 v1,请在模块的 dependencies 中添加 h3@^1。

开发处理程序的路由在不同主机上的匹配方式不同。在使用 nitropack v2 的主机上,所有变体都像 Nitro v2 一样按路径前缀匹配,因此 /_my-module 也会匹配 /_my-module/inter.woff2。在 Nitro v3 上,nuxt 和 nitro3 变体按完整路由精确匹配。

若要让所有变体都处理路由下方的路径,请在路由末尾添加 /**:

module.ts
addDevServerHandler({
  route: '/_my-module/assets/**',
  handler: {
    nuxt: event => serveAsset(event.req),
    nitro2: defineEventHandler(event => serveAsset(toWebRequest(event))),
  },
})

此处的 serveAsset 与前例中的 greet 一样,接收 Request 并返回 Response。该路由会匹配 /_my-module/assets 及其下的所有路径,因此不需要再注册基础路径。在使用 nitropack v2 的主机上,这需要在模块的 dependencies 中声明 @nuxt/kit@^4.6.1。

在使用 nitropack v2 的主机上,处理程序读取的 event.path 相对于其路由基路径,因为 Nitro v2 会去掉这段前缀。请求 /_my-module/assets/inter.woff2 时,event.path 是 /inter.woff2。完整 URL 仍可通过 toWebRequest() 获取,nuxt 变体则从 event.req.url 获取。

服务器工具函数

如果模块公开的工具函数通过别名、addServerImports 或类型模板接收 event,请将参数类型标注为 nuxt/server 中的 RequestEvent,或标注为其读取的字段子集,例如 Pick<RequestEvent, 'req' | 'context'>。类型为 H3Event 的参数只能接受某一个 h3 主版本的 event,因此无法从 nuxt/server 处理程序调用。

getNitroVersion 返回的是 Nitro 主版本,而不是主机运行的服务器 API;使用其他 server.builder 的主机不会运行 Nitro 的变体。

服务器插件

服务器插件也支持变体,但它们专用于 Nitro,因此没有可供注册的通用 nuxt 变体。

由于 nitro 中的 definePlugin() 和 nitropack/runtime 中的 defineNitroPlugin() 都是恒等函数,你可以将它们替换为类型注解,从而编写同时适用于 nitro2 和 nitro3 的代码。
module.ts
import { addNitroPlugin, createResolver, defineNuxtModule } from '@nuxt/kit'

export default defineNuxtModule({
  meta: { name: 'my-module' },
  setup () {
    const { resolve } = createResolver(import.meta.url)

    addNitroPlugin({
      nitro2: resolve('./runtime/server/plugin.legacy'),
      nitro3: resolve('./runtime/server/plugin'),
    })
  },
})

其他变体

对于指向服务器代码的其他内容,例如别名、类型模板或读取服务器 event 的应用插件,可以使用 resolveServerVariant 解析适用于当前 Nuxt 版本的变体;如果没有适用变体,则返回 undefined。

addServerImports 的 from 属性也接受相同的变体形式。

module.ts
import { addServerImports, createResolver, defineNuxtModule, resolveServerVariant } from '@nuxt/kit'

export default defineNuxtModule({
  meta: { name: 'my-module' },
  setup (_options, nuxt) {
    const { resolve } = createResolver(import.meta.url)

    const someServerFile = resolveServerVariant({
      nuxt: resolve('./runtime/server'),
      nitro2: resolve('./runtime/server.legacy'),
    })

    if (someServerFile) {
      nuxt.options.alias['#my-module/server'] = someServerFile
    }

    addServerImports({
      name: 'verifyToken',
      from: { nuxt: resolve('./runtime/verify'), nitro2: resolve('./runtime/verify.legacy') },
    })
  },
})

在使用 nitropack v2 时会优先选择 nitro2,使用 Nitro v3 时会优先选择 nitro3。因此,如果要测试 nuxt 变体,请在测试 fixture 中将 server.builder 设为非 Nitro 构建器,例如 @nuxt/vite-server。

声明无法推断的内容

Nuxt 会读取已注册文件的导入项,以判断该文件使用的 API,因此你无需声明其兼容性。但如果这还不够,你可以设置 meta.compatibility.server:

module.ts
export default defineNuxtModule({
  meta: {
    name: 'my-module',
    compatibility: { server: 'nuxt' },
  },
})

如果我们无法判断,代码将被视为 nitro2。

类型化运行时配置和 event

要为模块添加的运行时配置键提供类型,请从 nuxt/schema 扩展 RuntimeConfig(或 PublicRuntimeConfig)。这些键在 nuxt、nitro2 和 nitro3 文件中都会有类型。

module.ts
declare module 'nuxt/schema' {
  interface RuntimeConfig {
    myModule: { apiKey: string }
  }
}

构建器特定的类型(例如 useRequestEvent() 返回的 H3Event,或 nitro:config 钩子中的完整 Nitro 配置)来自生成的 .nuxt/nuxt.d.ts。没有该文件时,它们会解析为与构建器无关的类型(RequestEvent 和精简版 Nitro 配置)。若要支持任何服务器构建器,类型工具函数应接受 RequestEvent。

插件、存储和缓存

基于服务器插件、存储或缓存构建的模块没有 nuxt 变体,因为 nuxt/server 不涵盖这些领域。现在请将相关文件注册为 nitro2,移植后再添加 nitro3 变体。

Nitro 专用代码

nuxt/server 涵盖请求和响应相关工作:处理器、错误、路由参数、查询和请求体的读取与验证、标头、CORS、Cookie、会话、重定向、路由规则、运行时配置、应用配置、通过 serverFetch() 进行进程内 fetch,以及通过 useServerHooks() 使用运行时钩子。但 Nitro 涵盖的内容远不止这些,因此如果你需要以下任一功能,可能需要在 nitro2 或 nitro3 文件中继续使用 Nitro 自身的导入项。

使用与文件对应版本的模块说明符。导入 nitro/* 的文件会被视为 Nitro 3 代码,因此 nitro2 文件必须改为导入 h3 和 nitropack/*:

区域在 nitro2 文件中在 nitro3 文件中
存储:useStorage() 及其驱动配置nitropack/runtimenitro/storage
缓存:defineCachedEventHandler()、defineCachedFunction()nitropack/runtimenitro/cache
服务器插件来自 nitropack/runtime 的 defineNitroPlugin()来自 nitro 的 definePlugin()
Nitro 自身的运行时钩子,例如 request 和 error来自 nitropack/runtime 的 useNitroApp().hooks来自 nitro/app 的 useNitroHooks()
延迟处理器:lazyEventHandler()h3nitro/h3

nuxt/server 也不支持任务。

了解哪些 h3 辅助函数适用于可移植事件,哪些需要 Nitro 专用工具。

Nitro v3 中值得注意的变更

如果你只发布 nitro2 文件,它仍可通过 Nitro v2 兼容层在 Nuxt 5 上运行。相关说明以及 Nitro v3 中发生变化的行为,请参阅 Nuxt 5 模块服务器兼容性指南。