明確三個概念:「後端渲染」指傳統的 ASP、Java 或 PHP 的渲染機制;「前端渲染」指使用 JS 來渲染頁面大部份內容,表明是如今流行的 SPA 單頁面應用;「同構渲染」指先後端共用 JS,首次渲染時使用 Node.js 來直出 HTML。通常來講同構渲染是介於先後端中的共有部分。javascript
感受前端的確是折騰,以前還在流行先後端分離,如今怎麼又要作先後端同構了?前端
緣由是如今流行的SPA前端單頁面應用比較沉重,首次訪問須要加載文件較多,第一次加載過慢,用戶須要等待前端進行渲染頁面。並且不利於SEO及緩存,而且有必定的開發門檻。vue
先後端同構經過複用模板和JS文件,讓一份代碼能夠同時跑在服務器和瀏覽器,首次渲染使用nodejs渲染頁面,以後使用SPA路由跳轉。能夠有效減小用戶首次訪問的等待時間,而且對SEO比較友好,也便於緩存。java
本先後端同構項目主要分爲兩個部分,一個是基於koa2的渲染服務器,另外一個是基於原生JS和zepto的前端SPA。node
項目的特色是不使用vue和react等框架,門檻低,開發速度快,便於上手,比較輕巧,核心的router部分只有一百行左右的代碼。適用於頁面交互較少,變化不頻繁的場景下,能夠有效的提高性能和加載速度。react
前端部分的核心是路由部分,具體實現能夠基於history API或是hash,網上有不少實現,此次主要講下架構
前端部分採用MVC分層結構。webpack
router層作的主要是建立路由示例,調用路由的get方法,給特定頁面綁定來自control層的函數。
形式如:es6
import control from '../control' //路由的構造函數支持傳入渲染函數,路由的全局名稱,路由跳轉前調用的鉤子 router = new Router(render,'ROUTER',beforeFn) router.get('/page/a', control.pageA')
control層主要作的是加載跟後端共有的渲染模板和渲染數據,渲染出頁面後運行頁面函數web
形式如:json
let control = { pageA(req,res) { //webpack的動態加載,代碼分割功能 import(/* webpackChunkName: "pageA" */'script/pageA').then(module=> { // 檢測該頁面是否已有服務器渲染好,是的話直接運行module.default //不然加載模板和數據進行渲染,最後再調用頁面函數 if(this.needRender(module.default)) { //加載數據時訪問的地址就是當前準備渲染的頁面地址,只是加上了json=1的參數 loadData('pageA').then(data => res.render(xtpl,data,module.default)) } } } // 捕捉webpack熱更新,讓他只進行至關於頁面跳轉的操做而不是刷新頁面 if(module.hot) { module.hot.accept(['script/pageA'], () => { control[ROUTER.req.currentControl].call(ROUTER,null,ROUTER.res) }) }
view層即模板,這裏使用的是xtpl模板,在服務器環境和前端環境下都支持渲染頁面
頁面函數的形式
頁面函數要求使用es6的模塊寫法,配合webpack的按需加載功能
export default () => { window.addEventListener('scroll', fn) //頁面函數支持返回一個卸載函數,在頁面離開的時候會被調用 //主要用於內存的釋放,定時器的清除,事件監聽的移除等等 return function () { window.removeEventListener('scroll', fn) } }
使用koa2搭建的一個渲染服務器,在收到前端傳來的頁面請求時,會向API服務器請求數據,並識別頁面請求是否帶有json=1的參數,若是帶有,則爲前端路由跳轉時的請求,直接返回數據便可,若是沒有帶json參數,加載跟前端共用的模板,配合數據進行渲染,發送到瀏覽器。