又一款 JavaScript 后端 Web 框架:Elysia v1.0 来了!

Elysia 是一个符合人体工程学的 Web 框架,用于使用 Bun 构建后端服务器。设计时考虑到简单性和类型安全性,使用熟悉的 API 和对 TypeScript 的广泛支持,并针对 Bun 进行了优化。

Elysia 针对 Bun 进行设计但不限于 Bun,对其他运行环境也做了兼容。Elysia 符合 WinterCG 的设计要求,您可以在 Cloudflare Worker、Vercel Edge Function 以及支持 Web 标准请求的大多数其他运行时上部署 Elysia 服务器。

Elysia 1.0 是经过 1.8 年开发后的第一个稳定版本。自开始以来,我们一直在等待一个专注于开发人员经验、速度以及如何为人类而不是机器编写代码的框架。

Elysia 在各种情况下对其进行实战测试,模拟中型和大型项目,向客户交付代码,这是 Elysia 有足够信心交付的第一个版本。Elysia 1.0 引入了重大改进,并包含 1 项必要的重大更改。

  • Sucrose – 重写模式匹配静态分析而不是正则表达式
  • 启动时间提高了 14 倍
  • 移除约 40 个路由和 TypeScript 实例限制
  • 更快的类型推断速度提高了约 3.8 倍
  • Treaty 2
  • Hook 类型(破坏性变化)
  • 严格错误检查的内联错误

又一款 JavaScript 后端 Web 框架:Elysia v1.0 来了!

Sucrose

Elysia 经过优化,在各种基准测试中表现出色,其中一个主要因素就是 Bun 和我们自定义的 JIT 静态代码分析。如果你不知道 Elysia 内嵌了某种「编译器」,它可以读取你的代码并生成优化的函数处理方式。

这个过程非常快速,而且无需构建步骤直接运行。不过由于它主要是用许多复杂的正则表达式编写的,因此维护起来很有挑战性,而且如果发生递归,速度有时会很慢。因此我们重写了静态分析部分,使用基于部分 AST 和模式匹配的混合方法「Sucrose」来分离代码注入阶段。

我们没有使用更准确的基于 AST 的完整方法,而是选择只实现所需的规则子集,以提高性能,因为它需要在运行时快速运行。Sucrose 擅长以较低的内存使用率准确推断处理程序函数的递归属性,推断时间最多可缩短 37%,内存使用率也显著降低。

从 Elysia 1.0 开始,Sucrose 将取代基于 RegEx 的部分 AST 和模式匹配。

缩短启动时间

有了 Sucrose 以及与动态注入阶段的分离,我们可以推迟 JIT 而不是 AOT 的分析时间。换句话说,「编译」阶段可以懒散地进行评估。

在路由首次匹配时,将评估阶段从 AOT 卸载到 JIT,并在服务器启动前缓存结果,按需编译而不是编译所有路由。

在运行时性能方面,单次编译通常很快,不超过 0.01-0.03 毫秒(毫秒而不是秒)。在中型应用和压力测试中,我们测得的启动时间快达 6.5-14 倍。

移除路由和实例限制

自 Elysia 0.1 以来,您最多只能堆叠 ~40 个路由和 1 个 Elysia 实例。

这是 TypeScript 的限制,每个队列的内存都是有限的,如果超过了,TypeScript 就会认为类型实例化过深,可能是无限的。作为一种变通方法,我们需要将实例分离到控制器中,以克服限制,并像这样重新合并类型以卸载队列。

const main = new Elysia()
    .get('/1', () => '1')
    .get('/2', () => '2')
    .get('/3', () => '3')
    // 重复 40 次
    .get('/42', () => '42')
    // 类型实例化过深,可能是无限的

不过,从 Elysia 1.0 开始,经过一年的类型性能优化(特别是尾部调用优化)和差异优化,我们已经克服了这一限制。这意味着理论上我们可以无限量地堆叠路由和方法,直到 TypeScript 崩溃。

const controller1 = new Elysia()
    .get('/42', () => '42')
    .get('/43', () => '43')

const main = new Elysia()
    .get('/1', () => '1')
    .get('/2', () => '2')
    // 重复 40 次
    .use(controller1)

因此,我们将 ~40 条路由的限制增加到 JavaScript 内存限制,因此尽量不要堆叠超过 ~558 条路由 / 实例,并在必要时分离到一个插件中。

又一款 JavaScript 后端 Web 框架:Elysia v1.0 来了!

让我们感觉 Elysia 还没有准备好投入生产的阻碍终于得到了解决。

类型推断改进

由于我们在优化方面付出的努力,我们在大多数 Elysia 服务器上的测试结果达到了约 82% 。

由于消除了堆栈的限制并提高了类型性能,即使在 500 个路由堆栈之后,我们也可以期望几乎即时的类型检查和自动完成。

通过预先计算类型而不是将类型重新映射到 Eden,Eden 条约的类型推断性能提高了 13 倍。总体而言 Elysia 和 Eden Treaty 一起执行时速度将提高约 3.9 倍。

以下是 450 条路由的 Elysia + Eden Treaty 在 0.8 和 1.0 上的比较。

又一款 JavaScript 后端 Web 框架:Elysia v1.0 来了!

