Sapper:迈向理想的 Web 应用框架
迈出下一步
如果你要列出完美的 Node.js Web 应用框架的特点,你可能会想到以下这些:
- 它应该能够进行服务器端渲染,以实现快速的初始加载,并且没有 SEO 方面的顾虑。
- 作为推论,你的应用代码库应该是通用的——在服务器端和客户端只需编写一次。
- 客户端应用应该水合服务器端渲染的 HTML,将事件监听器(等等)附加到现有的元素上,而不是重新渲染它们。
- 导航到后续页面应该即时完成。
- 离线和其他渐进式 Web 应用特性必须开箱即用地支持。
- 最初只应加载第一个页面所需的 JavaScript 和 CSS。这意味着框架应该在路由级别进行自动代码分割,并支持动态
import(...)
以实现更细粒度的手动控制。 - 性能方面不能妥协。
- 一流的开发人员体验,包括热模块重载和所有装饰。
- 生成的代码库应该易于理解和维护。
- 应该能够理解和自定义系统的每个方面——没有锁定在框架中的 webpack 配置,并且尽可能少的隐藏“管道”。
- 学习整个框架应该很容易,在不到一个小时内就能完成,不仅对于有经验的开发者而言。
Next.js 接近于这个理想。如果你还没有遇到它,我强烈建议你浏览 learnnextjs.com 上的教程。Next 引入了一个绝妙的想法:你应用的所有页面都是 your-project/pages
目录中的文件,并且每个文件都只是一个 React 组件。
其他一切都是从这个突破性的设计决策中产生的。查找负责特定页面的代码很容易,因为你可以查看文件系统,而不是玩“猜组件名称”的游戏。项目结构的争论已成为过去。SSR(服务器端渲染)和代码分割的结合——React Router 团队放弃了,宣称“祝愿尝试服务器端渲染、代码分割应用的人好运”——变得微不足道。
但它并不完美。尽管列举如此优秀的事物中的缺陷可能有些刻薄,但确实存在一些。
- Next 使用称为“路由掩蔽”的功能来创建漂亮的 URL(例如
/blog/hello-world
而不是/post?slug=hello-world
)。这破坏了目录结构对应于应用结构的保证,并迫使你维护将这两种形式相互转换的配置。 - 所有路由都被假定为通用的“页面”。但通常需要仅在服务器端渲染的路由,例如 301 重定向或提供页面数据的 API 端点,而 Next 没有很好的解决方案。你可以向你的
server.js
文件中添加逻辑来处理这些情况,但这感觉与为页面采取的声明式方法相矛盾。 - 要使用客户端路由器,链接不能是标准的
<a>
标签。相反,你必须使用特定于框架的<Link>
组件,例如,这在像这篇博文这样的 markdown 内容中是不可能的。
但真正的问题是,所有这些好处都是有代价的。最简单的 Next 应用——一个渲染一些静态文本的单个“hello world”页面——包含 66kb 的 gzip 压缩的 JavaScript。解压缩后为 204kb,对于移动设备来说,这是一个不小的代码量,需要在性能至关重要的时刻解析,而性能是决定用户是否会继续使用你的应用的关键因素。而这仅仅是基线。
我们可以做得更好!
编译器即框架的范式转变
Svelte 引入了一个激进的想法:如果你的 UI 框架根本不是框架,而是一个将你的组件转换为独立 JavaScript 模块的编译器呢?与其使用 React 或 Vue 这样的库(它们对你的应用一无所知,因此必须是一个一刀切的解决方案),我们可以交付高度优化的原生 JavaScript。仅仅你的应用所需的代码,并且没有基于虚拟 DOM 的解决方案带来的内存和性能开销。
JavaScript 世界正在转向这种模式。Stencil,一个来自 Ionic 团队的受 Svelte 启发的框架,编译成 Web Components。Glimmer没有编译成独立的 JavaScript(其优缺点值得单独撰写一篇博文),但该团队正在围绕将模板编译成字节码进行一些引人入胜的研究。(React 也参与了行动,尽管他们目前的研究重点是优化你的 JSX 应用代码,这可能更类似于 Angular、Ractive 和 Vue 在过去几年中一直在进行的提前优化。)
如果我们以这种新的模式为起点会发生什么?
介绍 Sapper
Sapper 是这个问题的答案。Sapper 是一个 Next.js 风格的框架,旨在满足本文开头列出的 11 个标准,同时大幅减少发送到浏览器的代码量。它作为 Express 兼容的中间件实现,这意味着它易于理解和自定义。
使用 React 和 Next 编写的相同“hello world”应用,在 Sapper 中仅重 7kb。随着我们探索优化可能性空间(例如,对于非交互式页面,根本不发送任何 JavaScript,而只发送处理客户端路由的小型 Sapper 运行时),这个数字可能会在未来进一步下降。
更“真实世界”的例子呢?方便的是,RealWorld 项目(该项目挑战框架开发 Medium 克隆的实现)为我们提供了一种找出答案的方法。Sapper 实现 渲染一个交互式主页需要 39.6kb(11.8kb 压缩)。
整个应用需要 132.7kb(39.9kb 压缩),这比 327kb(85.7kb)的参考 React/Redux 实现小得多,但即使它一样大,也会感觉更快,因为使用了代码分割。这是一个关键点。我们被告知需要代码分割我们的应用,但如果你的应用使用传统的框架(如 React 或 Vue),那么你的初始代码分割块的大小有一个硬性下限——框架本身,这很可能占你应用总大小的很大一部分。使用 Svelte 方法,这种情况不再存在。
但大小只是故事的一部分。Svelte 应用也非常高效和内存友好,并且该框架包含强大的功能,如果你选择“最小”或“简单”的 UI 库,你将不得不牺牲这些功能。
权衡取舍
对于许多评估 Sapper 的开发者来说,最大的缺点可能是“但我喜欢 React,并且我已经知道如何使用它”,这是合理的。
如果你属于这个阵营,我建议你至少尝试一下其他框架。你可能会惊喜地发现!Sapper RealWorld 的实现总共包含 1201 行源代码,而参考实现则包含 2377 行,因为你可以使用 Svelte 的模板语法(只需五分钟即可掌握)非常简洁地表达概念。你可以获得作用域 CSS,内置未使用的样式删除和压缩,如果你愿意,还可以使用 LESS 等预处理器。你不再需要使用 Babel。SSR 非常快,因为它只是字符串连接。我们最近还引入了svelte/store,一个微小的全局存储,可以在你的组件层次结构之间同步状态,无需任何样板代码。最糟糕的情况是,你最终会感到证明了自己的正确性!
但仍然存在权衡取舍。有些人对任何形式的“模板语言”都有病态的厌恶,也许你也是如此。JSX 支持者会用“它只是 JavaScript”的口号来打击你,而这正是 React 最大的优势,即它具有无限的灵活性。这种灵活性也伴随着自己的权衡取舍,但我们今天不讨论这些。
然后是生态系统。围绕 React 特别是开发工具、编辑器集成、辅助库、教程、StackOverflow 答案,甚至工作机会——的宇宙是无与伦比的。虽然确实,将“生态系统”作为选择工具的主要原因表明你陷入了局部最优,容易被进步的浪潮所淹没,但这仍然是现有工具的一个主要优势。
路线图
我们还没有达到 1.0.0 版本,在达到这个版本之前,一些事情可能会发生变化。一旦我们达到这个版本(很快!),就会有很多令人兴奋的可能性。
我相信 Web 性能的下一个前沿是“全应用优化”。目前,Svelte 的编译器在组件级别运行,但一个理解这些组件之间边界的编译器可以生成更高效的代码。React 团队的Prepack 研究基于类似的想法,而 Glimmer 团队也在这个领域做了一些有趣的工作。Svelte 和 Sapper 非常适合利用这些想法。
说到 Glimmer,将组件编译成字节码的想法是我们可能在 2018 年借鉴的想法。像 Sapper 这样的框架可以根据你的应用的特点来确定使用哪种编译模式。它甚至可以为初始路由提供 JavaScript 以获得最快的启动时间,然后为后续路由懒加载字节码解释器,从而实现启动大小和应用总大小的最佳组合。
不过,我们主要希望 Sapper 的方向由其用户决定。如果你是一位喜欢生活在技术前沿的开发者,并且希望帮助塑造我们构建 Web 应用的未来,请加入我们在GitHub 和Discord 上的社区。