本文主要介绍一种全新的先后端合做模式,在介绍这种模式以前咋们先来看看先后端合做模式的演变。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(); };
运行效果:
本文主要与你们分享一种先后端合做新模式,也许当成一种新思路更恰当些😀,由于它只是对多页面应用构建的一种优化。无论怎样也但愿本文能让各位读者有所收获。周末愉快~~~🌴