同形的JavaScript:Web應(yīng)用的未來
在Airbnb,這幾年我們已經(jīng)學(xué)習(xí)了很多了關(guān)于構(gòu)建富應(yīng)用的經(jīng)驗(yàn),從2011年通過做我們的網(wǎng)站手機(jī)版,我們開始研究single-page應(yīng) 用,尤其是在我們正式推出Wish Lists和我們重新設(shè)計(jì)的search page以后。大部分都是大型JavaScript項(xiàng)目,這意味著大部分代碼要在瀏覽器中運(yùn)行這也是為去適應(yīng)一個(gè)更現(xiàn)代的交互體驗(yàn)。
在現(xiàn)在,這種方法是很普遍的,現(xiàn)在的一些知名的框架(Backbone.js, Ember.js, Angular.js)很容易讓開發(fā)者構(gòu)建一些富應(yīng)用程序。我們已經(jīng)發(fā)現(xiàn),無論怎么樣,這些應(yīng)用都會(huì)有一些限制,要想知道為什么,我們先來看一下Web apps 的歷史。
JavaScript Group Up
自Web起初, 瀏覽器是這樣工作的:瀏覽器將會(huì)請(qǐng)求一個(gè)特定的頁面(例如:請(qǐng)求http://www.geocities.com)使服務(wù)器產(chǎn)生響應(yīng)并生成HTML頁面 然后通過Internet發(fā)送回來, 這種工作方式在當(dāng)時(shí)已經(jīng)是很好的了是因?yàn)槟菚r(shí)的瀏覽器不是很強(qiáng)大,HTML頁面大多數(shù)都是獨(dú)立且靜態(tài)的文件。JavaScript 的來臨可以使Web頁面變得動(dòng)態(tài),不僅僅只是實(shí)現(xiàn)圖片幻燈片和日期控件。在個(gè)人電腦高速發(fā)展的今天, 一些牛B的程序員已經(jīng)擺脫了Web的限制,瀏覽器也在不斷的進(jìn)化?,F(xiàn)在,Web 已經(jīng)是一個(gè)成熟并具有強(qiáng)大功能的應(yīng)用平臺(tái),包括快速的JavaScript運(yùn)行,和支持HTML5標(biāo)準(zhǔn)可供開發(fā)人員創(chuàng)建一個(gè)功能強(qiáng)大富應(yīng)用程序. 在這以前它僅僅只是普通的本地平臺(tái)。
The Single-Page App
很快,利用這些新功能開發(fā)人員開始使用JavaScript構(gòu)建整個(gè)網(wǎng)站,這些經(jīng)典的單頁面應(yīng)用像 Gmail 能快速的響應(yīng)用戶行為, 而不是只為了渲染頁面就往返服務(wù)器。
一些成熟框架例如: Backbone.js, Ember.js, Angular.js 通常也會(huì)被當(dāng)做“MVC or MVVM模式”的框架而討論, 這個(gè)經(jīng)典的MVC模式看起來像這樣:
大部分的邏輯都在客戶端,(視圖,模版,控制,數(shù)據(jù)處理,國際化等)為數(shù)據(jù)提供處理接口, 服務(wù)器端可以使用任意一種語言編寫, 如 Ruby、Python、Java,它最多的也就是提供一個(gè)空的HTML頁面, 一旦JavaScript文件被下載,它們將被執(zhí)行以及在客戶端初始化, 從中獲取數(shù)據(jù)并直接渲染HTML頁面。
這對(duì)于用戶體驗(yàn)來說是很好的, 因?yàn)橐坏?yīng)用初始化并加載,它是可以支持頁面與頁面之間快速切換而不是通過刷新頁面, 在往好了說,它甚至可以實(shí)現(xiàn)離線操作功能。
這對(duì)開發(fā)人員也是很好, 因?yàn)樗梢悦鞔_的為 “client”/”service”分出界線, 從而促進(jìn)整個(gè)開發(fā)流程, 可以有效的防止兩種語言這間的實(shí)現(xiàn)邏輯重復(fù), 因?yàn)榍昂蠖送ǔJ鞘褂貌煌Z言開發(fā)的。
Trouble in Paradise
實(shí)際上, 無論如何它都是有缺陷的, 不過我們可以通過一些案例來正確的避免。
SEO
如果一個(gè)應(yīng)用只運(yùn)行在客戶端的話是不能通過HTML進(jìn)行”爬蟲”的,所以它默認(rèn)是不可被SEO的(搜索引擎優(yōu)化), 正常我們的爬蟲是通過向服務(wù)器創(chuàng)建個(gè)請(qǐng)求然后解析結(jié)果;但如果服務(wù)器返回個(gè)空頁面, 那就沒有意義了. 但也不是沒有解決的辦法。
Performance
同理, 如果服務(wù)器不能直接渲染整個(gè)HTML頁面,而是通過JavaScript去做這些事兒,用戶將會(huì)在加載完整個(gè)頁面之前看到幾秒鐘的空頁面或者一直加載控 件. 有很多研究表明用戶對(duì)訪問慢站點(diǎn)反應(yīng)很強(qiáng)烈。Amazon claims亞馬遜聲稱 “每提升100ms的頁面加載速度將會(huì)提升1%的收入” Twtter 40個(gè)工程師花費(fèi)的1年時(shí)間去重構(gòu),他們的站點(diǎn)(在服務(wù)器端渲染頁面,而不是在客戶端) 聲稱提高了5倍的加載時(shí)間。
Maintainability
在理想情況下我們在要?jiǎng)?chuàng)建一個(gè)分層明確低耦合的應(yīng)用程序, 來避免少量的應(yīng)用邏輯代碼在前后端重復(fù)(通常是前后端使用不同的語言開發(fā)). 常見的例子比如 日期/貨幣格式化,表單驗(yàn)證, 流程邏輯. 可維護(hù)性一直都是設(shè)計(jì)程序必要的也是困難的,特別是對(duì)于復(fù)雜應(yīng)用來說。
一些開發(fā)人員包括我們自己也被這些問題困擾著 — 通常只有真正在單頁面應(yīng)用下功夫, 才能清楚它的缺陷在哪里。
A hybrid Approach
到了***,我們想要一個(gè)綜合的解決方案: 我們想從服務(wù)器獲取整個(gè)HTML(高必性能, 可SEO) 但我們還想使客戶端代碼運(yùn)行快速且具有靈活性。
為此, 我們已經(jīng)在Airbnb嘗試使用”Isomorphic JavaScript”進(jìn)行構(gòu)建. 這是一個(gè)可以在客戶端和服務(wù)端都運(yùn)行的JavaScript應(yīng)用. 一個(gè)“isomorphic”應(yīng)用看起來是這樣的, 這里稱為“Client-server MVC”。
在這個(gè)世界里, 你的應(yīng)用和視圖層邏輯都可以在前后端運(yùn)行, 這樣就依次解決上述所有問題 — 性能優(yōu)化, 好的維護(hù)性, 可以被SEO,更有狀態(tài)的Web應(yīng)用。
通過Node.js,一個(gè)快速的, 穩(wěn)定的運(yùn)行在服務(wù)器端的JavaScript, 現(xiàn)在我們可以夢想成真. 通過創(chuàng)建適當(dāng)?shù)某橄螅?我們就可以在服務(wù)器端和客戶端運(yùn)行我們的邏輯代碼 — 這就是“isomorphic JavaScript“的定義。
Isomorphic JavaScript in the Wild
這不是一個(gè)新的想法, 早在2011年 Nodejitsu 已經(jīng)對(duì) isomorphic JavaScript 有了一個(gè)很好的描述- 但現(xiàn)在它才被采用. 現(xiàn)如今已經(jīng)有很多的 isomorphic JavaScript的框架了。
Mojito 是***個(gè)開源的 isomorphic JavaScript框架, 你可以通過任意途徑得到它. 它完全用Node.js寫的框架. 但自從他們在2012年4月開源以來在JavaScript社區(qū)沒有廣泛的流行起來主要原因是它依賴于 YUI 和 雅虎特殊的模式。
Meteor 可能是現(xiàn)今***的 isomorphic 項(xiàng)目. Meteor 原生的支持實(shí)時(shí)應(yīng)用, 這個(gè)團(tuán)隊(duì)正在圍繞這包管理器和開發(fā)工具來構(gòu)建一個(gè)完整的體系。
像Mojito,它是一個(gè)大型的, 原生的Node.js框架, 它在JavaScript社區(qū)里也是工作很好的.,而且1.0 release版本也將要發(fā)布了。 Meteor做為一個(gè)項(xiàng)目一直被密切關(guān)注著 — 它有一個(gè)全明星團(tuán)隊(duì), 并在安德森基金會(huì)獲得 $11.2M 資金 – 從未聽說過有公司會(huì)對(duì)一個(gè)開源產(chǎn)品這么關(guān)注。
Asana, 是一個(gè)任務(wù)管理應(yīng)用 有意思的是它是由 Fackbook 創(chuàng)始人之一的 Dustin Moskovitz創(chuàng)建的. Moskovitz’ 地位乃是世界最年輕的億萬富翁, Asana花費(fèi)了很多年在開發(fā)他們的閉源項(xiàng)目Luna, 這是isomorphic JavaScript***的例子之一。Luna, 在沒有Node.js以前它是構(gòu)建在v8cgi上, 它允許為每一個(gè)單獨(dú)用戶會(huì)話copy一個(gè)完整的應(yīng)用程序到服務(wù)器端運(yùn)行. 它為每個(gè)用戶創(chuàng)建獨(dú)立的進(jìn)程, 運(yùn)行在客戶端上的也是服務(wù)器端的代碼, 開啟對(duì)整個(gè)類的高級(jí)優(yōu)化, 比如 離線支持 即時(shí)更新。
在早些時(shí)候我們推出了一個(gè) 同構(gòu)庫它叫被叫做Rendr庫, 它允許你使用 Backbone.js + Handlebars.js 構(gòu)建單頁面應(yīng)用, 在服務(wù)器端也能全部被渲染。Rendr是我們在為了使 Airbnb mobile web 有更快的響應(yīng)速度而創(chuàng)建的產(chǎn)品。對(duì)于用戶來說高可用的響應(yīng)速度是尤為重要的. Rendr力求成為一個(gè)庫而不是一個(gè)框架, 所以它相比Mojito或Metetor來說, 它解決的問題相對(duì)來說是少的,但它很容易修改和擴(kuò)展。
Abstraction, Abstraction, Abstraction
這往往對(duì)一些大的項(xiàng)目來說是很困難的, 客戶端與服務(wù)器端是完全不同的運(yùn)行環(huán)境,所以我們要?jiǎng)?chuàng)建一系列抽象把解藕的應(yīng)用邏輯從底層抽出來, 所以我們可以像開發(fā)人員暴露一些單獨(dú)的API。
Routing
我們想從URI模式路由處理器中獲取單獨(dú)的一組路由,我們的路由處理需要訪問HTTP頭, cookies, URI信息, 和特殊的重定向(不是通過window.location或者Node.js 的req res)。
Fetching and persisting data
我們想要描述一個(gè)資源就需要渲染一個(gè)指定頁面,或者通過抓取組件的形式。 這個(gè)資源描述符可以是一個(gè)簡單的URI去指向一個(gè)JSON數(shù)據(jù), 或指向更大的應(yīng)用程序, 通過模型、集合、指定的模型類或者是一個(gè)主鍵KEY對(duì)封裝資源是很有用的 , 通常這些在某種程度上都被解析成一個(gè)URI。
View rendering
我們是否選擇直接操作DOM, 還是使用HTML模板,或者操作一個(gè)封裝DOM的抽像UI組件,來生成一個(gè)HTML標(biāo)記,我們也能在前后端夠渲染任何頁面, 這要看你的應(yīng)用是否需要了。
Building and packaging
到現(xiàn)在為止也只是走了一半的路程, 工具像 Grunt 和 Browserify 是在啟動(dòng)和運(yùn)行應(yīng)用程序工作流程中不可缺少的.。下面是構(gòu)建的幾個(gè)步驟: 編譯模板 ,包括一些客戶端依賴、 應(yīng)用混淆、 壓縮等。這個(gè)簡單的例子是合并所有應(yīng)用代碼、視圖和模板捆綁在一起, 但對(duì)于大型應(yīng)用來說會(huì)有幾百KB的下載。一個(gè)***的辦法是去創(chuàng)建一個(gè)動(dòng)態(tài)捆綁和采用延遲加載, 無論怎么樣它都是很復(fù)雜的。 靜態(tài)統(tǒng)計(jì)工具像Esprima可以使一些有上進(jìn)心的程序人員去嘗試進(jìn)行再一步的優(yōu)化以及使用 metaprogramming (元程序)來減少代碼.
Composing Together Small Modules
Isomorphic 框架要想走入市場意味著你要立刻解決所有的問題,但這樣會(huì)導(dǎo)致一個(gè)大的笨重的框架, 會(huì)很難被推廣和很難融入現(xiàn)有應(yīng)用程序。現(xiàn)在有很多的開發(fā)人員已經(jīng)解決這個(gè)問題,我們將會(huì)構(gòu)建一個(gè)輕量級(jí)的-可復(fù)用的-可繼承的 isomorphic 程序。
事實(shí)證明大多數(shù)的JavaScript模塊不用怎么修改就可以被同構(gòu), 例如:現(xiàn)流行的庫像 Underscore, Backbone.js, Handlebars.js, 重要的是 現(xiàn)在甚至jQuery也可以在服務(wù)器端使用。
為了證明這一點(diǎn),我已經(jīng)構(gòu)建一個(gè)簡單的應(yīng)用 “isomorphic-tutorial”你可以去Github上去下載。通過將幾個(gè)模塊結(jié)合在一起, 其中每個(gè)模塊都是可同構(gòu)的,它 僅僅使用幾百行代碼就很容易創(chuàng)建一個(gè)簡單的同構(gòu)應(yīng)用,它是使用Diretor為服務(wù)器端與瀏覽器提供路由,Superagent提供HTTP請(qǐng)求和使用 Handlebars.js做頁面模板,這些所有有構(gòu)建都是基于Express.js框架,當(dāng)然作為一個(gè)應(yīng)用組建起來是很復(fù)雜的,必須得引入更多層次的抽 象,但是我們希望會(huì)有更多的開發(fā)人員進(jìn)行更多的嘗試,它們將會(huì)有一個(gè)新的庫和新的標(biāo)準(zhǔn)誕生。
The View From Here
更多的機(jī)構(gòu)已經(jīng)在他們的產(chǎn)品中使用Node.js了,這也就不可避免會(huì)有更多的應(yīng)用開始在前后端共享代碼。 最重要的是要記住 “同構(gòu)JavaScript”是一個(gè)范圍 — 它開始只能共享模板,之后管理整個(gè)項(xiàng)目的視圖層,再到大多數(shù)應(yīng)用的業(yè)務(wù)邏輯層。事實(shí)上JavaScript代碼共享在前后端是要取決于你的程序設(shè)計(jì),以及 它的獨(dú)特約束。
Nicholas C. Zakas 有一個(gè)很好的文章“如何將UI層從客戶端拿到服務(wù)器端” 提高性能與可維護(hù)性。一個(gè)應(yīng)用不需要用Node.js去代替整個(gè)后端,這好比 “在倒洗澡水時(shí)把小孩也給倒掉了”,反而要想去創(chuàng)建一個(gè)好的API和一個(gè)RESTful resources(這里推薦阮一峰的一篇文章來簡單介紹什么是 RESTful resource) 的程序,傳統(tǒng)后臺(tái)是可以和Node.js結(jié)合去搭建的。
At Airbnb網(wǎng)站上,我們已經(jīng)開始使用Node.js一些基礎(chǔ)工具庫 Grunt 和 Browerify 重構(gòu)我們的客戶端。我們核心的Rails應(yīng)用可能永遠(yuǎn)都不會(huì)使用Node.js做替換,但能過這些工具我們很容易使JavaScript與 template共享同一環(huán)境。
如果你要這里是***次聽說,在過幾年 一些高級(jí)的WEB應(yīng)用將運(yùn)行JavaScript在服務(wù)器端。
Learn More
如果這個(gè)idea使你很興奮,那么你可以來我們的 Isomorphic JavaScript 工作室來看看,在舊金山 11月12日 星期三 或者 11月21日 星期四,我將在DevBeat上教你們 isomorphic JavaScript 如何組裝以及會(huì)告訴你們寫一個(gè)同構(gòu)程序是多少容易的一件事 兒。
原文鏈接:http://nerds.airbnb.com/isomorphic-javascript-future-web-apps/