你的博客用不着什么 JavaScript 框架

  最近,我终于决定将自己的网站从基于 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。

  你可以继续编写 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 视为可选的额外功能,而不是体验的基本组成部分。我鼓励你也这样做。

上一篇:TCP协议为什么需要三次握手?


下一篇:90 后开源老司机徐亮:从大学开始全职做开源是一种怎样的体验?