就在上周,Astro 团队发布了 1.0 的正式版本。
从年初我就开始关注这个项目了,但当时只是学习了一下仓库的工程化搭建相关的东西 (changesets 自动发包之类),并没有深入了解它本身的功能。借着正式发版的机会,这几天熟悉了一下 Astro 1.0,发现了很多有意思的地方,下文会分别从团队背景、框架定位和核心优势几个维度给大家展开介绍,最后也会推荐一些学习资料。
团队背景
在正式介绍 Astro 之前,先给大家聊一聊相关的背景。Astro 的作者是 Fred K. Schott,没错,就是那个开发 Snowpack 的老哥,可以说是 Unbundle 构建工具的祖师爷:
但无奈的是 Vite 发展势头实在太猛,而 Snowpack 渐渐日薄西山,他本人也写了文章发出下面的感慨:
文章链接: https://dev.to/fredkschott/5-more-things-i-learned-building-snowpack-to-20-000-stars-5dc9
大意就是 Snowpack 前途渺茫,用户越来越少,感觉要做到头了啦,而 Vite 发展的非常好,那后面开发的 Astro 就基于 Vite 来做吧。现在连 Snowpack 的官网都表示弃坑了,主动给 Vite 引流:
当然,除了 Snowpack,Fred K. Schott 的团队(叫Pika)还做了一件比较知名的产品: Skypack,即 NPM 包的 ESM CDN 服务:
不幸的是,Skypack 也很长时间(一年多)没有维护了,可以看出团队也不再想继续投入这个项目了,个人感觉主要有两个原因:
- 竞争对手的强大。竞品 esm.sh 一直在持续迭代且已经得到了 Deno 的官方推荐。
- 方案落地困难。此类 ESM CDN 应用到实际的业务项目中仍然有诸多的现实障碍,如请求数量多、不能 Tree Shaking、不能本地调试等等,要落地是一个比较难的问题。
在近些年的时间里,Fred K. Schott 团队一直将重心放在了新项目 Astro 上面,经过 16 个月的打磨, Astro 在全球拥有了 30000 多个用户,被 Google、Netlify 等大公司使用,Github 的 star 已经达到 15k +。
值得一提的是,Fred K. Schott 为了这个项目专门成立了一个创业公司: The Astro Technology Company,并且已经融到了 700 万美刀的种子轮投资,打算一直以开源的方式发展下去。相比于一些知名开源项目的赞助收入,如 Webpack[1] 22 w 刀/年、Babel[2] 30 w 刀/年,Astro 在经济方面有如鱼得水的优势。
框架定位
接下来聊一聊 Astro 框架的定位,是像 Vue、React 这样的底层渲染框架,还是像 Next.js 这种上层的研发框架?
这一点其实挺困扰初学者的,因为 Astro 既自创了类似于.vue、.jsx文件的 .astro 语法,又提供了像 Next.js 里面各种运行时的能力,比如约定式路由、构建优化、SSR 等等。
但实际上它给自己的定位非常清晰,即 content-focused 应用开发框架,换句话说,就是重内容、轻交互场景下的上层研发框架,比如大多数电商网站、文档站、博客站、证券网站等等。
你可以将 Astro 理解为一个垂直场景下的Next.js,但它可以在它适用的领域里面可以胜过其它所有竞品(如Next.js、Remix、Vuepress 等),这是它能够做起来的重要原因。接下来,我们就来看看 Astro 的优势在于哪些地方。
核心优势
Astro 的主要优势包括如下几点:
- Islands 架构,解决传统 SSR/SSG 框架的全量 hydration 问题,做到尽可能少的 Client 端 JS 的开销,甚至是 0 JS。
- 学习成本低。.astro 语法和传统的.jsx 和.vue 非常相似,对于新手前端来说也比较容易掌握。
- 使用灵活。对于页面的开发,你既可以使用官方的.astro 语法,也同样可以使用.md、.vue、.jsx 语法,也就是说,你可以自由选择其它前端框架的语法来开发,甚至可以在一个项目中同时写 Vue 组件和 React 组件!
- 构建迅速。底层构建体系基于 Vite 以及 Esbuild 实现,项目启动速度非常快。
Islands 架构
在如上的几个优点中,我们来重点说一说 Astro 的 Islands 架构,因为这是它高性能最主要的原因。
Islands 架构模型早在 2019 年就被提出来了,并在 2011 年被 Preact 作者Json Miller 在Islnads Architecture[3] 一文中得到推广。这个模型主要用于 SSR (也包括 SSG) 应用,我们知道,在传统的 SSR 应用中,服务端会给浏览器响应完整的 HTML 内容,并在 HTML 中注入一段完整的 JS 脚本用于完成事件的绑定,也就是完成 hydration (注水) 的过程。当注水的过程完成之后,页面也才能真正地能够进行交互。
那么当应用的体积逐渐增大时,需要在客户端执行的 JS 脚本也会越来越多,这也意味着 TTI(可交互时间) 指标越来越高:
为了解决这个问题,Islands 架构将页面拆分为各自独立的组件,包含静态组件和可交互组件,如下图的例子所示:
可以清楚的看到,一个页面中只有部分的组件交互,那么对于这些可交互的组件,我们可以并行地执行 hydration 过程,因为组件之间是互相独立的。
而对于静态组件,即不可交互的组件,我们可以让其不参与 hydration 过程,直接复用服务端下发的 HTML 内容。
可交互的组件就犹如整个页面中的孤岛(Island),因此这种模式叫做 Islands 架构。
相比于传统 SSR 中的全量 hydration,Islands 模式可以实现局部(partial) hydration,从而优化 JS 的体积,减少网络传输的成本和 JS 运行时的开销。
在 Astro 中,默认所有的组件都是静态组件,比如:
// index.astro
import MyReactComponent from '../components/MyReactComponent.jsx';
---
<MyReactComponent />
值得注意的是,这种写法不会在浏览器添加任何的 JS 代码。但有时我们需要在组件中绑定一些交互事件,那么这时就需要激活孤岛组件了,在 Astro 如何来激活呢?其实很简单,在使用组件时加上client:load指令即可:
// index.astro
---
import MyReactComponent from '../components/MyReactComponent.jsx';
---
<MyReactComponent client:load />
如此一来,Astro 会给浏览器传输一部分 JS 代码供这个组件完成 hydration,以便后续的交互。
对比 Next.js 和 Remix
读到这里,你可能会说了,相比于其它的业界方案,Astro 到底优势在哪里呢?我们不妨来盘点一下。
首先是大名鼎鼎的 Next.js,我们知道 Next.js 是一个非常经典的 React SSR 框架,也是使用传统的 SSR/SSG 技术,可以适用于几乎所有的 Web 开发场景。而 Astro 在其适用的content-focused场景下,性能会明显高于 Next.js,以下是两个真实的迁移案例:
可以看到,Astro 相比 Next.js 可以大幅度减少 JS 代码的体积(90% 以上),同时页面的运行时性能也提升了 30% 以上。除此之外,Astro 不仅支持使用 React 框架,而且支持 Vue、Solid 等在内的各种前端框架,灵活性更高。
其次是最近比较火的新秀框架 Remix,它基于 react-router 管理组件,通过 loader 和 action 的概念尽可能将逻辑代码放到服务端的 bundle,从而减少客户端 JS 的代码体积,同样是崇尚 0 JS 的理念,Remix 却仍然需要全量 hydration,无法完成 partial hydration。此外,Astro 还有两大优势:
- 除了 React,也支持其它的众多前端框架;
- 同时支持 SSR 和 SSG,而 Remix 不支持 SSG。
对比 React 18 的 Selection Hydration 特性
React 18 提供了 renderToPipeableStream API,真正实现了 SSR 场景下的 Selection Hydration,主要有如下的几个特点:
- 在完整的 HTML 渲染之前就可以进行组件的 hydrate,而不用等待 HTML 的内容发送完毕
- hydration 可中断。比如页面中有两个组件: Sidebar 和 Comment,当这个部分的 HTML 发送至浏览器时,React 打算开始对 Sidebar 组件进行 hydrate:
如果用户在这个过程中点击了 Comment 组件,那么 React 会中断当前对于 SideBar 组件的 hydrate,从而去执行 Comment 组件的 hydrate:
详情可见 React 18 SSR Architecture: https://github.com/reactwg/react-18/discussions/37
那么 Astro 中的 Islands 架构,即 Partial Hydration,和 React 的 Selection Hydration 到底是不是一个东西呢?
答案是否定的。两者存在着非常大的区别:
- 从渲染框架上来看,Selection Hydration 依附于具体框架的实现,而Partial Hydration 可以做到框架无关,即使是 Vue、Solid 的项目也可以做到Partial Hydration。
- 从客户端执行的 JS 总量来看,Partial Hydration 可以做到加载部分组件的 JS 代码,而Selection Hydration 仍然需要加载和执行全量的 JS 代码。
- 从服务端和客户端的交互来看,Selection Hydration 严重依赖于流式(Streaming)渲染,服务端需要加上transfer-encoding: chunked 的响应头,而Partial Hydration 没有这个限制。
因此,虽然两者都是在 Hydration 上做文章,但其实是两种完全不同的方案,而且 Partial Hydration 更加通用,限制更少,执行的 JS 更少。
小结
以上就是对 Astro 的介绍和分析,后面有机会给大家剖析一下内部的源码实现。文中如有不妥的地方,欢迎大家评论和指正。最后给大家推荐一些 Astro 的学习资料,方便大家读完文章进一步了解:
- Astro 官方文档: https://astro.build/。
- Astro 实战系列教程: https://aalam.in/blog/astro-get-up-and-running。
参考资料
[1]Webpack: https://opencollective.com/webpack。
[2]Babel: https://opencollective.com/babel。
[3]Islnads Architecture: https://jasonformat.com/islands-architecture/。