Elysia 1.2 - 你和我

以 HoyoMix 专辑《At the Fingertip of the Sea》中歌曲 Φ² 的名称命名,该歌曲用于 "You and Me"。
Elysia 1.2 专注于承诺扩展通用运行时支持和开发者体验:
适配器
最受欢迎的功能之一是支持更多运行时。
Elysia 1.2 引入 adapter,允许 Elysia 在不同运行时上运行。
import { node } from '@elysiajs/node'
new Elysia({ adapter: node() })
.get('/', 'Hello Node')
.listen(3000)
Elysia 设计为在 Bun 上运行,并将继续以 Bun 作为主要运行时,并优先在 Bun 上实现功能。
然而,我们现在为您提供更多选择,让您可以在适合您需求的各种环境中尝试 Elysia,例如无服务器环境如 AWS Lambda、Supabase Function 等。
Elysia 适配器的目标是在不同运行时提供一致的 API,同时保持最佳性能,使用相同的代码或对每个运行时进行最小更改。
性能
性能是 Elysia 的优势之一。我们不会在性能上妥协。
Elysia 不依赖桥接来将 Web 标准的请求转换为 Node Request/Response,而是直接使用原生 Node API 来实现最佳性能,同时在需要时提供 Web 标准兼容性。
通过利用 Sucrose 静态代码分析,它允许 Elysia 比大多数 Web 标准框架如 Hono、h3 更快,甚至比原生 Node 框架如 Fastify、Express 更快。
一如既往,您可以在 Bun HTTP framework benchmark 中找到基准测试。
Elysia 现在支持以下运行时适配器:
- Bun
- Web 标准 (WinterCG) 例如 Deno、浏览器
- Node (beta)
虽然 Node 适配器仍处于 beta 阶段,但它具有您期望的大多数功能,从返回生成器流到 WebSocket。我们推荐您尝试使用它。
我们将在未来继续扩展对更多运行时的支持,从以下开始:
- Cloudflare Worker
- AWS Lambda
- uWebSocket.js
通用运行时 API
为了与不同运行时兼容,Elysia 现在包装了一些精心挑选的实用函数,以在不同运行时提供一致的 API。
例如,在 Bun 中,您可以使用 Bun.file
来返回文件响应,这在 Node 中不可用。
import { Elysia } from 'elysia'
import { Elysia, file } from 'elysia'
new Elysia()
.get('/', () => Bun.file('./public/index.html'))
.get('/', () => file('./public/index.html'))
这些实用函数是 Bun 实用函数的复制品,设计为与 Elysia 支持的运行时兼容,并将在未来扩展。
目前,Elysia 支持:
file
- 返回文件响应form
- 返回 formdata 响应server
- Bun 的Server
类型声明的移植
带有 resolve 的宏
从 Elysia 1.2 开始,您现在可以在宏中使用 resolve
。
import { Elysia } from 'elysia'
new Elysia()
.macro({
user: (enabled: true) => ({
resolve: ({ cookie: { session } }) => ({
user: session.value!
})
})
})
.get('/', ({ user }) => user, {
user: true
})
使用新的宏对象语法,您现在可以返回生命周期而不是检索它,从而减少样板代码。
以下是旧语法和新语法的比较:
// ✅ 对象宏
new Elysia()
.macro({
role: (role: 'admin' | 'user') => ({
beforeHandle: ({ cookie: { session } }) => ({
user: session.value!
})
})
})
// ⚠️ 函数宏
new Elysia()
.macro(({ onBeforeHandle }) => {
role(role: 'admin' | 'user') {
onBeforeHandle(({ cookie: { session } }) => ({
user: session.value!
})
}
})
两种语法都受支持,但推荐使用新的对象语法。我们没有计划移除之前的语法,但我们将专注于新的对象语法及其新功能。
INFO
由于 TypeScript 的限制,宏的 resolve
仅适用于新的对象语法,而不适用于之前的语法。
名称解析器
Elysia 1.2 引入了一个带有自定义名称的解析器,允许您指定用于解码请求正文的解析器。
import { Elysia } from 'elysia'
new Elysia()
.parser('custom', ({ contentType }) => {
if(contentType === "application/kivotos")
return 'nagisa'
})
.post('/', ({ body }) => body, {
parse: 'custom'
})
parser
的 API 类似于 onParse
,但带有自定义名称,允许您在路由中引用它。
您也可以引用 Elysia 的内置解析器或提供多个解析器,按顺序使用。
import { Elysia } from 'elysia'
new Elysia()
.parser('custom', ({ contentType }) => {
if(contentType === "application/kivotos")
return 'nagisa'
})
.post('/', ({ body }) => body, {
parse: ['custom', 'json']
})
解析器将按顺序调用,如果解析器不返回值,它将移动到下一个解析器,直到其中一个解析器返回值。
WebSocket
我们重写了 WebSocket 以提高性能,并匹配最新 Bun 的 WebSocket API,同时保持与每个运行时的兼容性。
new Elysia()
.ws('/ws', {
ping: (message) => message,
pong: (message) => message
})
WebSocket 现在具有与 HTTP 路由更一致的 API,并具有类似于 HTTP 路由的生命周期。
TypeBox 0.34
Elysia 1.2 现在支持 TypeBox 0.34。
通过此更新,Elysia 现在使用 TypeBox 的 t.Module
来处理引用模型,以支持循环递归类型。
import { Elysia, t } from 'elysia'
new Elysia()
.model({
a: t.Object({
a: t.Optional(t.Ref('a'))
})
})
.post('/recursive', ({ body }) => body, {
body: 'a'
})
减少内存使用
我们重构了 Sucrose 的生成代码,以实现可交换的代码生成过程。
通过重构以更好地进行代码重复、路由器优化和不必要的代码移除。
这允许 Elysia 从几个部分重用代码,并减少大量内存使用。
对于我们的项目,只需升级到 Elysia 1.2,我们就看到了从 1.1 起高达 2 倍的内存减少。
这种内存优化会随着路由数量和路由复杂度的增加而扩展。因此,您可能会看到内存使用呈指数级减少。
值得注意的更新
以下是 Elysia 1.2 中进行的一些值得注意的改进。
Eden 验证错误
Eden 现在如果提供了验证模型,会自动推断 422
状态码。
import { treaty } from '@elysiajs/eden'
import type { App } from './app'
const api = treaty<App>('localhost:3000')
const { data, error } = await api.user.put({
name: 'saltyaom'
})
if(error)
switch(error.status) {
case 422:
console.log(error.summary)
break
default:
console.error(error)
}
路由器
我们更新了路由注册去重,以实现更优化的处理。
以前,Elysia 会检查所有可能的路由以防止路由去重。现在,Elysia 使用校验和哈希映射来检查路由是否已注册,并合并路由和代码生成过程循环,以改进静态路由注册的性能。
变更
- 事件监听器现在基于作用域自动推断路径参数
- 为批量
as
添加 ‘scoped’,用于将类型转换为 ‘scoped’,类似于 ‘plugin’ - 更新
cookie
到 1.0.1 - 更新 TypeBox 到 0.33
content-length
现在接受数字- 在
trace
中为id
使用 16 位十六进制 - 在生产构建中禁用
minify
,以实现更好的调试/错误报告
破坏性变更
升级到 Elysia 1.2 可能需要对您的代码库进行少量修改。
然而,这些是您需要注意的所有变更。
parse
type
现在与 parse
合并,以允许控制自定义和内置解析器的顺序。
import { Elysia, form, file } from 'elysia'
new Elysia()
.post('/', ({ body }) => body, {
type: 'json'
parse: 'json'
})
formdata
从 1.2 开始,如果响应是 formdata,您现在必须明确返回 form
,而不是在 1 级深对象中自动检测文件是否存在。
import { Elysia, form, file } from 'elysia'
new Elysia()
.post('/', ({ file }) => ({
.post('/', ({ file }) => form({
a: file('./public/kyuukurarin.mp4')
}))
WebSocket
WebSocket 方法现在返回它们各自的值,而不是返回 WebSocket
。
因此,移除了进行方法链的能力。
这是为了使 WebSocket 与 Bun 的 WebSocket API 匹配相同的 API,以实现更好的兼容性和迁移。
import { Elysia } from 'elysia'
new Elysia()
.ws('/', {
message(ws) {
ws
.send('hello')
.send('world')
ws.send('hello')
ws.send('world')
}
})
scoped
Elysia 现在移除了 constructor scoped
,因为它可能与 scope's scoped/global
混淆。
import { Elysia } from 'elysia'
new Elysia({ scoped: false })
const scoped = new Elysia()
const main = new Elysia()
.mount(scoped)
内部破坏性变更
- 移除路由器内部属性 static.http.staticHandlers
- 路由器历史编译现在与历史组合链接
后记
Elysia 1.2 是一个雄心勃勃的更新,我们已经为此工作了一段时间。
这是一个扩展 Elysia 影响力到更多开发者和更多运行时的策略,但我还想说一些其他事情。
让我们重新开始吧。
嗨,你好吗?我希望你一切安好。
我仍然喜欢做这件事,写关于 Elysia 的博客文章。已经有一段时间了。
你可能注意到,上次更新已经有一段时间了,而且不是一个长更新。我为此抱歉。
我希望你理解,我们也有自己的生活需要照顾。我们不是机器人,我们是人类。有时关于生活,有时关于工作,有时关于家庭,有时关于财务。
我想一直和你在一起。
做我喜欢的事情,继续更新 Elysia,继续写博客文章,继续创作艺术,但你知道我也有事情需要照顾。
我必须把食物带到餐桌上,我必须在财务上照顾很多事情。我也必须照顾自己。
我希望你一切安好,我希望你快乐,我希望你健康,我希望你安全。
即使我不在。一个名为 Elysia 的我的一部分将与你同在。
感谢你陪伴我。
在这里,我感受到由实数解带来的触感。
两个灵魂的涟漪现在已经到达我们的双缝。
在世界上投射出明暗相间的条纹,如同白天和黑夜。
你让我在阳光下自由。
我将你的摇篮梦带到月球并返回。
蠕虫会变成蝴蝶,
在人们回答“我是谁”之前。
在冰变成水之后,
我倒挂的大海将成为你的天空。
我们终于再次相遇了,Seele。