Skip to content
我们的赞助商
博客

Elysia 1.2 - 你和我

蓝色-紫色调背景,中间白色文本标签 Elysia 1.2

以 HoyoMix 专辑《At the Fingertip of the Sea》中歌曲 Φ² 的名称命名,该歌曲用于 "You and Me"

Elysia 1.2 专注于承诺扩展通用运行时支持和开发者体验:

适配器

最受欢迎的功能之一是支持更多运行时。

Elysia 1.2 引入 adapter,允许 Elysia 在不同运行时上运行。

ts
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 更快。

Node Benchmark

一如既往,您可以在 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 中不可用。

ts
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

ts
import { 
Elysia
} from 'elysia'
new
Elysia
()
.
macro
({
user
: (
enabled
: true) => ({
resolve
: ({
cookie
: {
session
} }) => ({
user
:
session
.
value
!
}) }) }) .
get
('/', ({
user
}) =>
user
, {
user
: true
})

使用新的宏对象语法,您现在可以返回生命周期而不是检索它,从而减少样板代码。

以下是旧语法和新语法的比较:

ts
// ✅ 对象宏
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 引入了一个带有自定义名称的解析器,允许您指定用于解码请求正文的解析器。

ts
import { 
Elysia
} from 'elysia'
new
Elysia
()
.
parser
('custom', ({
contentType
}) => {
if(
contentType
=== "application/kivotos")
return 'nagisa' }) .
post
('/', ({
body
}) =>
body
, {
parse
: 'custom'
})

parser 的 API 类似于 onParse,但带有自定义名称,允许您在路由中引用它。

您也可以引用 Elysia 的内置解析器或提供多个解析器,按顺序使用。

ts
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,同时保持与每个运行时的兼容性。

ts
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 来处理引用模型,以支持循环递归类型。

ts
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 倍的内存减少。

1.1 和 1.2 之间的内存比较

这种内存优化会随着路由数量和路由复杂度的增加而扩展。因此,您可能会看到内存使用呈指数级减少。

值得注意的更新

以下是 Elysia 1.2 中进行的一些值得注意的改进。

Eden 验证错误

Eden 现在如果提供了验证模型,会自动推断 422 状态码。

ts
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 合并,以允许控制自定义和内置解析器的顺序。

ts
import { Elysia, form, file } from 'elysia'

new Elysia()
	.post('/', ({ body }) => body, {
		type: 'json'
		parse: 'json'
	})

formdata

从 1.2 开始,如果响应是 formdata,您现在必须明确返回 form,而不是在 1 级深对象中自动检测文件是否存在。

ts
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,以实现更好的兼容性和迁移。

ts
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 混淆。

ts
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。

Elysia:人性化框架