从 v6 升级到 v7
AdonisJS v7 是在 v6 历时两年之后的一个重大版本发布。本指南涵盖你为使现有 v6 应用升级而必须做出的更改。
我们已尽力将破坏性变更的范围保持在较小水平。然而,仍然存在一些破坏性变更,且某些更新是必需的。在基础层面上:
- AdonisJS v7 需要 Node.js 24
- 兼容 TypeScript 5.9/6.0 与 ESLint 10
- Vite 集成已更新以适配 Vite 7
有用的链接
升级到 Node.js 24
AdonisJS v7 需要 Node.js 24 或更高版本。较旧的 Node.js 版本不再受支持。在继续本指南的其余部分之前,请确保更新你的本地开发环境、CI 流水线以及生产服务器。
node -v
使用编码代理(coding agent)进行升级
使用以下提示词与你的编码代理(Cursor、Claude Code、Copilot 等)配合,以处理升级中机械性的部分。请将它所做的更改与上面列出的破坏性变更进行核对。
升级所有包
将你项目中的每个 @adonisjs/* 包更新到其最新版本。你还必须将 @vinejs/vine、edge.js 和 Inertia 依赖升级到最新版本。
以下是一个跨平台脚本,你可以运行它来自动查找项目 package.json 文件中 AdonisJS 特定的依赖并一次性更新它们。
npm i $(node -e "const pkg = require('./package.json'); const deps = {...pkg.dependencies, ...pkg.devDependencies}; console.log(Object.keys(deps).filter(k => k.startsWith('@adonisjs/') || k === '@vinejs/vine' || k === 'edge.js' || k === '@japa/plugin-adonisjs' || k === 'vite' || k === 'argon2').map(k => k + '@latest').join(' '))") --force
替换 TypeScript JIT 编译器
我们已将 ts-node(和 ts-node-maintained)替换为 @poppinss/ts-exec 作为 JIT 编译器。移除旧包并安装新包。
npm uninstall ts-node ts-node-maintained @swc/core
npm install -D @poppinss/ts-exec
然后更新你 ace.js 文件中的导入。
import 'ts-node-maintained/register/esm'
import '@poppinss/ts-exec'
将 Youch 安装为项目依赖
Youch 不再打包在 @adonisjs/ace 和 @adonisjs/http-server 内部。它已从头重写,但这不会影响你的应用代码,因为 Youch 是框架在内部消费的。你只需将其作为开发依赖安装即可。
npm install -D youch
在 adonisrc.ts 中配置钩子
v7 在 adonisrc.ts 中引入了一个新的钩子系统。你至少必须添加 indexEntities 钩子。根据你的技术栈,你还需要为 Inertia、Tuyau、Bouncer 和 Vite 添加额外的钩子。
如果你的应用使用了 Tuyau,请确保安装 @tuyau/core 包。
npm install @tuyau/core
以下示例展示了一个完整的钩子配置。只包含与你的技术栈相关的钩子。
import { indexEntities } from '@adonisjs/core'
import { indexPages } from '@adonisjs/inertia'
import { defineConfig } from '@adonisjs/core/app'
import { indexPolicies } from '@adonisjs/bouncer'
import { generateRegistry } from '@tuyau/core/hooks'
export default defineConfig({
hooks: {
init: [
// Always needed
indexEntities(),
// If using Inertia (adjust framework to match yours)
indexPages({ framework: 'react' }),
generateRegistry(),
indexEntities({
transformers: { enabled: true, withSharedProps: true },
}),
// If using Bouncer
indexPolicies(),
],
buildStarting: [
// If using Vite
() => import('@adonisjs/vite/build_hook'),
],
},
})
汇编器钩子已重命名
汇编器(assembler)钩子的名称已更改。如果你之前使用的是 onBuildStarting 钩子(最常见的一个,用于 Vite),请将其更新为 buildStarting。
{
hooks: {
onBuildStarting: [() => import('@adonisjs/vite/build_hook')],
buildStarting: [() => import('@adonisjs/vite/build_hook')],
}
}
完整已重命名的钩子列表如下。
- onSourceFileChanged
+ fileChanged
- onDevServerStarted
+ devServerStarted
- onBuildCompleted
+ buildFinished
- onBuildStarting
+ buildStarting
+ fileAdded
+ fileRemoved
+ devServerStarting
+ testsStarting
+ testsFinished
更新测试 glob 模式
我们用 Node.js 内置的 glob 辅助函数替换了 glob 包。这需要在 adonisrc.ts 内的测试文件模式上做一个小的语法变更。
tests: {
suites: [
{
files: ['tests/unit/**/*.spec(.ts|.js)'],
files: ['tests/unit/**/*.spec.{ts,js}'],
},
],
forceExit: false,
},
移除 assetsBundler 属性
adonisrc.ts 中的 assetsBundler 属性已不再使用。移除它以解决升级后你会看到的 TypeScript 错误。
加密配置变更
来自 config/app.ts 的 appKey 导出不再用于加密。相反,你必须创建一个专用的 config/encryption.ts 文件。APP_KEY 环境变量仍然在使用。
export const appKey = env.get('APP_KEY')
v6 应用必须使用 legacy 驱动来继续解密现有数据。了解更多关于新的
加密驱动
的信息
import env from '#start/env'
import { defineConfig, drivers } from '@adonisjs/core/encryption'
export default defineConfig({
default: 'legacy',
list: {
legacy: drivers.legacy({
keys: [env.get('APP_KEY')],
}),
},
})
从容器中解析 encryption 绑定现在返回的是 EncryptionManager 实例,而非 Encryption 类。这是因为重写后的加密包支持多种算法,并使用管理器来在它们之间切换。
const encryption = await app.container.make('encryption')
// In v6
encryption instanceof Encryption
// In v7
encryption instanceof EncryptionManager
如果你之前是从容器中解析 Encryption 类并将其作为依赖传入,请通过直接解析该类构造函数来修复此问题。
import { Encryption } from '@adonisjs/core/encryption'
const encryption = await app.container.make(Encryption)
encryption instanceof Encryption // true
router.makeUrl 已废弃,改用 URL 构建器
router.makeUrl 和 router.makeSignedUrl 方法已被废弃。请改用新的类型安全 urlFor 辅助函数。
import router from '@adonisjs/core/services/router'
router.makeUrl('posts.show', { id: 1 })
import { urlFor } from '@adonisjs/core/services/url_builder'
urlFor('posts.show', { id: 1 })
在 Edge 模板内部,route 辅助函数已废弃,改用 urlFor。
route('posts.show', { id: 1 })
urlFor('posts.show', { id: 1 })
已移除的辅助函数
以下辅助函数已从 @adonisjs/core/helpers 中移除。每一个都有直接对应的替代方案。
| 已移除的辅助函数 | 替代方案 |
|---|---|
getDirname | import.meta.dirname |
getFilename | import.meta.filename |
slash | stringHelpers.toUnixSlash |
joinToURL | new URL() |
cuid / isCuid | 改用 UUID |
parseImports | 直接使用 parse-imports 包 |
import { getDirname, getFilename } from '@adonisjs/core/helpers'
getDirname()
getFilename()
import.meta.dirname
import.meta.filename
import { slash } from '@adonisjs/core/helpers'
slash('foo\\bar') // foo/bar
import stringHelpers from '@adonisjs/core/helpers/string'
stringHelpers.toUnixSlash('foo\\bar') // foo/bar
Request 和 Response 类已重命名
HTTP 包中的 Request 和 Response 类已重命名为 HttpRequest 和 HttpResponse。这避免了与全局可用的平台原生 Request 和 Response 类产生冲突。
大多数项目不会受影响,因为绝大多数代码库是与 HttpContext 对象交互,而不是直接导入这些类。然而,如果你扩展了这些类、使用模块增强来添加自定义属性、或者在它们上面注册了宏(macro),你将需要更新你的代码。
import { Request } from '@adonisjs/core/http'
import { HttpRequest } from '@adonisjs/core/http'
declare module '@adonisjs/core/http' {
interface Request {
someMethod(): void
}
interface HttpRequest {
someMethod(): void
}
}
Request.macro('someMethod', () => {})
HttpRequest.macro('someMethod', () => {})
Flash 消息的 errors 键已移除
已从 flash 消息存储中移除了已废弃的 errors 键。
验证错误一直以来都可通过 inputErrorsBag 键获取。errors 键是一个重复的键,会不必要地增大会话的负载大小。
如果你的模板或前端代码从 errors 读取数据,请将它们更新为改用 inputErrorsBag。
{{ flashMessages.get('errors.email') }}
{{ flashMessages.get('inputErrorsBag.email') }}
多部分(multipart)文件与字段在 request.all() 中合并
调用 request.all() 方法现在会返回一个合并了请求字段与 multipart 文件的对象。之前,它只返回字段。
request.allFiles()、request.file(name) 和 request.files(name) 方法继续像以前一样工作。
控制器自动生成路由名称
使用控制器的路由现在会自动获得一个生成的名称。这可能导致冲突:
- 如果你有两条指向同一个控制器方法的路由,其中只有一条具有显式名称。
- 未命名路由的自动生成名称会与另一条冲突,并在启动时抛出重复路由错误。
- 你会在启动应用时立即捕获到这个问题。
针对 JSON API 请求跳过的状态页面
由
全局异常处理器
渲染的状态页面,在请求的 Accept 头要求 JSON 响应时不再返回。API 客户端现在会收到 JSON 错误响应,而非渲染的 HTML 页面。这原本是一个 bug 修复,但如果你之前的 API 消费者收到的是 HTML 错误页面,这会改变其行为。
VineJS 中移除了 BaseModifiers
在最新版本的 VineJS 中,已移除 BaseModifiers 类。在大多数情况下,这不会影响你的应用。然而,如果你为了某个自定义用例而扩展或直接使用 BaseModifiers,你将需要调整你的实现。详情请参阅
VineJS v4 发布说明
。
应用关闭钩子以逆序运行
关闭(shutdown)钩子现在以逆序执行(最后注册,最先执行)。这是一个 bug 修复,与清理逻辑的预期行为一致,但如果你依赖之前(不正确)的顺序,可能会影响你的应用。
Inertia 集成重写
Inertia 集成在 v7 中进行了重大重构。目标是为 render 方法和页面 props 带来端到端的类型安全,在计算 props 时为 Transformers 添加一流的支持,并与 Inertia 生态系统中的上游变更保持一致。
类型安全的 render 方法
inertia.render 方法现在是类型安全的。这可能会在你的应用中暴露出之前被允许的不合法或不完整数据的 TypeScript 错误。
配置变更
已对 config/inertia.ts 文件进行以下更改。
export default defineConfig({
entrypoint: 'inertia/app/app.tsx',
history: {
encrypt: true,
},
encryptHistory: true,
sharedData: {
// ...
},
// Replaced by inertia_middleware (see below)
})
- 已移除
entrypoint配置选项。此配置选项在任何地方都未被使用。 history.encrypt选项已重命名为顶层的encryptHistory属性。- 已移除
sharedData属性,改用 Inertia 中间件(见下文)。
文件结构变更
Inertia 入口点和 SSR 文件已从 app 子目录中移出,现在直接位于 inertia 根目录下。
- inertia/app/app.tsx
+ inertia/app.tsx
- inertia/app/ssr.tsx
+ inertia/ssr.tsx
确切的文件扩展名取决于你的框架。例如,Vue 应用将使用 inertia/app.ts 和 inertia/ssr.ts。
共享数据移至中间件
config/inertia.ts 中的 sharedData 属性已被移除。你必须创建一个 Inertia 中间件来定义共享数据,并将其注册为 kernel 文件中的服务器中间件。
node ace make:middleware inertia_middleware
server.use([
() => import('#middleware/inertia_middleware'),
])
share 方法接收 HttpContext,如果在请求到达路由处理器之前就发送了响应(例如,在 404 期间),该上下文可能未被完全填充。请谨慎地守卫你的属性访问。
以下是默认的中间件文件。由于你是从 v6 升级,你的应用中不会有 UserTransformer。因此请随意移除这个导入,并按照代码库其他部分的方式序列化用户。
import type { HttpContext } from '@adonisjs/core/http'
import type { NextFn } from '@adonisjs/core/types/http'
import UserTransformer from '#transformers/user_transformer'
import BaseInertiaMiddleware from '@adonisjs/inertia/inertia_middleware'
export default class InertiaMiddleware extends BaseInertiaMiddleware {
share(ctx: HttpContext) {
/**
* The share method is called every time an Inertia page is rendered. In
* certain cases, a page may get rendered before the session middleware
* or the auth middleware are executed. For example: During a 404 request.
*
* In that case, we must always assume that HttpContext is not fully hydrated
* with all the properties.
*/
const { session, auth } = ctx as Partial<HttpContext>
/**
* Data shared with all Inertia pages. Make sure you are using
* transformers for rich data-types like Models.
*/
return {
errors: ctx.inertia.always(this.getValidationErrors(ctx)),
flash: ctx.inertia.always({
error: session?.flashMessages.get('error'),
success: session?.flashMessages.get('success'),
}),
user: ctx.inertia.always(auth?.user ? UserTransformer.transform(auth.user) : undefined),
}
}
async handle(ctx: HttpContext, next: NextFn) {
await this.init(ctx)
const output = await next()
this.dispose(ctx)
return output
}
}
declare module '@adonisjs/inertia/types' {
type MiddlewareSharedProps = InferSharedProps<InertiaMiddleware>
export interface SharedProps extends MiddlewareSharedProps {}
}
添加 tsconfig.inertia.json
你必须创建一个新的 tsconfig.inertia.json 文件,以避免 Inertia 前端代码库与后端代码库之间的循环引用问题。
这种循环引用之所以发生,是因为代码生成(codegen)让 Inertia 应用引用后端代码,而后端代码又引用 Inertia 页面来推断 prop 类型。
在项目根目录创建该文件。
{
"extends": "./inertia/tsconfig.json",
"compilerOptions": {
"rootDir": "./inertia",
"composite": true
},
"include": ["./inertia/**/*.ts", "./inertia/**/*.tsx"]
}
然后将一个 references 条目添加到你的主 tsconfig.json 中。
{
"extends": "@adonisjs/tsconfig/tsconfig.app.json",
"compilerOptions": {
"rootDir": "./",
"jsx": "react",
"outDir": "./build"
},
"references": [{ "path": "./tsconfig.inertia.json" }]
}
向 package.json 添加新的子路径导入
将以下条目添加到你的 package.json 的 imports 字段中。这些是对你项目已有的任何别名之外的补充。
{
"imports": {
"#generated/*": "./.adonisjs/server/*.js",
"#transformers/*": "./app/transformers/*.js",
"#database/*": "./database/*.js"
}
}