(認真把這篇文章看完,保證你會學到不少,若是沒學到請聯繫做者或直接報警)javascript
隨着前端技術的不斷變革,前端從後臺吐頁面 -> 前端MVC -> mvvm、react -> node直出 ->同構跨端的大體發展方向(後面我也講下前端的跨終端實現),前端正在向着模塊化、高效性、跨端性擴展。過渡完mvvm、react和node直出的階段,前端工程師們又開始在通日後臺同構的道路上探索。並且也出現了一些列可能的方案,例如使用mvc、mvvm或react在服務端作實現,不管如何。可是咱們不得面臨一系列問題。同構的目的?同構的核心?同構的代價?同構的優點?css
咱們先來看下這三個問題。html
首先說下目前比較主流的web先後端分離方案:這種方案將web站點分爲前端和後臺,前端經過拉取後臺數據的到頁面再經過js模板渲染到頁面上。幾個主要問題,一是頁面顯示須要等後臺請求(cgi等)返回來才能渲染,二是seo怎麼辦,同時也承受開發聯調效率的考研。前端
因此node直出的方案在阿里、騰訊等大的前端團隊裏陸續被使用起來,思路是經過node直出首屏內容和關鍵性seo信息解決了上述問題。vue
可是問題是咱們沒法所有直出頁面全部的內容,一般由於太大,因此前端仍然須要維護原有的前端代碼。更多狀況下,同一個站點中,咱們更但願的是在某些場景下使用前端渲染,另外一些狀況下使用後端直出(例如,但願hybridapp在沒有離線包的第一次直出,後面不須要下載靜態文件時使用前端拉取渲染的方式,或者在高級的瀏覽器下使用http2前端渲染,低端瀏覽器上則使用直出),結果咱們不得不維護兩套不一樣的先後臺代碼,儘管可能都是用js寫的。因此同構但願解決的是維護先後端維護兩套代碼的問題。java
同構但願作的事情是隻開發一套項目代碼,既能夠實現前端的渲染也能夠作後臺的直出。爲何能夠這樣作呢?由於後臺直出頁面在後端生成,實現的方式也是經過數據加上模板編譯的方式生成,前端渲染和後臺直出的模式生成dom的區別只在於數據和模板的渲染髮生在何時。node
同構的目的是爲了統一先後臺的方案,天然也會牽扯到先後端的適應性修改。例如前端的數據渲染如何與後臺直出保持一致、後臺如何處理異步的問題、在原有業務上的實現代價、和原有前端框架的的衝突性等等。若是考慮到這些問題,同構改造實現的代價就會很大,畢竟它是綜合了兩個開發人員的工做量。mysql
同構的網站應該具備一些優點:一、能夠根據用戶須要方便的選擇前端渲染仍是後臺支持;二、開發者真的只須要維護一套代碼,固然這個是不嚴謹的,後臺多出的工做是配置路由和數據接口編寫,但和前端dom相關的指維護一套;三、前端的模塊化開發和後臺全部模塊是共享的;四、能夠避免先後端工程師的聯調溝通成本,但總體成本會比單我的開發大;四、開發構建調試系統完善。react
這個被討論的比較火。但事實上,目前直接使用react都比較困難,而react的應用如今就少,並且也沒見過大型應用使用,因此同構的價值不大,其實對於此實現方案在開發時的配置依然比較多。不過技術上react同構是一個可行度很高的方案。jquery
想多瞭解的話能夠看下《isomorphic-reactjs》,其核心思路是使用renderToString將virtual dom直接轉化成爲html,由於virtual dom在先後端均可用。這樣就實現了直出的轉換。然而我想說的是,reactjs項目裏面html和js混淆,模板語言生硬,渲染和事件綁定在一塊兒,行爲和結構層不分離只使用js來管理,原本對通常人來講技術學習成本高,項目大了很難管理,有可能帶給咱們至少兩倍以上的成本。
相比來講這個可行性稍大些。例如你只須要在服務器端實現一個mvvm的核心,經過本身實現dom分析器其來解析後端模板中的directives、filter、和事件bind就能夠了,可是你要去作這個mvvm核心,並且若是你拋開主流的mvvm框架去作,並且要和前端使用的mvvm框架解析同樣,除非若是保證你寫的框架足夠優秀能,或者被別人接受。固然一個可行的方案是徹底根據現有某個主流mvvm框架(例如vuejs、avalon等)的語法來在後端實現一個功能相同的解析插件,由於現有主流的mvvm是不能在node上解析模板的。
這樣的話開發的代碼就必須是使用mvvm的模板來寫了。固然根據mvc框架來實現的原理相似,不過能夠靈活些。以前咱們作過相似的事情,實現了vuejs的一些基礎的directive和filter解析,可是咱們業務前端都沒有不少人使用vuejs,作同構很像後端上的一廂情願。最後放棄了。
不管是react仍是mvvm來實現,其實咱們要弄清楚同構須要作的最核心的一件事情是保證一個數據渲染機制(react是virtual to html、mvvm是view模板)在先後端都能正確解析。因此保證這件事情實現了問題是否是就簡單了。使用react或mvvm只是說咱們能夠更好的作前端模塊化管理。
大多狀況下,咱們更多推薦使用mvvm管理vm模板,由於這樣就能夠模板重用了,可是咱們也不得不考慮實現後端mvvm解析模板的代價。但其實在tpl模板層面只是須要一個通用模板,一個能同時支持後臺和前端模塊化開發的模板。mustache、handlebar、jade、ejs、artTemplate、kissy?彷佛有不少選擇,可是他們要麼都只能在一端 工做,要麼功能較簡單,都不能直接解決問題。可是這個問題必須獲得解決。
經過分析,因此整理下咱們須要解決的幾個問題:
基於如今的的node端發展趨勢,koa相比經典express(其它的一些不主流框架這裏就不比較了,你們也能夠去了解)有了不少優點,koa自實現的generator特性能用來寫沒有callback的異步處理,而使用express須要配合bluebrid使用promise特性;koa 不在內核方法中綁定任何中間件,它除了提供了一個輕量優雅的函數庫,還包含錯誤處理機制,並相對於express某些功能的語法糖使代碼更簡潔,另外generators 實現了的級聯中間件,控制權在中間件之間傳遞很清晰,然後面這些express都不能直接作到。
固然後端除了這些,還須要關注數據庫層的問題,通常使用mongodb、mysql或redis都OK,具體根據業務場景決定,並且同時也能夠搭配pm2來進行進程管理。
爲了兼容舊的框架,咱們仍是須要兼容到jquery/zepto,固然也但願可以兼容到mvvm的框架。這方面處理的方案能夠比較靈活,而是用後端mvvm直出的方案可能就兼容不了jquery的程序了。因此使用通用模板後你要用react都不會有任何問題。
swigjs是node端的一個優秀簡潔的模板引擎,可以根據路徑渲染頁面、支持面向對象的模板繼承、頁面複用、支持動態頁面、而且使用簡潔能快速上手,目前不只在node端較爲通用,相對於jade、ejs優秀,並且在瀏覽器端也能夠很好的運行。但問題是,即便直出後的狀況下瀏覽器上仍然有異步渲染的狀況,是否是直出後也須要引入這個swigjs模板庫呢?何況就算是瀏覽器渲染的方式,須要加載這個模板庫,並且模板是動態編譯的,必定會慢。 對於這些問題,咱們但願是前端渲染狀況下在構建階段就可以完成模板編譯,固然swig在後端的解決方案是沒有任何問題的。
爲此咱們仍是必須一個和swig相似的前端靜態編譯插件。幸運的是做者已經爲你作了這件事情:
npm install fis3-parser-swig
,使用時配置以下(暫不支持瀏覽器端模板繼承):
.match(/.+\/(.+)\/.+\.tpl$/, { // js 模版一概用 .tpl,可使用[模塊名.tpl]做爲模板 isMod: true, rExt: 'js', id: '$1.tpl', moduleId: '$1.tpl', release: '$1.tpl', // 發佈的後的文件名,避免和同目錄下的 js 衝突 parser: fis.plugin('swig') })
它的優點是配置後能夠在構建階段就將前端的模板文件編譯成js文件,就不用在頁面打開是加載swig模板引擎作渲染了。這樣具體的項目代碼請參見最後面的github地址。
構建上仍是基於fis3上開發,固然也能夠去自由選擇gulp或其它的,我的習慣fis3的一些優點,這裏就不展開討論了,若是想使用es6也可使用下做者以前作的插件fis3-parser-babel
,具體這裏就不展開介紹了。這裏先看下整個項目開發目錄的設計:
|--asyncWigdet #前端異步模塊的存放目錄,這裏渲染和直出加載的是相同的 |--testMod #前端異步加載的模塊 |--mock |--indexPage.json #mock的開發調試數據目錄 |--modules #前端擴展庫,例如angular或jquery的插件等 |--libs #前端基礎庫,例如angular、jquery等 |--pages #頁面入口模塊 |--index |--index.html #模塊html |--index.tpl #模塊模板 |--main.js #模塊對應的js |--index.scss #模塊對應的css |--server #服務器端內容 |--lib #服務器端基礎庫,例如db鏈接服務基礎模塊等 |--mock #mock的開發調試數據目錄 |--models #數據庫model文件 |--pages #前端編譯後的view模板 |--routes #koa route |--views #後臺若不使用模板,也能夠直接使用這裏無編譯的view |--index.js #koa入口任務腳本 |--pm2.json #pm2管理配置 |--widget #頁面內部業務模塊 |--search-bar |--index.html #模塊html |--index.tpl #模塊模板 |--main.js #模塊對應的js |--index.scss #模塊對應的css |--- ... |--fis-config.js #fis打包配置文件 |--dev #前端渲染頁面輸出目錄,固然這裏和能夠和編譯出的node端模板使用同一個目錄 |--index.html #入口頁面
目錄具體能夠參考github地址:koa-fis3-isomorphic
根據這個目錄總結一下:
這裏編譯出前端頁面和後端的pages模板(和views相同功能,但避免和原生views命名衝突)是經過fis3配置的不一樣任務來實現的,例如調試時可使用fis3 release dev
和fis3 release server
來完成不一樣構建,暫時使用的一個目錄
這裏只須要維護一套代碼目錄規範,先後端打包進行兩次(固然你能夠合併下兩個命令變爲一條)
前端自動打包都是fis3完成的,這裏咱們不須要去關心
前端的基礎框架能夠任意選擇,這裏只是用zepto作範例
受平時項目經驗的啓發,在koa + swig + fis3 + fis3-parser-swig的條件下,咱們配置url地址中的一個特定r參數來判斷使用前端渲染仍是後臺支持,例如:http://localhost:3000/index.html?r=1
使用前端渲染,不帶r=1
則使用後臺支持。服務端判斷帶有r=1
則轉到前端的html服務器上,前端判斷r=1
則調用數據render方法;不然後臺直接渲染模板,前端不作數據render,只作事件綁定。這樣簡單可靠的解決了這個問題。
var getUrlParam = function(name) { var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)"); //構造一個含有目標參數的正則表達式對象 var r = window.location.search.substr(1).match(reg); //匹配目標參數 if (r != null) { return unescape(r[2]); } return null; //返回參數值 }; // ... //前端摸個模塊的處理 init: function(data) { // url中帶有r,才作前端模板渲染 if (window.r) { this._renderData(data); } this._bindEvent(); }, _renderData: function(data) { this.$el.html(panelTpl({panel3: data})) },
總結一下完整的方案和思路:
在fis3構建和koa後端框架的前提下,使用swig來實現後端模板渲染,使用fis3-parser-swig來作前端頁面的模板編譯,最終可以在先後端支持統一個模板的解析。
同構的優點前面講到了,這裏說下幾個須要注意的問題
這裏的實現只表明我的思路,實現的具體組合方案有不少種,可是思想是一致的,例如使用gulp,或後臺解析mvvm模板或mvc模板等。
模板fis3-parser-swig
不支持繼承,由於是前端組件化環境編譯的,不須要使用模板繼承;node端能夠任意使用
開發調試時文件watcch變化不生效問題。若是開發時fis3 watch文件變化和nodemon wactch文件變化衝突,可能致使前端代碼不能自動生效。緣由是fis3要進行的文件改變的目錄被nodemon進程佔用,這是適當重啓下server就行了,這裏兩個watch目的也是爲了提高調試的效率。
使用同一套構建也是能夠的。 不過須要注意的問題,靜態編譯後前端的模板在數據沒返回是會顯示模板的語法亂碼(這個你們都不陌生了),一般解決思路是先讓模板隱藏,數據渲染完後顯示。這裏建議是兩次打包不一樣的配置生成不一樣的html:前端渲染方式靜態編譯時,模塊主容器的內容就不要用模板了,動態引入的tpl模板會被編譯成js去填充渲染模塊的主容器。
若是使用後臺直出後,須要加載的前端js仍然是和前端渲染時同樣的,這裏能夠去作分離減少直出後加載的js文件大小。如今爲了便於處理放在一塊兒
原文: https://ouvens.github.io/frontend-build/2016/04/21/koa-fis3-swig-nodejs-isomorphic.html
有不正確的地方請大神指教~~