引言
2017 年 9 月,Cloudflare 推出了 Cloudflare Workers,使得我们可以在他们的边缘网络上运行 JavaScript。这意味着你的代码将在全球一百多个位置的整个边缘网络上部署,大约在 30 秒内完成。利用该技术,你可以专注于编写靠近用户所在位置的应用程序,无论他们在世界的哪个地方(延迟约为 50 毫秒)。
与 Node.js 或浏览器的运行时不同,Worker 的运行时是基于 V8 的,V8 是由 Google Chrome 开发的 JavaScript 引擎。在此之前,你只能在他们的平台上运行一些小型脚本,这些脚本会在到达服务器之前在边缘上运行,以提高性能或基于请求头添加一些逻辑。
2020 年 11 月,在开发 Nuxt 3 期间,我们决定将 Nuxt 生产环境下运行在边缘运行时 / V8 隔离环境。
这使得在使用类似于 Cloudflare Workers 的平台时,你可以在全球范围内以约 50 毫秒的速度进行服务器端渲染,而无需担心服务器、负载均衡和缓存问题,其费用为约每百万次请求 0.3 美元(费用详情)。时至今日,新的平台也开始出现,允许在 V8 隔离环境中运行应用程序,例如 Deno Deploy。
挑战
为了让 Nuxt 在 Worker 上运行,我们不得不重写 Nuxt 的某些部分,使其能够适应各种环境(Node.js、浏览器或 V8)。
我们从服务器开始,并创造了 unjs/h3:一个专为高性能和可移植性而构建的最小化 HTTP 框架。它取代了我们在 Nuxt 2 中使用的 Connect,但同时也与 Connect 保持兼容,因此你可以继续使用 Connect/Express 中间件。在 Worker 中,对于每个传入的请求,它会启动 Nuxt 的生产环境,将请求发送给它,并将响应返回。
在 Nuxt 2 中,启动服务器的时间(也称为冷启动)约为 ~300 毫秒,这是因为我们需要加载服务器和应用程序的所有依赖项以处理请求。
通过处理 h3,我们决定将附加到服务器的每个处理程序进行代码拆分,并在请求时仅在需要时进行延迟加载。当你启动 Nuxt 3 时,我们只会将 h3 和对应的处理程序加载到内存中。当请求到达时,我们加载与路由对应的处理程序并执行它。
通过采用这种方法,我们将冷启动时间从约 300 毫秒减少到约 2 毫秒。
我们在将 Nuxt 部署在边缘运行时上面临另一个挑战:生成的捆绑文件大小。这包括服务器、Vue 应用程序和 Node.js 依赖项的组合。目前,Cloudflare Workers 在 worker 大小方面有一个限制,免费计划为 1MB,每月 5 美元的计划为 5MB。
为了实现这一点,我们创建了 unjs/nitro,这是我们的服务器引擎。当运行 nuxt build
命令时,它会将你的整个项目打包,并将所有依赖项包括在最终输出中。它使用 Rollup 和 vercel/nft 来跟踪 node_modules
中使用的代码,以删除不必要的代码。基本的 Nuxt 3 应用程序生成的输出的总大小约为 700kB(经过 gzip 压缩)。
最后,为了在开发(Node.js)和在 Cloudflare 上的生产环境(边缘运行时)之间提供相同的开发者体验,我们创建了 unjs/unenv:一个库,用于转换 JavaScript 代码以在任何地方(平台无关)运行,通过模拟或添加已知依赖项的 polyfill。
在 Nuxt,我们相信你应该拥有选择适合你的最佳托管提供商的自由。
这就是为什么你可以通过边缘端渲染来部署 Nuxt 应用程序的原因:
- NuxtHub
- Cloudflare Page
- Deno Deploy
- Vercel Edge Functions(在幕后使用 Cloudflare Workers)
- Netlify Edge Functions(在幕后使用 Deno)
我们还支持许多其他部署提供商,包括静态托管或传统的 Node.js 无服务器和服务器托管商。
推动全栈能力
现在我们在边缘运行时上运行 Nuxt,我们可以做更多事情,不仅仅是渲染 Vue 应用程序。借助服务器目录,只需一个 TypeScript 文件,你就可以创建一个 API 路由。
要添加 /api/hello
路由,创建一个名为 server/api/hello.ts
的文件:
export default defineEventHandler((event) => {
return {
hello: 'world'
}
})
现在你可以在页面和组件中通用地调用此 API:
<script setup>
const { data } = await useFetch('/api/hello')
</script>
<template>
<pre>{{ data }}</pre>
</template>
需要注意的一点是,当我们创建 useFetch 和 $fetch 时,在服务器端渲染期间,如果调用 API 路由,它将模拟请求并直接调用函数代码:避免了 HTTP 请求,减少了页面的渲染时间。
在开发体验方面,你会注意到当创建服务器文件时,Nuxt 服务器会继续运行,而不需要重新构建 Vue 应用程序。这是因为 Nuxt 3 支持创建 API 和服务器路由时的热模块替换(HMR)。
此外,通过利用类似于 drizzle-orm 的对象关系映射(ORM),开发者可以连接边缘和无服务器数据库,如 D1、Turso、Neon、Planetscale 等等。
我创建了 Atidone,这是一个开源示例,展示了在边缘上运行具有身份验证和数据库的全栈应用程序。源代码在 GitHub 上以 MIT 许可证的形式提供,地址为 atinux/atidone。
结论
我们对边缘端渲染及其带来的潜力感到兴奋。我们 Nuxt 团队迫不及待地想看看你们将在此基础上构建的作品!
欢迎加入我们的 Discord 服务器 或在 Twitter 上 @nuxt_js 提及我们,与我们分享你的工作。