本文主要介紹一種全新的先後端合做模式,在介紹這種模式以前咋們先來看看先後端合做模式的演變。css
singsong:該模式主要對傳統多頁面應用構建的改進。關於 SPA(Single Page Application,單頁應用程序)能夠參考 SSR(Server-Side Render,服務端渲染)。html
在那個前端角色比較弱化的年代,頁面主要以靜態頁面爲主。合做模式很簡單粗暴。前端
前端開發完頁面,輸出給後端。後端拿到頁面拼模板,而後再渲染輸出。但隨着前端業務複雜性逐漸地增長,這種模式合做起來就不是很愉快了。後端在拼模板時,不能確保輸出的頁面結構與前端保持一致。爲了規避這個問題,聰明的開發人員就給前端同窗建議:使用後端開發環境開發前端頁面。node
前端直接在本地部署後端開發環境,只負責 views 開發。再基於代碼託管工具 git,實現先後端的合做。此時仍是之後端爲主導,維護性成本依然很高。webpack
隨着 node.js 的崛起,前端工程化也逐漸成爲前端開發重要組成部分。各類構建工具(如 webpack、rollup、grunt、gulp 等),MVVM 框架(如 React、Vue、Angular 等),模塊化系統(cjs、amd、umd、ES6-modules 等),CSS 預處理器(SASS、Less、Stylus、Postcss 等),模塊管理工具(NPM、Yarn、brower 等)猶如雨後春筍般不斷涌現。SAP 也開始在前端領域流行來,前端能作的事情更多。如客戶端渲染,靜態分析、優化打包等。後端只需負責數據提供。此時,先後端已徹底分離,各自負責各自的業務。git
這種合做模式的核心:客戶端渲染 + 接口。因爲基於客戶端渲染,對瀏覽器的 SEO 不是很友好。雖然能夠經過 SSR 來解決,可是 SSR 也存在侷限性。關於 SSR 感興趣的同窗可自行查閱相關的資料。本文主要探討傳統多頁面應用構建的優化。github
基於前端工程化,要讓傳統多頁面應用構建也支持先後端徹底分離。還須要作一件事,前端腳手架須要與後端使用相同的模板渲染引擎。前端編寫好後,直接輸出模板給後端使用。爲此本身也構建一個 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();
};
複製代碼
運行效果:
本文主要與你們分享一種先後端合做新模式,也許當成一種新思路更恰當些😀,由於它只是對多頁面應用構建的一種優化。無論怎樣也但願本文能讓各位讀者有所收穫。週末愉快~~~🌴