Nuxt 模块作者迁移指南:让 Nuxt 2 模块兼容 Nuxt 3(@nuxt/kit、Nuxt Bridge 与 ESM 改造实践)
模块(module)是扩展 Nuxt 能力的标准方式,而 Nuxt 3 是一次彻底重写:从 Vue 2 迁移到 Vue 3、从 webpack 4 + Babel 迁移到 Vite/webpack 5 + esbuild,更重要的是把 Nuxt 从"运行时依赖"变为"仅构建期依赖"。这篇指南以 Nuxt 官方迁移文档《Modules》为骨架,面向仍然维护着 Nuxt 2 生态模块的作者,系统讲解在保留既有代码的同时让模块兼容 Nuxt 3 的最小改造成路径:模块兼容层与兼容性边界、@nuxt/kit 对模块容器的替代、通过 Nuxt Bridge 先行验证、CommonJS 到 ESM 的迁移、插件默认导出修正,以及"避免运行时模块"的关键设计约束。读完你既能照单执行每一条改造步骤,也能理解 Nuxt 模块系统在构建期的真实运行机制。
模块兼容性(Module Compatibility)总览
Nuxt 3 为 Nuxt 2 模块提供了一个基础向后兼容层:借助 @nuxt/kit 的自动包装(auto wrapper),一部分旧的模块调用方式仍能工作。但官方明确强调:这只是兜底,通常还需要你手动做若干适配,才能真正兼容 Nuxt 3;在某些场景下,甚至必须引入 Nuxt Bridge 才能实现"同一份代码同时兼容 Nuxt 2 与 Nuxt 3"。
目前官方推荐的最优迁移路径是参考仓库中的模块编写指南(模块指南入口),使用 @nuxt/kit 提供的工具函数把模块整体重写。本迁移指南(docs/7.migration/20.module-authors.md)则提供另一套思路:如果你暂时不想完全重写,可以只做本节列出的准备工作,先让既有模块在 Nuxt 3 下可用,再逐步演进。
在动手前,请先对照下面两个兼容性边界判断你的模块受影响面:
插件兼容性(Plugin Compatibility)
Nuxt 3 插件并非与 Nuxt 2 完全向后兼容。二者的插件系统差异较大:
- Nuxt 2 中插件通过
this.$xxx注入到 Vue 实例,并依赖context参数(如app、route、store); - Nuxt 3 中插件接收
nuxtApp参数(详见 plugins 目录文档),一切能力通过nuxtApp.provide、组合式函数与运行时钩子暴露。
如果你注入的是一个"全局 Vue 插件"性质的 Nuxt 插件,除了改参数签名,还必须保证插件文件存在默认导出(详见下文"确保插件默认导出"一节)。
Vue 兼容性(Vue Compatibility)
凡是使用 Composition API 的插件或组件,都需要独占的 Vue 2 或 Vue 3 支持——Nuxt 2 构建在 Vue 2 上,Nuxt 3 构建在 Vue 3 上,两者运行时不能混用。
让同一份代码同时服务 Nuxt 2 与 Nuxt 3 的常规做法是引入 vue-demi(VueUse 团队维护的"半版本"兼容库):它会根据宿主应用实际安装的 Vue 主版本,自动切换到对应的 @vue/composition-api 或原生 Vue 3 API,从而让基于 Composition API 的插件与组件在两端行为一致。
迁移第一步:告别模块容器,拥抱 @nuxt/kit
当 Nuxt 3 用户安装你的模块时,模块将不再能访问模块容器 this.*(Nuxt 2 时代的 this.options、this.nuxt、this.addPlugin()、this.extendBuild() 等成员方法)。作为替代,模块作者需要使用 @nuxt/kit 暴露的工具函数来访问容器提供的全部功能。
这一变化在仓库源码中有清晰印证:@nuxt/kit 的公共导出(见 packages/kit/src/index.ts)不再提供"模块容器实例"式的 API,而是提供一批独立函数,例如:
- 模块定义:
defineNuxtModule、installModule - 插件/组件注入:
addPlugin、addPluginTemplate、addComponent、addComponentsDir、addLayout - 页面与路由:
extendPages、addRouteMiddleware、extendRouteRules - 服务端(Nitro):
addServerHandler、addDevServerHandler、addServerPlugin - 配置与模板:
addTemplate、updateRuntimeConfig、extendViteConfig、extendWebpackConfig - 兼容性:
checkNuxtCompatibility、assertNuxtCompatibility、hasNuxtCompatibility
官方强烈推荐用 defineNuxtModule 定义模块。底层模块定义本身只是一个接收 (inlineOptions, nuxt) 的函数(模块结构文档):
export default defineNuxtModule({
meta: {
name: '@nuxtjs/example', // 通常是 npm 包名
configKey: 'sample', // nuxt.config 中承载模块选项的键
compatibility: { nuxt: '>=3.0.0' },
},
defaults: {},
setup (moduleOptions, nuxt) {
// 模块逻辑
},
})
从 packages/kit/src/module/define.ts 的实现看,defineNuxtModule 会为模块自动完成以下工作:
- 用
defu合并inlineOptions、nuxt.config中configKey对应的选项与defaults(并可基于schema进一步校验默认值); - 依据
meta.name/meta.configKey生成唯一键,通过nuxt.options._requiredModules保证模块只被安装一次(见normalizedModule中的去重分支); - 自动把
hooks字段注册到 Nuxt 生命周期钩子上(nuxt.hooks.addHooks); - 基于
meta.compatibility自动执行兼容性检查,不满足且开启experimental.enforceModuleCompatibility时直接抛出ModuleCompatibilityError; - 在
setup执行前后为nuxt._perf记录module:*性能阶段; - 挂载
getMeta/getOptions/getModuleDependencies等内部辅助方法。
这些自动化行为正是 Nuxt 2 模块容器中"手写合并选项、手动防重装"等样板代码的替代品——迁移模块本质上就是把旧容器 API 的调用,逐个翻译成 @nuxt/kit 的独立函数调用。
先用 @nuxt/bridge 验证
官方把"迁移到 @nuxt/bridge"称为支持 Nuxt 3 的第一步也是最关键的一步。Nuxt Bridge 是一个前向兼容层(详见 Bridge 概览):在 Nuxt 2 项目中安装并启用 @nuxt/bridge 模块后,即可提前体验并接近 Nuxt 3 的能力,同时保留在 Nuxt 2 上运行的能力。
如果你的模块带有 fixture 或示例项目,请把 @nuxt/bridge 加入其配置。例如在示例应用的 nuxt.config.ts 中(参照 Bridge 配置示例 的"更新 nuxt.config"部分):
// nuxt.config.ts —— 使用 ESM 语法,避免 module.exports/require
import { defineNuxtConfig } from '@nuxt/bridge'
export default defineNuxtConfig({
bridge: false, // 先关闭,验证无 Bridge 时行为不变
})
并在项目里安装依赖与切换命令:
# 安装(在 Nuxt 2 项目内执行)
npm install -D @nuxt/bridge nuxi
"scripts": {
- "dev": "nuxt",
+ "dev": "nuxt2",
- "build": "nuxt build",
+ "build": "nuxt2 build",
- "start": "nuxt start",
+ "start": "nuxt2 start"
}
注意:Bridge 虽然提供与 Nuxt 3 几乎一致的特性,但存在限制,例如 useAsyncData 与 useFetch 组合式函数不可用。把模块放进启用 Bridge 的 fixture 中跑通,能尽早暴露"只兼容 Nuxt 2、不兼容 Nuxt 3 插件/组合式 API"的问题。
从 CommonJS 迁移到 ESM
Nuxt 3 原生支持 TypeScript 与 ECMAScript Modules,模块代码必须能在一个原生 ESM 的 Node.js 上下文里被加载。这意味着:
- 优先用静态
import/export语法,而不是require/module.exports; - 不要把构建产物命名为
.esm.js、.es.js之类只对打包器有效的约定——Node.js 原生 ESM 只认.mjs(ESM)、.cjs(CJS),以及package.json中声明了"type": "module"的.js文件; - 建议在
package.json中使用带条件导出的exports字段精确声明模块系统。
{
"name": "my-module",
"exports": {
".": {
"import": "./dist/mymodule.mjs"
}
}
}
在 ESM 环境下 require、require.resolve、__filename、__dirname 均不可用,需要替换为 import()、import.meta.url 对应的写法。ESM 与 CJS 的完整背景、报错形式(如 SyntaxError: Unexpected token 'export'、Named export not found)以及互操作(interop)陷阱,参见仓库中的 原生 ES Modules 概念文档,其中还包含面向库作者的两条修复路径:将 ESM 文件重命名为 .mjs,或让整个库只提供 ESM 输出。
确保插件默认导出
如果你通过模块注入的 Nuxt 插件没有 export default(典型的如直接调用 Vue.use() 的全局 Vue 插件),那么这种插件在 Nuxt 3 中可能根本不会被执行——Nuxt 3 期望每个插件文件默认导出一个插件函数。修复方式是在插件文件末尾补上一个空的默认导出:
::: code-group
// ~/plugins/vuelidate.js
import Vue from 'vue'
import Vuelidate from 'vuelidate'
Vue.use(Vuelidate)
// ~/plugins/vuelidate.js
import Vue from 'vue'
import Vuelidate from 'vuelidate'
Vue.use(Vuelidate)
export default () => { }
:::
迁移到 Nuxt 3 后,这类代码还应当进一步改造成新的插件形态(接收 nuxtApp 并调用 nuxtApp.vueApp.use(Vuelidate)),完整写法参考 plugins 目录文档。
避免运行时模块(Avoid Runtime Modules)
Nuxt 3 中 Nuxt 只是一个构建期依赖,模块不应试图在 Nuxt 运行时里"挂钩子"或"常驻运行"。这意味着你的模块应当做到:即使只被添加到 buildModules(而不是 modules)也能正常工作。本指南列出三类需要避开的运行时耦合,全部可以用 Nuxt 3 官方机制替代:
用 runtimeConfig 替代 process.env 读写
不要在模块构建期写 process.env、再让运行时插件读取它。环境相关的配置应交给 runtimeConfig:
// 模块内(构建期)不再这样写:
// process.env.MY_FLAG = '1'
// 改为:通过 @nuxt/kit 更新 runtimeConfig
import { defineNuxtModule, updateRuntimeConfig } from '@nuxt/kit'
export default defineNuxtModule({
setup (_options, nuxt) {
updateRuntimeConfig({
myModule: { flag: process.env.MY_FLAG || '0' },
})
},
})
runtimeConfig 的值在构建期被序列化进应用,运行时通过 useRuntimeConfig() 读取,从而切断"模块→全局进程环境→插件"这条运行时依赖链。更深入的用法见 运行时配置指南。
不要依赖生产环境的运行时钩子
诸如 vue-renderer:* 之类的运行时渲染钩子仅存在于 Nuxt 2 的运行时架构中,Nuxt 3 已不存在对应的运行时容器。除非纯粹用于 nuxt dev 开发模式,并且用 if (nuxt.options.dev) { } 显式包裹(如下代码所示),否则应避免在模块中挂钩 vue-renderer:* 这类运行时钩子用于生产环境。
export default defineNuxtModule({
setup (_options, nuxt) {
if (nuxt.options.dev) {
// 仅在开发模式挂接运行时相关逻辑
}
},
})
不要"在模块里 import" serverMiddleware,改用文件路径引用
把 serverMiddleware 直接 import 进模块代码会让中间件与模块的模块系统上下文(打包产物、闭包环境)强绑定,导致无法在 Nuxt 3 的独立服务端运行时中加载。正确做法是引用文件路径,让 Nuxt 在构建期自行解析与打包这些文件,从而保证服务端中间件与模块上下文解耦。
在 Nuxt 3 中更推荐直接通过 @nuxt/kit 的 Nitro 工具注册服务端逻辑(见 packages/kit/src/index.ts 中导出的 addServerHandler 等),它天然使用路径引用、完全构建期化。
上述例外规则(
(*))统一为:除非仅用于nuxt dev且以if (nuxt.options.dev) { }守卫,否则一律避免。
(可选)迁移到 TypeScript
这一步骤不是强制项,但 Nuxt 生态正在整体转向 TypeScript,官方强烈建议考虑迁移,理由很实在:
- 可以直接把
.js文件改名为.ts开始——TypeScript 是渐进式的,现有代码几乎不需要改动即可逐步获得类型检查; - Nuxt 2 与 Nuxt 3 的模块与插件都支持直接书写 TypeScript 语法,无需额外依赖即可编写和发布带类型的模块代码(配合
defineNuxtModule的泛型推断,模块选项也能获得自动补全与类型校验)。
仓库本身的强类型实现就是现成参照:例如 defineNuxtModule 的类型签名定义于 packages/kit/src/module/define.ts,模块的 meta、选项、运行时钩子等类型则沉淀在 packages/schema/src/types/module.ts。
兼容性检查:模块 meta 背后的机制
最后补充一个贯穿全文的机制细节:meta.compatibility 并非摆设。在 packages/kit/src/compatibility.ts 的 checkNuxtCompatibility 实现中,Nuxt 会用 verkit 的 satisfies 对 meta.compatibility 声明的约束做语义化版本匹配(并自动剔除 nightly 的边缘版本前缀),同时支持对 Nuxt、builder(@nuxt/vite-builder/@nuxt/webpack-builder/@nuxt/rspack-builder)与 Nitro 三个维度做版本约束检查。这与 define.ts 中"兼容性不满足则禁用模块或抛出 ModuleCompatibilityError"的逻辑闭环——所以给模块正确声明 compatibility: { nuxt: '>=2.0.0' } 之类约束,是实现 Nuxt 2/Nuxt 3 双端兼容声明的一部分。
小结与推荐阅读
把 Nuxt 2 模块改造成 Nuxt 3 兼容版本,核心可以归纳为五步行动清单:
- 引入
@nuxt/bridge到你的 fixture/示例项目,先跑通双版本验证环境(Bridge 概览); - 用
@nuxt/kit替代模块容器this.*,用defineNuxtModule重新组织模块定义; - 完成 ESM 迁移(原生 ESM 指南),调整
package.json导出与 Node 兼容写法; - 为所有注入的 Nuxt 插件补上默认导出;
- 消除运行时依赖:
process.env换runtimeConfig、去掉生产环境的vue-renderer:*钩子、serverMiddleware改为路径引用,并用nuxt.options.dev守卫仅限开发的功能。
新模块的完整开发流程(模板初始化、playground 调试、测试、打包与发布)可继续阅读 创建你的第一个模块;模块的目录组织、runtime/ 目录职责、注入运行时资产(组件、组合式函数、插件、Nitro 路由与中间件)等,参见 模块结构解析。希望这套"先兼容、再重写"的策略能帮你平稳度过 Nuxt 2 → Nuxt 3 的生态切换期。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00