Skip to content
在 AI 工具中打开 Anthropic
← Blog

Elysia 1.2 - 你与我

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

以 HoyoMix 的专辑《 At the Fingertip of the Sea 》中的歌曲 Φ² 命名,正如在 "你与我" 中使用的那样。

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

适配器 ​

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

Elysia 1.2 引入了 适配器 以允许 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 直接使用原生 Node API,以实现最佳性能,而不是依赖于桥接将 Web 标准的请求转换为 Node 请求/响应。并在需要时提供 Web 标准兼容性。

通过利用 Sucrose 静态代码分析,Elysia 比大多数 Web 标准框架(如 Hono、h3,甚至原生 Node 框架如 Fastify、Express)都要快。

Node 基准测试

与往常一样,您可以在 Bun HTTP 框架基准测试 中找到基准测试。

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 - 返回表单数据响应
  • server - Bun 的 Server 类型声明的移植

具有解析的宏 ​

从 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 以提高性能,并使其 API 和行为与最新的 Bun 的 WebSocket API 匹配,同时保持与每个运行时的兼容性。

ts
new Elysia()
	.ws('/ws', {
		ping: (message) => message,
		pong: (message) => message
	})

WebSocket 现在具有更一致的 API,与 HTTP 路由相匹配,并具有与 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,就看到内存使用量减少了多达 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 中使用 16 位十六进制数作为 id
  • 在生产构建中禁用 minify 以便更好地调试/错误报告

破坏性更改 ​

在升级到 Elysia 1.2 时,您的代码库可能需要进行一些小的必需更改。

不过,以下是您需要注意的所有更改。

解析 ​

type 现在与 parse 合并,以允许控制自定义和内置解析器的顺序。

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

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

表单数据 ​

从 1.2 开始,如果响应是表单数据,您现在必须显式返回 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 的 API 与 Bun 的 WebSocket API 匹配,以便更好地兼容和迁移。

ts
import { Elysia } from 'elysia'

new Elysia()
	.ws('/', {
		message(ws) {
			ws 
				.send('hello') 
				.send('world') 

			ws.send('hello') 
			ws.send('world') 
		}
	})

作用域 ​

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: Ergonomic Framework for Humans