Skip to main content

Sapper:迈向理想的网页应用框架

迈出下一个重要步伐

快速入门(供急躁者使用):Sapper 文档启动模板

如果要列出完美的 Node.js 网页应用框架应具备的特征,你可能会想到这些:

  1. 应该支持服务器端渲染,以实现快速的初始加载,且不存在 SEO 方面的限制
  2. 作为推论,你的应用程序代码库应该是通用的 — 一次编写,同时适用于服务器和客户端
  3. 客户端应用应该对服务器渲染的 HTML 进行水合(hydrate),为现有元素附加事件监听器(等),而不是重新渲染它们
  4. 后续页面的导航应该是即时的
  5. 必须默认支持离线功能和其他渐进式 Web 应用特性
  6. 初始加载时应只加载首页所需的 JavaScript 和 CSS。这意味着框架应在路由层面实现自动代码分割,并支持动态 import(...) 以实现更细粒度的手动控制
  7. 性能方面不做妥协
  8. 一流的开发者体验,包括热模块重载等所有配套功能
  9. 最终的代码库应该易于理解和维护
  10. 应该能够理解和自定义系统的每个方面 — 框架中不应该有被锁定的 webpack 配置,且尽可能减少隐藏的”管道”代码
  11. 应该能够在一小时内轻松掌握整个框架,而且不仅仅是对有经验的开发者而言

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 模块的编译器会怎样?我们可以发送高度优化的原生 JavaScript,而不是使用像 React 或 Vue 这样对你的应用一无所知因此必须是一个通用解决方案的库。只提供你的应用所需的代码,并且没有基于虚拟 DOM 解决方案所带来的内存和性能开销。

JavaScript 世界正在向这个模型发展Stencil,一个受 Svelte 启发的来自 Ionic 团队的框架,编译为 Web 组件。Glimmer _不会_编译为独立的 JavaScript(其利弊值得单独写一篇博文),但该团队正在进行一些围绕将模板编译为字节码的fascinating研究。(React 也在参与其中,尽管他们目前的研究重点是优化你的 JSX 应用代码,这可以说更类似于 Angular、Ractive 和 Vue 多年来一直在做的预编译优化。)

如果我们以新模型为起点会怎样?

介绍 Sapper

Sapper 就是对这个问题的回答。**Sapper 是一个类似 Next.js 的框架,旨在满足本文开头列出的十一个标准,同时大幅减少发送到浏览器的代码量。**它作为兼容 Express 的中间件实现,这使得它易于理解和定制。

相同的”hello world”应用在使用 React 和 Next 时需要 204kb,而在使用 Sapper 时仅需 7kb。随着我们探索优化的可能性,比如对于不需要交互的页面完全不发送任何 JavaScript(除了处理客户端路由的微小 Sapper 运行时),这个数字在未来可能会进一步下降。

那么一个更”真实世界”的例子呢?恰好,RealWorld 项目为框架开发 Medium 克隆版实现提供了一个测试方法。Sapper 实现仅需 39.6kb(压缩后 11.8kb)就能渲染一个交互式首页。

整个应用需要 132.7kb(压缩后 39.9kb),这比参考的 React/Redux 实现的 327kb(压缩后 85.7kb)要小得多,但即使体积相同,由于代码分割的存在,它也会_感觉_更快。这是一个关键点。我们被告知需要对应用进行代码分割,但如果你的应用使用像 React 或 Vue 这样的传统框架,那么你的初始代码分割块的大小就有一个硬性下限 — 框架本身,这可能占据你的应用总大小的很大一部分。使用 Svelte 方法,这种情况不再存在。

但大小只是故事的一部分。Svelte 应用也非常高效且内存利用率高,而且框架包含强大的功能,如果你选择”最小”或"简单”的 UI 库,就必须牺牲这些功能。

权衡

对于许多评估 Sapper 的开发者来说,最大的缺点可能是”但我喜欢 React,而且我已经知道如何使用它”,这是公平的。

如果你属于这个阵营,我邀请你至少尝试一下其他框架。你可能会感到惊喜!Sapper RealWorld 实现总共有 1,201 行源代码,而参考实现有 2,377 行,因为你可以使用 Svelte 的模板语法(掌握只需五分钟)非常简洁地表达概念。你可以获得作用域 CSS,内置未使用样式的移除和压缩,如果你愿意,还可以使用像 LESS 这样的预处理器。你不再需要使用 Babel。SSR 速度惊人,因为它只是字符串连接。我们最近还引入了 svelte/store,这是一个微型全局存储,可以在零样板代码的情况下同步组件层次结构中的状态。最坏的情况是你会感到自己的选择被证明是正确的!

但是仍然存在权衡。有些人对任何形式的”模板语言”都有一种病态的厌恶,也许这适用于你。JSX 支持者会用”它只是 JavaScript”的口号来抨击你,这也正是 React 最大的优势所在,那就是它具有无限的灵活性。这种灵活性也有其自身的权衡,但我们在这里不讨论这些。

然后是_生态系统_。特别是围绕 React 的领域 — 开发工具、编辑器集成、辅助库、教程、StackOverflow 答案,甚至工作机会 — 是无与伦比的。虽然把”生态系统”作为选择工具的主要理由表明你被困在局部最优解上,容易被进步的浪潮所淹没,但这仍然是现有框架的一个主要优势。

路线图

我们还没有到达 1.0.0 版本,在到达之前可能会有一些变化。一旦我们达到(很快!),就会有很多令人兴奋的可能性。

我相信网页性能的下一个前沿是”整体应用优化”。目前,Svelte 的编译器在组件级别运作,但一个能够理解组件之间边界的编译器可以生成更高效的代码。React 团队的 Prepack 研究建立在类似的想法上,Glimmer 团队也在这个领域做一些有趣的工作。Svelte 和 Sapper 很有条件利用这些想法。

说到 Glimmer,将组件编译为字节码的想法是我们可能在 2018 年借鉴的。像 Sapper 这样的框架可以根据你的应用特征来决定使用哪种编译模式。它甚至可以为初始路由提供 JavaScript 以获得最快的启动时间,然后为后续路由延迟提供字节码解释器,从而实现启动大小和总应用大小的最佳组合。

但主要的是,我们希望 Sapper 的方向由其用户决定。如果你是那种喜欢在技术前沿生活并希望帮助塑造我们构建网页应用未来的开发者,请在 GitHubDiscord 上加入我们。