今年年初,我终于决定将自己的网站从基于 PHP 的 CMS 移植到基于 JavaScript 的静态网站生成器(SSG)了。原因如下:
虽然一开始我是“全栈”开发人员,但现在我只负责前端工作:如果我需要编写自定义功能,那么能用 JavaScript 编写的代码我就不想用 PHP 来写。
我不需要抽象层或 CMS 的复杂性——我最喜欢用 markdown 文件编写内容,并且希望永远不要再碰 Mysql 数据库或所见即所得编辑器。
我想提高网站的性能:静态 HTML 文件在 99% 的时候都比动态页面更快。
最后还有成本优势:LAMP stack 服务器得按月付费;Netlify 的免费套餐(每月构建 300 分钟)应该可以轻松覆盖零成本个人博客的需求。
当我决定使用静态站点生成器和 JavaScript(排除了 Jekyll 和 Hugo)后,就只剩下两个差别颇大的选项了:
根据官方网站的说法,“Gatsby 是一个基于 React 的免费开源框架,可帮助开发人员构建速度飞快的网站和应用”。它有一个由 GraphQL 支持的数据层,并将所有内容输出到静态文件,使你可以在几乎任何地方托管它。
当我第一次听说我可以编写 React 并使用这个很酷的 GraphQL 新玩意儿,同时还能输出不需要 JavaScript 的静态页面时,我很想尝试它一下。我是这么想的:“这听起来像是渐进增强,但用不着什么投入”。不幸的是,就像大多数听起来过于美好的事情一样,经过一些调查我发现它就是个坑。用户首次访问 Gatsby 网站时会发生这些事情:
用户请求一个页面。
服务器将静态生成的 HTML 文档发送到用户的浏览器,然后浏览器开始渲染页面。
HTML 文档到达后,JavaScript 包(包括 React 库和渲染页面所需的其他 JavaScript)开始在后台下载、解析和编译。
JavaScript 已准备就绪,可以运行——整个 DOM 通过 React 组件“被水化”(hydrated)。
这里就有些不对劲——Gatsby 需要你以 React 组件的形式再加载一次页面;在完成多出来的这一步之前,所有需要 JavaScript 的元素(例如按钮、菜单、自定义输入)实际上都不能交互。
哪怕你的网站没有任何互动元素(链接除外,即使没有 Gatsby,它们也无需 JavaScript 即可工作),你的用户也必须下载这部分 JavaScript,仅仅是为了将你的网站变成单页应用程序(SPA);SPA 是有自己的缺点的,我们稍后再提。
这种多出来的操作看来是违背我转向 SSG 的初衷(提高页面速度)的。华丽的 Gatsby 网站在 2,000 美元的 MacBook 上可能很快,但对于使用 3G 连接和廉价智能手机的用户来说,它显示是能显示出来,但是没有响应;用户等待加载 JavaScript 的过程要持续 15 秒。电池和数据流量也得跟着往下掉了。
如果浏览器需要解析 296kb 的 JavaScript 代码才能显示出博客文章的列表,这就不是什么"渐进增强”,而是用错了工具。从网站 /Web 应用的大致区别来看,React 是用于构建 Web 应用的,这种应用需要有响应用户输入或实时获取数据的交互式 UI;而博客只是一个网站而已。
单页应用程序这种网站放弃了传统的 Web 导航方法,即通过加载新的 HTML 文档来加载新内容;相反,它使用 AJAX 和 History API 之类的 JavaScript 特性来切换到新内容上,而不会触发页面加载。它的目标是提高感知的性能,并使网站看起来更像“原生”应用(从应用商店下载的那种)。不再需要整页重新加载的问题在于,浏览器和辅助技术将页面加载用作触发某些有用行为的信号,包括宣布新页面的标题或将键盘焦点重置到文档的开头。
如果你在开发关注可访问性的单页应用程序,那么你可能会试着使用 JavaScript 来模拟浏览器的行为。Gatsby 试图通过包含一个 RouteAnnouncer 组件来为你解决这个问题。它使用一个 ARIA live region 来宣布页面的 title 或 h1,以对使用屏幕阅读器软件的用户提示页面跳转行为。但这种方法也存在问题:它在配置和本地化方面仍然存在很多未解决的 issue。
我们已经看到,单页应用程序在导航方面存在固有的可访问性问题,但要注意的是,使用前端框架也会在其他方面带来可访问性问题。在 2020 年 2 月对 100 万个首页的调查中,WebAIM 发现使用 React 的网页的可访问性错误比平均水平高 5.7%;而使用 Vue 的网页则高出 25%。这并不一定意味着框架一定会导致这些错误,但是更多的 JavaScript 与更差的可访问性之间存在很强的相关性。
很有可能,你构建的第一个网页的性能要比之后构建的许多页面都要好得多——它由一个 HTML 文件和一些 CSS 组成,也许还有一些未优化的图像,但它们并不会阻止页面加载。如果你也有我这样的经历,那么开始添加 JavaScript 的那一刻,你的网页性能就开始急剧下降了。并非所有字节都是一样的:与同等大小的 JavaScript 文件解析、编译和执行所需的时间相比,图像解码和渲染到屏幕所需的时间要少得多。
JavaScript 是一种强大的语言,可以完成一些令人难以置信的事情,但是在开发中你很容易过早开始使用它,其实本来用 HTML 和 CSS 就够了。应该看看最小功能原则:在你充分利用功能较弱的语言(HTML)之前,请不要使用功能更强大的语言(JavaScript)。在我看来,将博客变成 JavaScript 单页应用程序会带来不必要的复杂性。
这篇文章并不是要批判 Gatsby 而写的。它的背后有一些聪明的头脑,他们已经承认了本文中提到的许多问题,并试图解决它们。静态渲染和水化的页面还是比完全客户端渲染的 React 应用(如 create-react-app 生成的页面)要好得多,后者没有 JavaScript 就没法用。我确实不太满意 Gatsby 的宣传手法,他们说 Gatsby 适合任何类型的网站。客户端 JavaScript 是有成本的,开发人员应该意识到这一点。
这使我陷入了一个两难境地:使用 Gatsby 开发网站是绝妙的体验;但是开发体验(DX)应该永远排在用户体验(UX)之后。那么如何在构建 Gatsby 网站时避免那些因为大量使用 JS 而带来的固有问题呢?当然,我们应该尽量删掉那些 JavaScript。所幸 Gatsby 社区内做出了很多努力来构建更、,更轻量级的网站:
首先,使用 gatsby-plugin-preact 将 React 换成 Preact 可以节省几千字节。我在 Component Gallery 上用了它,立刻将 JavaScript 负载减少了约 30kb。
如果你想用更激进的方法,可以使用一个插件来从你的 Gatsby 网站删除所有 Gatsby JavaScript。
https://www.gatsbyjs.org/packages/gatsby-plugin-no-javascript/
你可以继续编写 react 组件和 GraphQL,甚至可以使用 CSS-in-JS 库(只要它输出 CSS 或内联样式),而无需向浏览器发送任何 JavaScript。只要扔掉所有客户端 JavaScript 就可以解决 Gatsby 的大多数问题。Gatsby Starter Low Tech 博客使用 no-javascript 插件和其他一些技术(包括将所有图像转换为灰度),来帮助你创建一个轻量且节能的博客。
这时候我感觉有点不对劲——使用一个会大量推送客户端 JavaScript 的框架,却要删除所有 JavaScript 代码,这似乎是一种很复杂的网站构建方式。我想看看是否可以不用客户端 JavaScript 来构建功能完善的博客,这样就用不着什么插件来删除它了。于是我转向了另一个选项:
Eleventy 鼓励你按照自己的意愿构建网站。你可以使用自己最熟悉的技术,它只负责生成页面。Eleventy 为你提供了十种可以任意搭配的模板语言选项,包括 markdown、nunjucks 和 liquid;这意味着我可以从 Craft 中复制并粘贴旧的模板,更改文件扩展名,并做一些细微的调整就能运行在 Eleventy 中。用不着针对什么新的打包器来调整前端构建流程,我只需放入现有的 webpack 文件和 src 文件夹即可。使用并发包,我可以在 Eleventy 的 serve 过程中同时运行构建脚本。
像 Gatsby 一样,Eleventy 也有一个插件生态系统(虽然很小,但增长迅速)。我挑选了一些不需要添加客户端 JavaScript 也能添加功能的插件:
在帖子中显示代码段时,通常会包含特定于语言的语法高亮显示。有一些 JavaScript 库可以做到这一点,其中最流行的似乎是 Prism——你可以在客户端中运行它,但由于我们使用的是 JavaScript SSG,因此可以在构建时运行它,并将语法高亮显示所需的 HTML 元素和 CSS 类直接烘焙到文档中——这样就无需在浏览器中下载这个库了。
eleventy-plugin-embed-tweet 也可以在构建时而非客户端运行 JavaScript。Twitter 的默认嵌入代码迫使用户下载大量 JavaScript 才能显示一条推文。这个插件可以在构建时获取并渲染推文,这样只需少量 HTML 和 CSS 即可,根本不需要额外的 JavaScript。
与其他新技术一样,Eleventy 缺少某些更加成熟的工具所提供的功能。例如,在 Eleventy 中没有一种优雅的方法来生成响应式图像。相比之下,Gatsby 中出色的 gatsby-image 插件可以生成延迟加载和响应式的图片元素,并能在加载全分辨率文件后在低分辨率或 SVG 版本的图像间平滑切换。Eleventry 还有一些让我感到困惑的事情:我有一阵子一直搞不懂它的分页功能,认为它只是将帖子分页到指定大小的一些组中,之后才意识到它可以动态生成全新的页面;我还发现自己在同一文件中混用了模板语言:你可以随意在 markdown 文件中包含 nunjucks 标签,或将基于 yaml 的 frontmatter 换成 JavaScript,但这会破坏语法高亮显示、linting 和自动格式化。
如果你还是选择了 Gatsby,我也不会怪你——有时候使用一个 opinionated 的框架也不错,并且如果你想要快速完成工作,这是一个可靠的解决方案。只是要注意它的性能成本,以及所有与 JavaScript 相关的潜在可访问性问题。
我选择使用 Eleventy 来构建自己的网站,但我知道这种方法并不适合所有人——完全按照自己的意愿来构建某些东西可能是很麻烦的事情。但你也用不着完全学我——与 Gatsby 类似,Eleventy 也有许多入门项目可以用作基础。其中一些工具,例如 Andy Bell 的 Hylia 入门套件可以在几分钟内搞定一个网站。它甚至预配置了 Netlify CMS,因此你无需编写任何代码即可编辑网站内容。
我学到了什么呢?使用 Eleventy 可以轻松构建不带 JavaScript 的博客,但总会有一些功能需要客户端 JavaScript 的:
我的网站拿掉了 Google Analytics,但它对用户来说没什么用途,所以我也不在乎——我会在另一篇文章中介绍它的服务端替代品。
我使用了 loading="lazy"属性来延迟加载图片,但它的浏览器支持不够完整,并且在原生浏览器实现改进之前,它无法在加载图片时淡入淡出。
黑暗模式切换——虽然我可以只用 CSS 来实现,无需访问 cookies 或本地存储,但我没办法在页面之间保持设定的值。
我是否会在不久的将来在网站上加入 JavaScript 呢?答案可能是否定的:我上面列出的功能并不是那么重要的。我并不是推荐大家都删除自己网站上的所有 JavaScript 文件,但从现在开始,在构建网站时我会尝试将 JavaScript 视为可选的额外功能,而不是体验的基本组成部分。我鼓励你也这样做。
参考阅读:
https://iainbean.com/posts/2020/your-blog-doesnt-need-a-javascript-framework/
InfoQ Pro 是 InfoQ 专为技术早期开拓者和乐于钻研的技术探险者打造的专业媒体服务平台。扫描下方二维码关注 InfoQ Pro,即可在【充电计划】中获取技术 PPT 下载链接,每周更新哟~持续关注我们,还有更多技术分享活动与干货资料,就等你来!