本文主要介紹一種全新的先後端合做模式,在介紹這種模式以前咋們先來看看先後端合做模式的演變。css
singsong:該模式主要對傳統多頁面應用構建的改進。關於 SPA(Single Page Application,單頁應用程序)能夠參考 SRR(Server-Side Render,服務端渲染)。
在那個前端角色比較弱化的年代,頁面主要以靜態頁面爲主。合做模式很簡單粗暴。html
前端開發完頁面,輸出給後端。後端拿到頁面拼模板,而後再渲染輸出。但隨着前端業務複雜性逐漸地增長,這種模式合做起來就不是很愉快了。後端在拼模板時,不能確保輸出的頁面結構與前端保持一致。爲了規避這個問題,聰明的開發人員就給前端同窗建議:使用後端開發環境開發前端頁面。前端
前端直接在本地部署後端開發環境,只負責 views 開發。再基於代碼託管工具 git,實現先後端的合做。此時仍是之後端爲主導,維護性成本依然很高。node
隨着 node.js 的崛起,前端工程化也逐漸成爲前端開發重要組成部分。各類構建工具(如 webpack、rollup、grunt、gulp 等),MVVM 框架(如 React、Vue、Angular 等),模塊化系統(cjs、amd、umd、ES6-modules 等),CSS 預處理器(SASS、Less、Stylus、Postcss 等),模塊管理工具(NPM、Yarn、brower 等)猶如雨後春筍般不斷涌現。SAP 也開始在前端領域流行來,前端能作的事情更多。如客戶端渲染,靜態分析、優化打包等。後端只需負責數據提供。此時,先後端已徹底分離,各自負責各自的業務。webpack
這種合做模式的核心:客戶端渲染 + 接口。因爲基於客戶端渲染,對瀏覽器的 SEO 不是很友好。雖然能夠經過 SRR 來解決,可是 SRR 也存在侷限性。關於 SRR 感興趣的同窗可自行查閱相關的資料。本文主要探討傳統多頁面應用構建的優化。git
基於前端工程化,要讓傳統多頁面應用構建也支持先後端徹底分離。還須要作一件事,前端腳手架須要與後端使用相同的模板渲染引擎。前端編寫好後,直接輸出模板給後端使用。爲此本身也構建一個 fes 腳手架。web
在使用 fes 構建了幾個項目後,雖然在開發體驗、開發效率上都獲得了很大地提高。但與後端合做起來不是很輕鬆。由於在開發以前須要與後端約定好模板數據變量,通常會以文檔形式進行說明。若是後端更新了數據,沒同步更新文檔,就會存在數據不一致的問題。對開發效率大打折扣,與傳統的後端從新拼接模板相比優點不是很明顯。秉着 geek 的精神,就想能不能 將模板數據以接口的形式提供給前端。這樣上述問題不就迎刃而解了麼。gulp
因而就對 fes 進行改造。讓其支持模板數據接口的配置,在渲染以前會根據配置拉起接口數據,再進行渲染輸出。同時還提供了接口數據適配功能,能讓客戶端更好地控制數據結構。後端
mockConfig: { // 訪問路徑做爲key '/index': { // 提供渲染模板數據接口 api: 'https://postman-echo.com/get?page=index', // 適配數據 format: data => data.args, }, '/fes/info': { api: 'https://postman-echo.com/get?page=info', format: data => data.args, }, }
另外,爲了方便查看模板數據,開發模式下輸入 mock
指令可獲取當前的模板數據。前端工程化
$ mock Mock Data: { "/about": { "page": "about", "common": "commons", "data": "singsong", "name": "fes-about-page" }, "/index": { "page": "index", "common": "commons", "name": "fes-index-page", "data": { "name": "fes" }, "article": { "_value": {}, "_state": 1 } }, "/post": { "common": "commons" } }
若是須要查看對應頁面數據,只需輸入對應的路徑便可。如 /index
。
$ /index Mock Data [/index]: { "page": "index", "common": "commons", "name": "fes-index-page", "data": { "name": "fes" }, "article": { "_value": {}, "_state": 1 } }
除了 mock 指令,還提供了以下指令:
這種合做模式,須要後端多作一件事。額外提供一個包含模板數據的接口供前端使用(只存在開發環境下,在上線時須要關閉掉)。只需對該類接口定義特定的前綴,而後在模板渲染邏輯以前攔截請求響應數據。這樣不只能保持數據一致性,並且維護起來也方便。
這裏以改進後 fes 進行演示。以下以 index 頁面
爲例進行講解:
index 頁面路由爲:http://127.0.0.1:3001/index
,結構以下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta http-equiv="X-UA-Compatible" content="ie=edge" /> <title>{{ title }}</title> </head> <body> <h1>{{ title }}</h1> </body> </html>
模板數據接口爲:http://127.0.0.1:3001/mockdata/index
。其中/mockdata
爲前綴,接口返回的數據以下:
{ "rawdata": { "title": "index page" }, }
在 fes 中對模板數據接口進行以下配置
mockConfig: { // 訪問路徑做爲key '/index': { // 提供渲染模板數據接口 api: 'http://127.0.0.1:3001/mockdata/index', } }
這裏若是隻想要 rawdata
中的變量數據。能夠經過format
進行適配。最後的配置信息以下:
mockConfig: { // 訪問路徑做爲key '/index': { // 提供渲染模板數據接口 api: 'http://127.0.0.1:3001/mockdata/index', // 適配數據 format: data => data.rawdata, } }
路由處理器
//path: ./controllers/index.js module.exports = async ctx => { const {url} = ctx; // 從 DB 獲取數據 const getDataFromDB = () => { // 作一些數據查詢操做…… return { rawdata: { title: 'index page', }, }; }; const data = getDataFromDB(); // 判斷是否有前綴進行不一樣的響應 // 這裏前綴已保存在 ctx.mockApiPrefix 中 if (url.indexOf(ctx.mockApiPrefix) === 0) { ctx.body = data; } else { await ctx.render('index', data.rawdata); } };
定義路由
// path: ./pages/index.js // 該接口與頁面請求使用相同的路由處理,經過是否有`PREFIX`進行區別 module.exports = (PREFIX = '') => [ { method: 'get', path: `${PREFIX}/index`, middleware: require('./controllers/index'), }, ];
註冊路由
const Router = require('koa-router'); const router = new Router(); const pagesConfig = require('./pages/index'); const config = []; // 定義前綴 const MOCK_API_PREFIX = '/mockdata'; // 註冊頁面路由 config.push(...pagesConfig()); // 註冊模板數據接口 if (process.env.NODE === 'dev') { config.push(...pagesConfig(MOCK_API_PREFIX)); } const generateRoutes = (router, config) => { config.forEach(({method, path, middleware}) => { router[method](path, middleware); }); }; module.exports = app => (ctx, next) => { // 綁定前綴,方便後續邏輯使用 ctx.mockApiPrefix = MOCK_API_PREFIX; generateRoutes(router, config); app.use(router.routes()).use(router.allowedMethods()); return next(); };
運行效果:
本文主要與你們分享一種先後端合做新模式,也許當成一種新思路更恰當些😀,由於它只是對多頁面應用構建的一種優化。無論怎樣也但願本文能讓各位讀者有所收穫。週末愉快~~~🌴