Elysia 1.3 和 Scientific Witchery

以 Mili 的歌曲 Ga1ahad and Scientific Witchery 命名。
本次发布没有带来闪亮的新功能。
它是关于精炼,使事物变得更好,以至于我们认为它是**“魔法”**。
Elysia 1.3 带来了几乎零开销、精炼、修复技术债务以及重构内部代码的特点,包括:
Exact Mirror
我们在 Elysia 1.1 中引入了 normalize,以确保数据符合我们期望的形状,它工作得很好。
它有助于减少潜在的数据泄漏、意外属性,用户也非常喜欢它。然而,它带来了性能成本。
在底层,它使用 TypeBox's Value.Clean
来动态地将数据强制转换为指定的 schema。
它工作得很好,但没有我们希望的那么快。
因为 TypeBox 没有提供 Value.Clean
的编译版本,不像 TypeCompiler.Check
那样利用提前知道形状的优势。
这就是为什么我们引入了 Exact Mirror 作为替代。
Exact Mirror 是 TypeBox 的 Value.Clean 的即插即用替代品,通过利用提前编译来实现显著的性能改进。
性能
对于没有数组的小型对象。我们测量到对于相同对象,速度提升高达 ~500 倍。
Exact Mirror 在小型数据上运行
对于中型和大型对象。我们测量到速度提升高达 ~30 倍。
Exact Mirror 在中型和大型数据上运行
对 Elysia 的意义
从 Elysia 1.3 开始,Exact Mirror 是规范化默认策略,取代了 TypeBox。
通过升级到 Elysia 1.3,您可以期望在无需任何代码更改的情况下获得显著的性能改进。
以下是 Elysia 1.2 上的吞吐量。
Elysia 关闭规范化时
以下是 Elysia 1.3 上相同代码的吞吐量
Elysia 开启规范化时
我们测量到在使用单个 schema 进行规范化时,吞吐量提升高达 ~1.5 倍。
这意味着如果您使用多个 schema,您应该会看到更大的性能改进。
与无 schema的相同代码相比,我们看到性能差异小于 2%。
Elysia 无验证运行时
这很了不起。
以前,您不得不在安全性和性能之间选择,因为我们缩小了使用和不使用验证之间的性能差距。但现在您不必担心它。
现在,我们将验证开销从显著的数量降低到几乎为零,而无需您进行任何更改。
它就是这样工作,像魔法一样。
然而,如果您想使用 TypeBox 或完全禁用规范化。您可以使用构造函数像其他配置一样设置它:
import { Elysia } from 'elysia'
new Elysia({
normalize: 'typebox' // 使用 TypeBox
})
您可以访问 Exact Mirror on GitHub 来亲自尝试基准测试。
System Router
我们在 Elysia 中从未遇到过路由器的性能问题。
它具有出色的性能,并且我们尽可能地进行了超优化。
我们将其推向了 JavaScript 在实际意义上所能提供的极限。
Bun Router
然而,Bun 1.2.3 提供了内置的路由解决方案(可能)使用原生代码。
虽然对于静态路由,我们没有看到太多性能改进,但我们发现动态路由性能提升 2-5%,无需任何代码更改。
从 Elysia 1.3 开始,我们提供了双路由器策略,使用 Bun 的原生路由器和 Elysia 的路由器。
Elysia 将尽量使用 Bun 路由器,并在可能的情况下回退到 Elysia 的路由器。
Adapter
为了实现这一点,我们必须重写内部编译代码,以支持来自 adapter 的自定义路由器。
这意味着,现在可以使用自定义路由器与 Elysia 自身的路由器一起使用。
这为某些环境中的性能改进打开了机会,例如:使用内置的 uWebSocket.js router
,它具有路由的原生实现。
Standalone Validator
在 Elysia 中,我们可以使用 guard
定义 schema 并将其应用于多个路由。
然后,我们可以通过在路由处理程序中提供 schema 来覆盖公共 schema,有时看起来像这样:
Elysia 使用默认覆盖 guard
但有时我们不想覆盖 schema。
相反,我们希望它既能工作,又能允许我们组合 schema 而不是覆盖它们。
从 Elysia 1.3 开始,我们可以做到这一点。
现在,我们可以通过将 schema 提供为 standalone 来告诉 Elysia 不覆盖它,而是将其视为独立的。
import { Elysia } from 'elysia'
new Elysia()
.guard({
schema: 'standalone',
response: t.Object({
title: t.String()
})
})
结果,我们得到了像合并本地和全局 schema 一起的效果。
Elysia 使用 standalone 合并多个 guard
Reduced Type Instantiation
Elysia 的类型推断已经非常快。
我们对类型推断的优化非常自信,它比大多数使用类似 Express 语法的框架更快。
然而,对于具有多个路由和复杂类型推断的非常大型规模应用的用户的。
我们在大多数情况下将类型实例化减少了一半,并测量到推断速度提升高达 60%。
类型实例化从 109k 减少到 52k
我们还将 decorate
的默认行为更改为不递归循环每个对象和属性,而是进行 intersect。
这应该解决使用重型对象/类(如 PrismaClient
)的用户的问题。
结果,我们应该得到更快的 IDE 自动完成、建议、类型检查和 Eden Treaty。
Performance Improvement
我们重构并优化了许多内部代码,累积起来带来了显著的改进。
Route Registration
我们重构了存储路由信息的方式,并重用对象引用而不是克隆/创建新的。
我们看到了以下改进:
- 内存使用量减少高达 ~5.6 倍
- 路由注册时间加快高达 ~2.7 倍
Elysia 1.2(左)和 1.3(右)之间的路由注册比较
这些优化应该在中大型规模应用中显示真实结果,因为它随着服务器拥有的路由数量而扩展。
Sucrose
我们实现了 Sucrose 缓存,以减少不必要的重新计算,并在为非内联事件编译每个路由时重用编译的路由。
Elysia 1.2(左)和 1.3(右)之间的 Sucrose 性能比较
Sucrose 将每个事件转换为校验和数字并将其存储为缓存。它使用很少的内存,并且在服务器启动后会被清理。
这项改进应该有助于重用全局/作用域事件的重用每个路由的启动时间。
Instance
我们在创建多个实例并将它们作为插件应用时看到了显著的改进。
- 内存使用量减少高达 ~10 倍
- 插件创建速度加快高达 ~3 倍
Elysia 1.2(左)和 1.3(右)之间的 Elysia 实例比较
这些优化通过升级到 Elysia 1.3 将自动应用。然而,对于小型应用,这些性能优化可能不会显著明显。
因为服务一个简单的 Bun 服务器作为固定的 10-15MB 成本。这些优化更多是减少现有开销并有助于改进启动时间。
总体性能更快
通过各种微优化、修复技术债务以及消除未使用的编译指令。
我们在 Elysia 请求处理速度上看到了一些总体改进。在某些情况下高达 40%。
Elysia 1.2 和 1.3 之间的 Elysia.handle 比较
Validation DX Improvement
我们希望 Elysia 验证就是这样工作。
您只需说明您想要什么,然后就得到它。这是 Elysia 最有价值的方面之一。
在本更新中,我们改进了我们一直欠缺的一些领域。
Encode schema
我们已将 encodeSchema 从 experimental
中移出,并默认启用它。
这允许我们使用 t.Transform 来应用自定义响应映射,返回给最终用户。
使用 t.Transform 将值拦截为新值
此示例代码将拦截响应,将 “hi” 替换为 “intercepted”。
Sanitize
为了防止 SQL 注入和 XSS,并确保字符串输入/输出安全,我们引入了 sanitize 选项。
它接受一个函数或函数数组,拦截每个 t.String
,并将其转换为新值。
使用 Bun.escapeHTML 的 sanitize
在此示例中,我们使用 Bun.escapeHTML 并将每个 “dorothy” 替换为 “doro”。
由于 sanitize
将全局应用于每个 schema,它必须在根实例上应用。
这应该大大减少手动安全验证和转换每个字符串字段的样板代码。
Form
在 Elysia 的先前版本中,无法在编译时使用 form 和 t.Object
对 FormData 响应进行类型检查。
我们现在引入了新的 t.Form 类型来修复这个问题。
使用 t.Form 验证 FormData
要迁移到类型检查表单,只需在响应 schema 中将 t.Object
替换为 t.Form
。
File Type
Elysia 现在使用 file-type 来验证文件类型。
使用 t.File 定义文件类型
一旦指定了 type
,Elysia 将通过检查魔术数字自动检测文件类型。
然而,它也被列为 peerDependencies,默认不与 Elysia 一起安装,以减少不需要它的用户的捆绑包大小。
如果您依赖文件类型验证以获得更好的安全性,建议更新到 Elysia 1.3。
Elysia.Ref
我们可以使用 Elysia.model
创建引用模型,并使用名称引用它。
然而,有时我们需要在 schema 内部引用它。
我们可以使用 Elysia.Ref
来引用模型并提供自动完成。
使用 Elysia.Ref 引用模型
您也可以使用 t.Ref
来引用模型,但它不会提供自动完成。
NoValidate
我们收到了一些反馈,一些用户希望快速原型化他们的 API,或者有时试图强制执行验证时遇到问题。
在 Elysia 1.3 中,我们引入了 t.NoValidate
来跳过验证。
使用 t.NoValidate 告诉 Elysia 跳过验证
这将告诉 Elysia 跳过运行时验证,但仍提供 TypeScript 类型检查和 OpenAPI schema 用于 API 文档。
Status
我们收到了关于 error
命名的许多回应。
从 Elysia 1.3 开始,我们决定弃用 error
,并推荐使用 status
代替。
IDE 显示 error 已弃用并重命名为 status
error
函数将像以前版本一样工作,无需立即更改。
然而,我们推荐重构为 status
,因为我们将至少在接下来的 6 个月或直到大约 Elysia 1.4 或 1.5 支持 error
函数。
要迁移,只需将 error
重命名为 status
。
".index" is removed from Treaty
以前,您必须添加 (treaty).index
来处理以 / 结尾的路径。
从 Elysia 1.3 开始,我们决定放弃使用 .index
,并可以直接调用方法来绕过它。
Eden Treaty 显示不使用 .index
这是一个破坏性更改,但迁移应该需要最小的努力。
要迁移,只需从您的代码库中移除 .index
。这应该是一个简单的更改,通过使用 IDE 搜索来批量更改和替换匹配 .index
以移除它。
Notable changes
以下是来自变更日志的一些显著更改。
Improvement
encodeSchema
现在稳定并默认启用- 优化类型
- 在使用 Encode 时减少冗余类型检查
- 优化 isAsync
- 默认展开 Definition['typebox'] 以防止不必要的 UnwrapTypeModule 调用
- Elysia.form 现在可以进行类型检查
- 重构类型系统
- 将
_types
重构为~Types
- 使用 aot 编译检查自定义 Elysia 类型,例如 Numeric
- 重构
app.router.static
,并将静态路由器代码生成移动到编译阶段 - 优化
add
、_use
和一些实用函数的内存使用 - 改进多个路由的启动时间
- 在编译过程中动态创建所需的 cookie 验证器
- 减少对象克隆
- 优化查找内容类型标头分隔符的起始索引
- Promise 现在可以作为静态响应
ParseError
现在保留堆栈跟踪- 重构
parseQuery
和parseQueryFromURL
- 为
mount
添加config
选项 - 在异步模块挂载后自动重新编译
- 支持 when hook 有函数时的宏
- 支持 ws 上的 resolve 宏
- #1146 添加从处理程序返回 web API 的 File 的支持
- #1165 在响应 schema 验证中跳过非数字状态代码
- #1177 当抛出错误时 cookie 不签名
Bug fix
- 从
onError
返回的Response
使用 octet stream - 使用
mergeObjectArray
时意外的内存分配 - 处理 Date 查询中的空空格
Change
- 仅当
maybeStream
为 true 时向 mapResponse 提供c.request
- 为
routeTree
使用普通对象而不是Map
- 移除
compressHistoryHook
和decompressHistoryHook
- webstandard 处理程序现在如果不在 Bun 上返回
text/plain
- 除非明确指定,否则为
decorate
使用非 const 值 Elysia.mount
现在默认设置detail.hide = true
Breaking Change
- 移除
as('plugin')
,改为as('scoped')
- 为 Eden Treaty 移除根
index
- 从
ElysiaAdapter
移除websocket
- 移除
inference.request
Afterword
嗨?好久不见。
生活有时很困惑,不是吗?
有一天你在追逐梦想,努力向它前进。
不知不觉,你回头一看,发现自己已经远远超过了目标。
有人仰望你,你成为他们的灵感来源。成为某人的榜样。
听起来很棒,对吧?
但我不认为我会成为他人的好榜样。
我想过诚实的生活
有时,事情会被夸大。
我可能看起来像一个能创造任何事物的天才,但其实不是。我只是尽力而为。
我和朋友玩电子游戏,听奇怪的歌,看电影。我甚至在cosplay大会上见朋友。
就像普通人一样。
一直以来,我只是紧紧抱着你的胳膊。
我就像你一样,没什么特别的。
我尽力而为,但有时也会像傻瓜一样行事。
即使我不认为我有什么让我成为榜样的东西,我也想让你让我说一声感谢。
我无聊且略显孤独的生活,请不要美化它太多。
~ 我很高兴你也是邪恶的。