使用 Eden Treaty 对 Elysia 进行 450 个路由的压力测试,结果如下:Elysia 0.8 花费了 ~1500 毫秒,Elysia 1.0 花费了 ~400 毫秒。

由于移除了堆栈限制和重新映射过程,现在可以为单个 Eden Treaty 实例叠加超过 1000 个路由。

Treaty 2

我们请求您就 Eden Treaty 提供反馈,告诉我们您喜欢什么以及可以改进的地方。您已经向我们指出了条约设计中的一些缺陷,并提出了几项改进建议。

正因如此,今天我们推出了 Eden Treaty 2,这是对更符合人体工程学设计的全面革新。

尽管我们不喜欢破坏性变化,但 Treaty 2 是对 Treaty 1 的继任者。

在 Treaty 2 中有哪些新内容:

  • 更符合人体工程学的语法
  • 端到端单元测试类型安全性
  • 拦截器
  • 无”$”前缀和属性

我们最喜欢的是端到端单元测试类型安全性,因此与其启动模拟服务器并发送获取请求,不如使用 Eden Treaty 2 编写具有自动完成和类型安全性的单元测试。

两者之间的区别在于 Treaty 2 是 Treaty 1 的继任者。我们不打算对 Treaty 1 引入任何破坏性变化,也不强迫您升级到 Treaty 2。

您可以选择继续在当前项目中使用 Treaty 1,而无需升级到 Treaty 2,并且我们将以维护模式进行维护。您可以导入 treaty 来使用 Treaty 2,或者导入 edenTreaty 来使用 Treaty 1。

Treaty 2 的文档可在「Treaty概述」中找到,而 Treaty 1 的文档则位于「Treaty Legacy」。

Hook 类型

我们讨厌破坏性更改,这是我们第一次在大规模上进行此操作。我们为 API 设计投入了很多精力,以减少对 Elysia 所做的更改,但这是修复有缺陷设计的必要步骤。

以前当我们添加一个带有 on 的挂钩时,比如 onTransform 或者 onBeforeHandle,它会变成一个全局挂钩。这对于创建像插件之类的东西非常好,但不适合像控制器之类的本地实例。

const plugin = new Elysia()
    .onBeforeHandle(() => {
        console.log('Hi')
    })
    // log Hi
    .get('/hi', () => 'in plugin')

const app = new Elysia()
    .use(plugin)
    // will also log hi
    .get('/no-hi-please', () => 'oh no')

为了解决这个问题,我们引入了一个钩子类型,通过引入 hook-type 来指定钩子应该如何继承。

钩子类型可分为以下几类:

  • local(默认)- 仅适用于当前实例和后代实例
  • 作用域 – 仅适用于 1 个上代实例、当前实例和后代实例
  • 全局(旧行为)- 适用于应用该插件的所有实例(所有祖先、当前和后代)

要指定钩子的类型,只需向钩子添加 { as: hookType } 即可。

内联错误

从 Elysia 0.8 开始,我们可以使用该 error 函数返回带有状态代码的响应以进行 Eden 推理。然而这有一些缺陷。

如果您为路由指定响应架构,Elysia 将无法为状态代码提供准确的自动完成功能。例如,缩小可用状态代码的范围。

又一款 JavaScript 后端 Web 框架:Elysia v1.0 来了!

内联错误可以从模式生成细粒度类型,提供类型缩小、自动完成和对值准确性的类型检查,在值而不是整个函数处用红色波浪线下划线。我们建议使用内联错误而不是导入错误,以获得更准确的类型安全性。

后续的计划

达到稳定发布意味着我们相信 Elysia 足够稳定并且可以在生产中使用。保持向后兼容性现在是我们的目标之一,除了安全性之外,我们努力避免对 Elysia 进行重大更改。我们的目标是让后端开发变得简单、有趣和直观,同时确保使用 Elysia 构建的产品拥有坚实的基础。

我们将专注于完善我们的生态系统和插件。引入一种符合人体工程学的方法来处理冗余和平凡的任务,开始一些内部插件重写、身份验证、JIT 和非 JIT 模式之间的同步行为以及通用运行时支持。

Bun 在运行时、包管理器及其提供的所有工具中都表现出色,我们相信 Bun 将成为 JavaScript 的未来。我们相信,通过向 Elysia 开放更多运行时并提供有趣的 Bun 特定功能,或者至少易于配置,例如 Bun Loaders API,最终将让人们更多地尝试 Bun,而不是 Elysia 选择仅支持 Bun。

Elysia 核心本身部分兼容 WinterCG,但并非所有官方插件都适用于 WinterCG,有一些具有 Bun 特定功能,我们希望修复该问题。我们还没有通用运行时支持的具体日期或版本,因为我们将逐步采用和测试,直到确保它可以正常工作而不会出现意外行为。

您可以期待以下运行时的支持:Node、Deno、Cloudflare Worker。

我们还希望支持以下内容:Vercel Edge Function、Netlify Function、AWS Lambda / LLRT。

我们还支持并测试 Elysia 在以下支持服务器端渲染或边缘功能的框架上:Nextjs、Expo、Astro、SvelteKit。

参考链接:elysiajs.com/blog/elysia…

原文链接:https://juejin.cn/post/7347147911148781583 作者:一纸忘忧

(0)
上一篇 2024年3月18日 上午11:17
下一篇 2024年3月18日 下午4:07

相关推荐

发表回复

登录后才能评论