SyMind / learning

路漫漫其修远兮,吾将上下而求索。
10 stars 1 forks source link

同构 JavaScript:Web 应用的未来 #15

Open SyMind opened 3 years ago

SyMind commented 3 years ago

原文:https://medium.com/airbnb-engineering/isomorphic-javascript-the-future-of-web-apps-10882b7a2ebc#.4nyzv6jea

在 Airbnb 过去的几年里,我们在构建富 Web 应用体验时学到了很多东西。2011年,我们携我们的移动端网站进军单页面应用领域,并推出了“愿望列表”和新设计的搜索页面等。它们都是大型的 JavaScript 应用程序,这意味着大部分在浏览器中运行的代码都是为了支持更现代的交互体验。

这种方式现在很普遍,像 Backbone.js、Ember.js 和 Angular.js 这样的库让开发人员更容易构建这些富 JavaScript 应用。然而我们发现,此类应用有一些显著的缺点。为了解释原因,让我们先快速回顾一下 Web 应用的历史。

JavaScript 的进化

Web 刚出现时,浏览体验是这样的:Web 浏览器会请求指定的页面(例如,“http://www.geocities.com”,使互联网上的某个服务器生成一个 HTML 页面并通过网络将其发回。因为浏览器功能不是很强大,大多是静态的 HTML 页面。JavaScript 是为了让网页更具动态性而创造的,但起初没有支持太多功能。

经过多年个人电脑的发展,富有创造力的技术人员将 Web 推向了极致,浏览器也在不断发展。现在,Web 已经发展为一个功能完备的应用程序平台,而高性能的 JavaScript 运行时和 HTML5 标准使开发人员能够创建以前只有在原生平台才能实现的富应用程序。

单页应用

不久,开发人员就利用这些新功能使用 JavaScript 在浏览器中构建整个应用程序。像 Gmail 这样的应用程序是单页应用程序的典型例子,它可以立即响应用户的交互,不必在每呈现一个新页面就往返一次服务器。

像 Backbone.js、Ember.js 和 Angular.js 这样的库通常被称为客户端 MVC(Model-View-Controller)或 MVVM(Model-View-ViewModel)库。典型的客户端 MVC 结构如下:

image

大部分应用程序逻辑(视图、模板、控制器、模型、国际化等)都存在于客户端中,通过 API 获取数据。服务器可以用任何语言编写,比如 Ruby、Python 或 Java,它主要为了提供初始的 HTML 页面。一旦浏览器下载了JavaScript 文件,就会执行他们来初始化客户端应用程序,从 API 获取数据并渲染其余的 HTML 页面。

这对用户来说非常好,因为一旦应用程序完成初始化加载,就可以支持页面间的快速导航,而无需刷新页面,甚至可以离线工作。

这对开发人员来说也是非常好的,因为理想的单页应用程序清晰地分离客户端和服务器之间的关注点,促进了一个良好的开发工作流程,并避免需要在两者之间共享太多的逻辑,而这两者通常是用不同的语言编写的。

局限

然而,在实践中,这种方式有一些致命的缺点。

SEO

一个只能在客户端运行的应用程序不能为爬虫程序提供 HTML,因此默认情况下它的 SEO 很差。网络爬虫的功能是向服务器发送请求并解析返回结果;但是如果服务器返回一个空白页,就没有什么价值了。有解决办法,但仍然无法通过一些考验。

性能

同样,如果服务器不渲染完整的 HTML 页面,而是等待客户端 JavaScript 来渲染,那么在看到页面上的内容之前,用户将经历几秒钟的白屏或加载动画。有大量的研究表明,一个速度慢的网站会对用户产生巨大的影响,从而影响收入。亚马逊声称页面的加载时间每减少 100 毫秒,收入就会增加 1%。Twitter 花了一年时间并投入 40 名工程师重建了他们的网站,将其在服务器上渲染而不是在客户端上,声称加载速度提高了 5 倍。

包括我在内的一些开发人员都对这种方式感到痛心——通常只有在投入时间和精力构建一个单页应用程序之后,才能清楚地知道缺点是什么。

互补

归根结底,我们想要结合新老方式以取长补短:我们想要服务器端提供完整的 HTML 以提高性能和 SEO,我们也想要客户端应用程序的用户体验和灵活性。

为此,我们在 Airbnb 进行了“同构 JavaScript”应用程序的实验,这是一种可以同时在客户端和服务器端运行的 JavaScript 应用程序。

同构应用程序可能看起来像这样:

image

这意味着拥有更好的性能、更好的可维护性、友好的 SEO。

有了 Node.js,一个高性能的、稳定的服务器端 JavaScript 宿主环境,让我们现在可以实现这个梦想了。通过建立适当的抽象,我们编写应用程序的逻辑可以同时在服务器和客户端运行——这就是同构 JavaScript 的定义。

抽象,抽象,抽象

这些项目往往是大型的、全栈的 Web 框架,表明了问题的难度。客户端和服务器是非常不同的环境,因此我们必须创建一组抽象,将我们应用程序的逻辑与底层实现分离。

路由

我们需要将 URI 映射到对应的路由处理程序。我们的路由处理程序需要能够访问 HTTP 头、Cookie 和 URI 信息,并能够在不直接访问 window.location(browser)或 req 和 res(Node.js)的情况下完成重定向。

获取和持久化数据

我们想独立于请求机制来描述用于渲染特定页面或组件所需要的资源。资源描述符可以是指向 JSON 的普通 URI,对于大型应用程序,将资源封装到模型和集合中,然后指定一个模型类和主键,在某些情况下将转换为一个 URI(译者注:此处所描述的方法可能类似于 GraphQL 这种 API 查询语言)。

渲染视图

无论我们选择直接操作DOM,使用基于字符串的 HTML 模板,还是具有 DOM 抽象的 UI 组件库,我们都需要能够同构地渲染标记的能力。根据应用程序的需要,我们应该能够在服务器或客户端上渲染任何视图。

构建和打包

事实证明,编写同构应用程序的代码只是成功的一半。像 Grunt 和 Browserify 这样的工具是工作流的重要组成部分,可以让应用程序真正启动并运行。构建步骤有很多:编译模板,包含客户端依赖、进行转换、代码压缩等。简单的情况是将所有应用程序代码、视图和模板合并到一个包中,但对于较大的应用程序,这可能会导致数百 KB 的下载量。更好的方法是进行代码拆分和按需加载。

组合小模块

首个面世的同构框架意味着必须一次性解决以上所有问题。这会导致框架庞大且笨重,难以采用并整合到现有的应用。随着越来越多的开发人员的参与,我们将看到大量可重用的小模块,可以将他们集成在一起来构建同构的应用程序。

事实证明,大多数 JavaScript 模块已经可以同构使用,几乎不需要修改。例如,在服务器上可以使用流行的库,如Underscore、Backbone.js、Handlebars.js、Moment,甚至 jQuery。

为了演示这一点,我创建了一个名为 isomorphic tutorial 的示例应用程序,您可以在 GitHub 上查看它。通过将几个可以同构使用的模块组合在一起,只需几百行代码就可以轻松创建一个简单的同构应用程序。它使用 Director 处理服务端和浏览器的路由,Superagent 处理 HTTP 请求,Handlebars.js 处理模板,所有这些都构建在 Express.js 应用程序基础之上。当然,随着应用程序复杂性的增加,必须引入更多的抽象,但我希望随着更多开发人员的尝试,会出现新的库和标准。