本文为翻译
原文标题:Move over JavaScript: Back-end languages are coming to the front-end
原文作者:Klint Finley
在网络计算早期,任何繁重的工作都会交由大型机处理:用户通过可视终端连接上这些巨大的机器后,基本只能收发文本。上世纪 70 年代,个人电脑诞生,这使得客户端拥有了如同服务端一样的处理真实计算的能力(比如在网络中 处理身份验证 和 存储)。上世纪 90 年代,互联网的崛起又将“钟摆”摆回到了服务端,此时的 Web浏览器 和 大型机的终端 其实扮演着差不多的角色。
在过去十年,由于开发者通过 JavaScript 创建出了 “单页应用程序”(SPA),客户端再次回归。但是,有些新工具 正在将“钟摆”摆回到服务端。
这类工具的先驱是 Phoenix,是编程语言 Elixir 的一个框架,它拥有一个叫做 LiveView 的特性。通过 LiveView 和 一点 JavaScript,开发者可以为 实时应用(比如 聊天室 或 Twitter风格的动态应用)创建 基于浏览器的 UI。所有 UI 元素 先在服务端渲染,再发送到浏览器迅速展示。我们依赖的那 一小点 JavaScript 只是用来建立一个 WebSocket 连接而已,该链接用于处理浏览器的输入,并从服务端接收更新后的 HTML / CSS。
Phoenix 并不是首个为 后端开发者 提供 创建前端 UI 能力的平台。微软 .NET ASP.NET Web Forms 早在 2002 就存在了,它启发了许多新工具。我举几个例子,Node.js 的 Caldera,PHP 框架 Laravel 的 Livewire,还有 Ruby on Rails 的 StimulusReflex。微软在这期间还发布了名为 Blazor Server 的 .NET 新特性,用于将老旧的 Web Forms 思想 现代化。
“我的目标不是除掉 SPA,而是让一大类应用程序能够免于使用 SPA。” Phoenix 的创建者 Chris McCord 如是说。
My goal is not to get rid of single-page applications, but to obviate them for a large class of applications.
Web 开发 (不那么)美好的旧时光
假设现在是 1997 年,你想开发一个 基于 Web 的日历程序。在互联网早期,你的应用是完全依赖于服务端的。服务端会渲染一个包含你每日日程的静态页面,有序排列在表格的 31 个框里。你不能通过 拖放日程 来修改日程的日期,而只能通过页面底部的表单添加新的日程。点击 “提交” 按钮会将数据发送到服务端,服务端会将数据存储在数据库 并 渲染新页面来响应更改。简而言之,更新一天的日程需要刷新整个页面。
即便网速很快,这也称不上一个很优秀的体验。但随着 浏览器 和 Web技术 变得越来越成熟,仅更新网页上的小段 成为了可能。使用一点 JavaScript,就能创建一个 点击 “提交” 后立即展示新日程 的日历程序。脚本会异步发送信息到服务端,并只更新你添加新日程那天的框,而不是整个页面。你可以拖放日程,甚至可以创建一个离线日历,以防你掉线,一旦你重新连接,可以同步更改。
近年来,Angular.js,React 和 Vue.js 这些开源 JavaScript UI 库使得 在 Web 创建 日益复杂的 类桌面应用程序 成为了可能。这种方式有很多优势,比如 高响应性 和 离线能力。
但这其实也是在权衡取舍。将更多的计算任务交给浏览器,会给客户端的 CPU 和 电池寿命 带来更多负担。由于客户端必须下载 JavaScript 的程序逻辑,所以这会增加下载文件总共的大小 并 占用大量带宽。而且我们最终总会有两个不同的代码库,一个用于客户端,用 JavaScript 编写,而另一个用于后端,一般用其他语言编写。
对于很多应用程序来说,这种权衡是值得的,例如,日历程序需要离线支持能力,游戏依赖很多的客户端程序逻辑。但 Phoenix 创建者 Chris McCord 认为,对于非常依赖网络连接的应用程序,在服务端处理程序逻辑 和 页面渲染 更有意义。
以基于浏览器的聊天程序为例。一般来说,会使用 JavaScript 接收用户输入,发送信息到服务端,拉取其他用户的讯息,然后重新生成页面。而如果使用 LiveView,浏览器会创建一个 WebSocket 连接到 Phoenix 服务器 并 将信息传递过去。服务端会收集聊天室中所有其他用户的信息,渲染页面需要更改的部分,并发送给浏览器。不仅减少了客户端的处理,还减少了浏览器的接收负载,因为程序逻辑没有下放出去。
Phoenix 崛起
McCord 最初想为 Ruby on Rails 创建一个类似的体验,他创建了一个叫 render-sync 的库,这个库可以在服务器上进行部分重新渲染,并通过 WebSocket 发送到浏览器。但他对 render-sync 的性能并不满意。他想找到比 Rails 更快更灵敏度东西,毕竟 Rails 不是为实现实时应用程序设计的。在评估了几个不同的编程平台,他开始研究 Erlang 平台的 Elixir 语言。
Erlang 于 1986 由 爱立信的一个团队创建,用于驱动电子通讯应用程序。“它们很早就开始处理分布式系统了,” McCord 说。“Erlang 没有注重 多核架构 的处理,因为当时 多核架构 还没诞生 ,不过他们创造的架构对于未来计算称得上是完美。”
关键在于,Erlang 设计基于支持大量并发用户。“Elixir 会运行百万级的轻量线程,” McCord 说。但是对于 McCord 来说,Erlang 真正与众不同之处在于 预调度进程 的能力,这样 CPU 就不会在任何一个线程上挂起。这样一来,如果有用户启动了 一个特别长的 CPU密集型进程,其他用户不必等待进程完成后才能完成自己的。
尽管在其他平台实现这类容错机制是可能的,但 McCord 很喜欢 Elixir,Elixir 能轻松解决很多问题。“你不必用什么特殊的方式编写代码,”他说。Elixir 为 McCord 带来了信心,通过服务端和浏览器的 WebSocket 长连接(和 telnet 或 SSH 连接差不多),去创建一个服务端渲染系统 来 支持实时应用程序。这使得开发者可以创建有状态的应用程序,而不必在 客户端 或 服务端 处理 “hydrate”,就像老旧的 ASP.NET WebForms 系统那样。
McCord 开始用 Elixir 编写 Phoenix 框架,着眼于最终增加 LiveView 特性。“LiveView 一直都是我最终要完成的的目标,但我得先做好准备工作,”McCord 说。
"LiveView was always the end game, but I needed to do the plumbing first."
Elixir 之外
尽管 McCord 选择在 Elixir 上建立 LiveView,而不是 Ruby,但 Ruby on Rails 现在也有一些不同的方式构建实时应用了。其中一个是 StimulusReflex,由 Nate Hopkins 创建。另一个是 Hotwire,用于构建基于 WebSocket 实时 Rails 应用 的 一组工具。 由 Basecamp 开发,用于他们自己的 Hey.com 邮件服务。
Rails 开发者表示这些新工具很符合他们的需求。Matt E. Patterson,Atlas Obscura 的一位开发者,他在之前的工作中曾使用过 StimulusReflex 和 Hotwire ,尽管如此,他表示 Rails 的可扩展性能力已经能够满足大多数用户的需求了。“我并不是说 Ruby 是最快的语言,但是人们看到的大多数现实中的可扩展性问题 都是因为运行了太多编写糟糕的数据库查询,和 Ruby 或 Rails 没什么关系,”他说。很多应用程序并不需要支持成千上万的并发用户。在这类场景中,对于开发者,去学习一门新的编程语言 和 背后的一整套新范式 并不是很值得。这有力地解释了为何 LiveView 背后的核心思想 能进入很多不同的语言和平台。在 2018 年,Caleb Porzio 看到了 McCord 的 LiveView 的演示,他深受启发,为 PHP 框架 Laravel 新建了一个版本。“我刚辞去工作,准备脱离工作和编程,好好休息一下,”他说。“但在我休假第二天,我看了 Chris 关于 LiveView 的演讲。真的惊到我了。我意识到它可以把 Laravel 带到一个新的高度。”他在一天之内创建了第一个原型程序,最终发展为了 Laravel 的 Livewire。
Porzio 首先尝试让 Livewire 像 LiveView 一样工作。“我通过 WebSocket 在后端运行长期进程,”Porzio 说道。“但 PHP 并非为此创造出来的。”他希望代码能在现有的 PHP 基础上运行良好,而无需创建新的自定义架构。所以他转而使用 AJAX 请求 传递 浏览器到服务端 的状态。换句话说,Livewire 在技术上并不像 Phoenix LiveView 那样 “live”。“但我们可以伪装得很好,”Porzio 开起玩笑。
确实,这比起 启动一个长链接来维护数据 要慢一些。但 Livewire 有一些措施能让这个过程变得更高效些,比如 预取(prefetch)。Livewire 还能帮助避免某些陷阱,比如对用户连接中断的处理。
Livewire 的用户表示,他们对这些权衡感到满意。Tina Hammar 是 瑞典 斯德哥尔摩 的一名全栈开发者。她一开始是一名 PHP 开发者,后来她学习使用 Laravel 和 Vue 构建了 SPA。在 2019,她用这些技术构建了自己的 SaaS 程序 BokaMarknad(瑞典语,意为 “图书市场”)。在 2020 年 2 月,Livewire 发布后,她用 Laravel 和 Livewire 完全重写了 BokaMarknad。
在此之前,Hammar 说她必须同时维护两个代码库:一个是前端的,用 JavaScript 编写,而另一个是后端的,用 PHP 编写。Livewire 让整个程序可以合并到一个代码库,这让代码更容易理解了。将大部分逻辑保持在后端也让 Hammar 对应用程序的安全性更有信心了,因为暴露给用户 凭证信息 和 敏感信息 的方式更少了。
而且这让她能够专注于她最熟悉的语言:PHP。“我爱 PHP,而且 Laravel 社区真的很棒,”她说。“我不介意编写 JavaScript。但专注于 PHP 更简单。”
自由全栈开发者 Josh Hanley 也有类似的经历,他将一个使用 Vue 和 Laravel 的应用程序迁移到 Laravel 和 Livewire。他同意,问题不在于 JavaScript,而在于需要同时维护两个独立的代码库。“我能在 blade 模版里搞定一切,” 他说。“这是最吸引人的地方。”
钟摆的终点?
当然,很多应用程序使用 LiveView、StimulusReflex 或 Livewire 这类工具,却仍会使用 JavaScript。但 JavaScript 是被嵌入到后端的模版里的,所以即使应用程序使用多种语言,也只会有一个代码库。
有几个 JavaScript UI 库 — 包括 React 和 Vue.js 已经支持服务端渲染了,而且要早于 Phoenix LiveView 推出。这使得将应用程序合并到一个基于 JavaScript 的代码库成为了可能。问题在于对应用程序来说,什么才是最有意义的。
“如果你发现自己在 JavaScript 框架中 而不是 Laravel 中写 HTML 模版,这可能是表明你更适合搞 SPA,”Hanley 说。“你要多问问自己想要实现什么目标,哪种技术更适合实现目标。”
Vue 的创建者 尤雨溪 也表示同意。“我不认为这两种模式从根本上优于另一种,它们有不同的权衡策略,这取决于你在构建的程序类型,以及开发者们感觉自己更适合怎样的 心智模型 / 语言,”尤雨溪 如是说。
与此同时,前后端框架的界限正变得越来越模糊。例如,一个即将推出的新特性,名为 React Server Components,它使得 React 能够将更多的计算任务卸给服务端。现如今,库的服务端渲染只是简单的渲染一组 UI 元素,并将其发送到 浏览器。但是通过 Server Components,一些 React 应用的逻辑讲运行在服务端。在概念上,这很类似 Phoenix LiveView,但不同之处在于,状态维护在客户端而不是服务端。这对在浏览器中运行大部分逻辑的应用程序来说是很有意义的。
也许我们看到的并不是钟摆的摆动,而是一种平衡状态。如果达到了这种状态,计算可以根据用户的需要平衡的在客户端和服务端之间运行。