在Airbnb,这几年我们已经学习了很多了关于构建富应用的经验,从2011年通过做我们的网站手机版,我们开始研究single-page应用,尤其是在我们正式推出Wish Lists和我们重新设计的search page以后。大部分都是大型JavaScript项目,这意味着大部分代码要在浏览器中运行这也是为去适应一个更现代的交互体验。
在现在,这种方法是很普遍的,现在的一些知名的框架(Backbone.js, Ember.js, Angular.js)很容易让开发者构建一些富应用程序。我们已经发现,无论怎么样,这些应用都会有一些限制,要想知道为什么,我们先来看一下Web apps 的历史。
JavaScript Group Up
自Web起初, 浏览器是这样工作的:浏览器将会请求一个特定的页面(例如:请求http://www.geocities.com)使服务器产生响应并生成HTML页面然后通过Internet发送回来, 这种工作方式在当时已经是很好的了是因为那时的浏览器不是很强大,HTML页面大多数都是独立且静态的文件。JavaScript 的来临可以使Web页面变得动态,不仅仅只是实现图片幻灯片和日期控件。在个人电脑高速发展的今天, 一些牛B的程序员已经摆脱了Web的限制,浏览器也在不断的进化。现在,Web 已经是一个成熟并具有强大功能的应用平台,JS的执行速度变快以及HTML5的标准使得开发者能够创建富应用,而这些应用之前只可能是在本地平台构建!
The Single-Page App
很快,利用这些新功能开发人员开始使用JavaScript构建整个网站,这些经典的单页面应用像 Gmail 能快速的响应用户行为, 而不是只为了渲染页面就往返服务器。
一些成熟框架例如: Backbone.js, Ember.js, Angular.js 通常也会被当做“MVC or MVVM模式”的框架而讨论, 这个经典的MVC模式看起来像这样:
大部分的逻辑都在客户端,(视图,模版,控制,数据处理,国际化等)为数据提供处理接口, 服务器端可以使用任意一种语言编写, 如 Ruby、Python、Java,它最多的也就是提供一个空的HTML页面, 一旦JavaScript文件被下载,它们将被执行以及在客户端初始化, 从中获取数据并直接渲染HTML页面。
这对于用户体验来说是很好的, 因为一旦应用初始化并加载,它是可以支持页面与页面之间快速切换而不是通过刷新页面, 在往好了说,它甚至可以实现离线操作功能。
这对开发人员也是很好, 因为它可以明确的为 “client”/”service”分出界线, 从而促进整个开发流程, 可以有效的防止两种语言这间的实现逻辑重复, 因为前后端通常是使用不同语言开发的。
Trouble in Paradise
实际上, 无论如何它都是有缺陷的, 不过我们可以通过一些案例来正确的避免。
SEO
如果一个应用只运行在客户端的话是不能通过HTML进行”爬虫”的,所以它默认是不可被SEO的(搜索引擎优化), 正常我们的爬虫是通过向服务器创建个请求然后解析结果;但如果服务器返回个空页面, 那就没有意义了. 但也不是没有解决的办法。
Performance
同理, 如果服务器不能直接渲染整个HTML页面,而是通过JavaScript去做这些事儿,用户将会在加载完整个页面之前看到几秒钟的空页面或者一直加载控件. 有很多研究表明用户对访问慢站点反应很强烈。Amazon claims亚马逊声称 “每提升100ms的页面加载速度将会提升1%的收入” Twtter 40个工程师花费的1年时间去重构,他们的站点(在服务器端渲染页面,而不是在客户端) 声称提高了5倍的加载时间。
Maintainability
在理想情况下我们在要创建一个分层明确低耦合的应用程序, 来避免少量的应用逻辑代码在前后端重复(通常是前后端使用不同的语言开发). 常见的例子比如 日期/货币格式化,表单验证, 流程逻辑. 可维护性一直都是设计程序必要的也是困难的,特别是对于复杂应用来说。
一些开发人员包括我们自己也被这些问题困扰着 — 通常只有真正在单页面应用下功夫, 才能清楚它的缺陷在哪里。
A hybrid Approach
到了最后,我们想要一个综合的解决方案: 我们想从服务器获取整个HTML(高必性能, 可SEO) 但我们还想使客户端代码运行快速且具有灵活性。
为此, 我们已经在Airbnb尝试使用”Isomorphic JavaScript”进行构建. 这是一个可以在客户端和服务端都运行的JavaScript应用. 一个“isomorphic”应用看起来是这样的, 这里称为“Client-server MVC”。
在这个世界里, 你的应用和视图层逻辑都可以在前后端运行, 这样就依次解决上述所有问题 — 性能优化, 好的维护性, 可以被SEO,更有状态的Web应用。
通过Node.js,一个快速的, 稳定的运行在服务器端的JavaScript, 现在我们可以梦想成真. 通过创建适当的抽象, 我们就可以在服务器端和客户端运行我们的逻辑代码 — 这就是“isomorphic JavaScript“的定义。
Isomorphic JavaScript in the Wild
这不是一个新的想法, 早在2011年 Nodejitsu 已经对 isomorphic JavaScript 有了一个很好的描述- 但现在它才被采用. 现如今已经有很多的 isomorphic JavaScript的框架了。
Mojito 是第一个开源的 isomorphic JavaScript框架, 你可以通过任意途径得到它. 它完全用Node.js写的框架. 但自从他们在2012年4月开源以来在JavaScript社区没有广泛的流行起来主要原因是它依赖于 YUI 和 雅虎特殊的模式。
Meteor 可能是现今最好的 isomorphic 项目. Meteor 原生的支持实时应用, 这个团队正在围绕这包管理器和开发工具来构建一个完整的体系。
像Mojito,它是一个大型的, 原生的Node.js框架, 它在JavaScript社区里也是工作很好的.,而且1.0 release版本也将要发布了。 Meteor做为一个项目一直被密切关注着 — 它有一个全明星团队, 并在安德森基金会获得 $11.2M 资金 – 从未听说过有公司会对一个开源产品这么关注。
Asana, 是一个任务管理应用 有意思的是它是由 Fackbook 创始人之一的 Dustin Moskovitz创建的. Moskovitz’ 地位乃是世界最年轻的亿万富翁, Asana花费了很多年在开发他们的闭源项目Luna, 这是isomorphic JavaScript最著名的例子之一。Luna, 在没有Node.js以前它是构建在v8cgi上, 它允许为每一个单独用户会话copy一个完整的应用程序到服务器端运行. 它为每个用户创建独立的进程, 运行在客户端上的也是服务器端的代码, 开启对整个类的高级优化, 比如 离线支持 即时更新。
在早些时候我们推出了一个 同构库它叫被叫做Rendr库, 它允许你使用 Backbone.js + Handlebars.js 构建单页面应用, 在服务器端也能全部被渲染。Rendr是我们在为了使 Airbnb mobile web 有更快的响应速度而创建的产品。对于用户来说高可用的响应速度是尤为重要的. Rendr力求成为一个库而不是一个框架, 所以它相比Mojito或Metetor来说, 它解决的问题相对来说是少的,但它很容易修改和扩展。
Abstraction, Abstraction, Abstraction
这往往对一些大的项目来说是很困难的, 客户端与服务器端是完全不同的运行环境,所以我们要创建一系列抽象把解藕的应用逻辑从底层抽出来, 所以我们可以像开发人员暴露一些单独的API。
Routing
我们想从URI模式路由处理器中获取单独的一组路由,我们的路由处理需要访问HTTP头, cookies, URI信息, 和特殊的重定向(不是通过window.location或者Node.js 的req res)。
Fetching and persisting data
我们想要描述一个资源就需要渲染一个指定页面,或者通过抓取组件的形式。 这个资源描述符可以是一个简单的URI去指向一个JSON数据, 或指向更大的应用程序, 通过模型、集合、指定的模型类或者是一个主键KEY对封装资源是很有用的 , 通常这些在某种程度上都被解析成一个URI。
View rendering
我们是否选择直接操作DOM, 还是使用HTML模板,或者操作一个封装DOM的抽像UI组件,来生成一个HTML标记,我们也能在前后端够渲染任何页面, 这要看你的应用是否需要了。
Building and packaging
到现在为止也只是走了一半的路程, 工具像 Grunt 和 Browserify 是在启动和运行应用程序工作流程中不可缺少的.。下面是构建的几个步骤: 编译模板 ,包括一些客户端依赖、 应用混淆、 压缩等。这个简单的例子是合并所有应用代码、视图和模板捆绑在一起, 但对于大型应用来说会有几百KB的下载。一个最好的办法是去创建一个动态捆绑和采用延迟加载, 无论怎么样它都是很复杂的。 静态统计工具像Esprima可以使一些有上进心的程序人员去尝试进行再一步的优化以及使用 metaprogramming (元程序)来减少代码.
Composing Together Small Modules
Isomorphic 框架要想走入市场意味着你要立刻解决所有的问题,但这样会导致一个大的笨重的框架, 会很难被推广和很难融入现有应用程序。现在有很多的开发人员已经解决这个问题,我们将会构建一个轻量级的-可复用的-可继承的 isomorphic 程序。
事实证明大多数的JavaScript模块不用怎么修改就可以被同构, 例如:现流行的库像 Underscore, Backbone.js, Handlebars.js, 重要的是 现在甚至jQuery也可以在服务器端使用。
为了证明这一点,我已经构建一个简单的应用 “isomorphic-tutorial”你可以去Github上去下载。通过将几个模块结合在一起, 其中每个模块都是可同构的,它 仅仅使用几百行代码就很容易创建一个简单的同构应用,它是使用Diretor为服务器端与浏览器提供路由,Superagent提供HTTP请求和使用Handlebars.js做页面模板,这些所有有构建都是基于Express.js框架,当然作为一个应用组建起来是很复杂的,必须得引入更多层次的抽象,但是我们希望会有更多的开发人员进行更多的尝试,它们将会有一个新的库和新的标准诞生。
The View From Here
更多的机构已经在他们的产品中使用Node.js了,这也就不可避免会有更多的应用开始在前后端共享代码。 最重要的是要记住 “同构JavaScript”是一个范围 — 它开始只能共享模板,之后管理整个项目的视图层,再到大多数应用的业务逻辑层。事实上JavaScript代码共享在前后端是要取决于你的程序设计,以及它的独特约束。
Nicholas C. Zakas 有一个很好的文章“如何将UI层从客户端拿到服务器端” 提高性能与可维护性。一个应用不需要用Node.js去代替整个后端,这好比 “在倒洗澡水时把小孩也给倒掉了”,反而要想去创建一个好的API和一个RESTful resources(这里推荐阮一峰的一篇文章来简单介绍什么是 RESTful resource) 的程序,传统后台是可以和Node.js结合去搭建的。
At Airbnb网站上,我们已经开始使用Node.js一些基础工具库 Grunt 和 Browerify 重构我们的客户端。我们核心的Rails应用可能永远都不会使用Node.js做替换,但能过这些工具我们很容易使JavaScript与template共享同一环境。
如果你要这里是第一次听说,在过几年 一些高级的WEB应用将运行JavaScript在服务器端。
Learn More
如果这个idea使你很兴奋,那么你可以来我们的 Isomorphic JavaScript 工作室来看看,在旧金山 11月12日 星期三 或者 11月21日 星期四,我将在DevBeat上教你们 isomorphic JavaScript 如何组装以及会告诉你们写一个同构程序是多少容易的一件事 儿。
也请继续跟着我@spikebrehm和我们的技术团队@AirbnbNerds.一起关注 Airbnb web 应用的演变。