大家好,很高兴又见面了,我是"高级前端?进阶?",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发!
1.什么是 Astro
Astro 是一种流行的 Web 框架,用于构建以内容为中心的高性能网站。Astro 代表下一代前端架构,可以优化网站,允许选择已有的 UI 框架(如 React、Svelte 和 Vue),使用 Astro 构建的站点加载速度提高 33%,JavaScript 大小减少 90%。
但是,自 Astro 1.0 首发以来,开发人员不得不在 SSG 或 SSR 之间做出选择。 SSG 通过提前构建页面来提升性能,而 SSR 利用动态能力按需生成 HTML。但是,随着 Astro 2.0 的发布,其通过混合渲染实现了 SSG、SSR 的两全其美。
Astro 2.0 是第一个为 Markdown 和 MDX 提供完整类型安全的 Web 框架。 Astro 可以通过内置的解析、验证和自动 TypeScript 类型生成来组织 Markdown。 对于在站点上使用 Markdown 来说,Astro 2.0 的发布是一个很好的消息。
Astro 2.0 的新特性还包括:Markdown 和 MDX 的自动类型安全检测、混合渲染(静态&动态结合)、重新设计错误(引入错误叠加层(Error Overlay))、开发服务器优化、集成 Vite 4.0 等等。
Astro 在 2 年前开源,在 Github 上有超过 28.4K 的 star,1.4k 的 fork,有超过 29.2k 的项目使用它,NPM 周平均下载量超过 84K。而本文将重点探索 Astro 2.0 中的混合渲染的工作原理。
2.Astro 的构建过程
Astro 的构建过程分多个阶段进行,从 Vite 生成的服务器端 JavaScript 包开始。 输出的包内容包括:
- 用于渲染 HTML 的服务器端 JavaScript
- 使用静态分析收集客户端交互所需的所有组件的客户端清单(client manifest)
- 客户的 CSS 和其他资源
接下来,Astro 使用客户端清单启动第二个构建过程,该过程打包优化的客户端 JavaScript。如果开发者配置的 output 是 static,Astro 将执行服务器端 JavaScript 并将输出写入 .html 文件, 然后丢弃服务器端 JavaScript。
如果配置的 output 是 server,则 Astro 将服务器端 JavaScript 传递给适配器以进行进一步处理。 适配器确保服务器端 JavaScript 与特定托管服务提供商的 JavaScript 运行时兼容。 请注意,在这种情况下,最终输出不是一组 .html 文件,而是渲染 HTML 所需的 JavaScript 代码。
不幸的是,这种设置非常严格,不允许在同一个项目中混合使用静态和动态路由,而这正是 Astro 2.0 所需要的。
3.Astro 2.0 混合渲染
Astro 2.0 改进了 astro 构建管道以处理混合渲染。 在最初的打包过程中,一个新的静态分析步骤决定了哪些页面应该被预渲染。 这允许开发者将路由(routes)分成单独的块,具体取决于何时渲染。
SSG:与以前的 static 过程非常相似,执行预渲染块并将输出写入 .html 文件, 然后丢弃这些块。
SSR:与以前的 server 过程非常相似,服务器块被传递给适配器以进行进一步处理。 它最终将部署为无服务器或边缘函数,具体取决于适配器。
4.静态分析
4.1 es-module-lexer
es-module-lexer 是 es-module-shims 中使用的 JS 模块语法词法分析器。是一个非常小的单个 JS 文件,压缩后为 4K,其包含内联 WebAssembly,仅用于对 ECMAScript 模块语法进行非常快速的源代码分析。
es-module-lexer 输出的是 export 列表和 import 说明符的位置,包括动态导入(Dynamic Import)和导入元(Import Meta)处理。
举个性能的例子:es-module-lexer 对 720K 的 Angular 1 文件 可以在 5 毫秒内完成解析,而最快的 JS 解析器 Acorn 则需要 100 多毫秒。接下来要说的 Astro 静态分析技术就是在 es-module-lexer 的 基础上完成的。
4.2 Astro 的静态分析
静态分析是一种用于分析源代码而不实际执行的技术, Astro 在很多地方都依赖于这种方法,在构建过程中尤为重要。 出于性能原因,Astro 必须能够在不执行其源代码的情况下对页面进行分类。 对于体积较小的项目,执行可能不是问题,但随着站点规模的扩大执行就会成为瓶颈。
混合渲染是专门为静态分析而设计的,Astro 的构建过程能够检查是否存在 export const prerender = true 语句以确定应预渲染哪些页面。 为了高效地做到这一点,Astro 借助于出色的 es-module-lexer 库,这个库也在 Node.js 内部使用。
---
// 这个路由应该在构建时预渲染
export const prerender = true
const text = await fetch("https://example.com/").then((res) => res.text())
---
<article set:html={text} />
5.混合渲染场景
5.1 用例:提高流行页面的渲染性能
考虑一个使用 Astro 构建的小、中型电子商务网站,包含数千种不同的产品。 如果要使用静态站点生成 SSG,那么任何一个产品发生变化或缺货时,将不得不重新构建整个网站。
此时就需要服务端渲染,即允许产品页面根据每个请求动态生成。 这又引入了一个新问题,即主页也不再是静态渲染的。而加载性能对于销售量至关重要,同时主页没有任何动态内容需要根据每个请求重新构建。
混合渲染通过提前将主页渲染为静态 HTML 来解决此性能问题,同时保持站点的其余部分按需渲染。
5.2 将 API 添加到现有的静态站点
随着静态网站的增长,可能会发现需要托管一个公共 API。 从历史上看,向静态站点添加 API 需要特定于机器的配置,但在 Astro 2.0 中,可以在不牺牲静态页面性能的情况下添加服务器端点(API 路由)。
import type { FunctionalComponent } from 'preact';
import { h } from 'preact';
// 下面需要通过fetch请求数据
const data = await fetch('https://example.com/movies.json').then((response) =>
response.json()
);
console.log(data);
const Movies: FunctionalComponent = () => {
return <div>{JSON.stringify(data)}</div>;
};
export default Movies;
事实上,服务器端点可以通过 Integrations API 的 injectRoute hooks 自动添加,从而快速实现与强大的第三方生态系统集成!
injectRoute({
pattern: '/foo/[dynamic]',
entryPoint: 'foo/dynamic-page.astro',
});
5.3 用例:提高大型站点的构建性能
静态生成 SSG 很难扩展到数千个页面,因为每个页面都必须在构建过程中渲染。 服务端渲染将页面渲染推迟到请求时,从而消除构建过程中的潜在瓶颈。
当 getStaticPaths 生成数百条路由时,服务器端渲染可以带来显著的性能提升。 在内部测试中,通过将动态路由切换到服务器端渲染,发现构建时间最多缩短了 30%。
6.Astro 2.1 新特性
2023 年 3 月 8 日, Astro 2.1 发布,又新增了以下诸多特性:
6.1 内置图像支持
Astro 2.1 为 Astro 带来了对内置图像优化的实验性支持。 要在项目中启用它,可以使用 --experimental-assets 标志运行 Astro 或在 Astro 配置文件中设置 experimental.assets 布尔标志。
如果已经熟悉 @astrojs/image 集成,Astro 的新内置 <Image> 组件应该感觉很熟悉,但有一些显著的改进。 现在可以使用 width 和 height 属性调整图像的大小而不改变其纵横比,并使用新的 quality 属性控制图像优化级别。
---
import { Image } from "astro:assets"
import penguin from "~/assets/penguin.png"
---
<Image src={penguin} quality="high" alt="a high-quality penguin" />
生成的图像将以现代网络格式自动优化,以最小的累积布局偏移 (CLS) 实现最佳性能。
6.2 集成 Markdoc
一个新的实验性 Markdoc 集成也与 Astro 2.1 一起发布。 现在可以通过在项目目录中运行 npx astro add markdoc 来将 @astrojs/markdoc 添加到项目中。
Markdoc 是 Stripe 在需要适合其规模的东西时开发的一种很有前途的新内容格式。 虽然仍然喜欢 Astro 的 MDX,但用户在处理大量 MDX 文件时发现速度明显下降的情况并不少见。
Astro 建立了一个性能基准来比较 Astro 内部的 Markdoc 构建性能与现有的内容格式:Markdown 和 MDX,惊奇的发现构建速度比 MDX 渲染静态内容和可交互岛屿快了 3 倍。
一旦将 @astrojs/markdoc 集成添加到项目中,Markdoc 就可以使用 .mdoc 文件扩展名在内容集合中使用。 可以将 Markdoc 文件与现有的 Markdown 和 MDX 文件混合使用,以实现平稳、渐进的迁移过程。
6.3 astro check
astro check 是一个内置的 Astro CLI 命令,可以对项目的 .astro 文件运行 TypeScript 类型检查。 它通常用于在合并更改或部署站点之前捕获 CI 中的错误和类型错误。
{
"scripts": {
"dev": "astro check --watch & astro dev"
}
}
在 Astro 2.1 中,一个新的 --watch 标记已添加到 astro 检查命令中。 这将产生一个长时间运行的进程,用于侦听文件更改并对更改的文件重新运行诊断。 可以通过将脚本添加到 package.json 来与开发服务器并行运行它。
6.4 动态路由的推断类型
Astro 页面中的 getStaticPaths() 导出用于定义应为 SSG 模式下的动态路由构建哪些页面。 对于每个页面,返回一个 params 和 props 属性。
在 Astro 2.1 中,添加了两个新的 helper 来推断这些类型,并在模板中使用这些值时提供类型检查。
给定以下 getStaticPaths() 函数:
---
// Example: src/pages/blog/[slug].astro
export async function getStaticPaths() {
const posts = await getCollection("blog")
return posts.map((post) => {
return {
params: { slug: post.slug },
props: { draft: post.data.draft, title: post.data.title },
}
})
}
---
现在可以使用新的 InferGetStaticParamsType 和 InferGetStaticPropsType 来获取参数和 props 值的类型定义,而无需手动定义它们:
import { InferGetStaticParamsType, InferGetStaticPropsType } from 'astro';
export async function getStaticPaths() {
/* ... */
}
type Params = InferGetStaticParamsType<typeof getStaticPaths>;
type Props = InferGetStaticPropsType<typeof getStaticPaths>;
const { slug } = Astro.params;
// ^? { slug: string; }
const { title } = Astro.props;
// ^? { draft: boolean; title: string; }
7.本文总结
本文主要和大家介绍Astro 2.x,文章从什么是 Astro、Astro 的构建过程、Astro 2.0 混合渲染原理、混合渲染适用场景、Astro 2.1 新特性等诸多维度展开。
因为篇幅有限,文章并没有过多展开,如果有兴趣,可以在我的主页继续阅读,同时文末的参考资料提供了大量优秀文档以供学习。最后,欢迎大家点赞、评论、转发、收藏!
参考资料
https://astro.build/blog/hybrid-rendering/
https://astro.build/blog/astro-2/
https://astro.build/blog/introducing-content-collections/
https://vitejs.dev/blog/announcing-vite4.html
https://astro.build/
https://docs.astro.build/en/guides/markdown-content/
https://github.com/withastro/astro
https://astro.build/blog/astro-210/