对Web开发的效率和质量要求越来越高,专业的人士做专业的事情越来越重要。前后端分离的工程实践几乎成为一种标配。
以下代码均是为了说明实现思路而简单写就的,不可以作为实际生产环境使用。请酌情修改。
目标
前后端分离的目标是最大化专业开发人员的生产效率,减少不必要的沟通和协调。通过工具将各自的劳动成果粘合到一起。
问题
不同团队需要解决的具体问题可能不尽相同,不过总体上不外乎这三个部分。
前端独立开发环境。完全不需要后端介入,就可以自己在本地开发调试,预览。
后端独立开发环境。同样的不需要前端同学的介入即可完成接口测试等工作。
前后端构建产物关联在一起。
前端独立开发环境
前端独立开发需要解决几个基本的问题。
如何预览HTML页面。一般会启动一个静态服务器来处理HTML页面(比如这个 )。当然也可以通过文件的方式来打开页面预览。
如何mock接口数据。页面大多数需要根据接口返回的数据来动态拼装。能够mock接口,利用假数据来测试页面和前端逻辑至关重要。这方面的工具也很多比如这个。
如何打包生成产物。代码写好要打成包以供部署使用。
简单方案
从上面需要解决的问题看,至少需要一个http服务器,用来提供html和接口服务。我们可以用node来实现一个简化版本的开发服务器。另外还需要一个构建打包工具。
功能点:
能够提供http服务,预览html效果。
能够提供接口mock服务,预览动态效果。
Javascript 支持到es5,不支持模块系统;css 只允许写原声的css;可以用简单的脚本搞定这一切。
开发服务器简单实现的示例代码:
- 利用express 实现简单的开发服务器。
var express = require('express')
var bodyParser = require('body-parser')
var fs = require('fs')
var http = require('http')
var app = express()
var httpServer = http.createServer(app)
app.use(express.static('./'))
app.use(bodyParser.json())
app.use(function(req, res, next) {
// 默认返回json数据结构
res.header('content-type', 'application/json;charset=utf-8')
// 解决cors跨域问题
res.header('access-control-allow-credentials', 'true')
res.header('access-control-allow-headers', 'content-type,cookieorigin,x-requested-with')
res.header('access-control-allow-methods', 'POST, GET, OPTIONS')
res.header('access-control-allow-origin', req.get('origin'))
if(req.method === 'OPTIONS') {
res.end()
} else {
next()
}
})
// url 返回规则配置
app.use(function(req, res, next) {
if(new RegExp('/api/order/list', 'i').test(req.url)) {
res.end(fs.readFileSync('./mock/order_center/list.json'))
} else {
next()
}
})
httpServer.listen(8088);
console.log('http server at 8088');
在server.js
中实现了一个简单的开发服务器。可以把具体的规则部分抽出到单独的文件中,实现更加复杂的逻辑。使用的时候,只需要把server.js copy到项目目录下,安装所需要的依赖即可。
如果不考虑采用较高的es6特性,模块化方案,sass等特性,纯手撸代码,那么可以简单的把所有的文件拼合成一个文件即可。
// 用namespace的方式来防止变量冲突
// namespace.js
function namespace(packageName, implementation) {
if(packageName) {
var nm = window
packageName.split('.').forEach(function(name) {
if(name) {
nm[name] = nm[name] || {}
nm = nm[name]
}
})
if(implementation) {
for(var k in implementation) {
nm[k] = implementation[k]
}
}
return nm
}
}
namespace('cn.honchy.utils', {
namespace: namespace
})
假设我们pages下面用来存放页面代码,其他的工具函数都放在utils里面,打包的需要是合并成为一个js文件,那么可以简单的这么干。当然如下代码也仅仅考虑合并在一起。其他的比如依赖分析等暂不考虑
// buid.js 用来合并代码用
// 假设我们src下所有的文件
var fs = require('fs')
var path = require('path')
var execSync = require('child_process').execSync
var files = fs.readdirSync(path.resolve(__dirname , './src'))
// 读取到文件之后需要拍下顺序,把pages放到最后面
files.sort(function(a, b) {
if(a === 'pages') {
return 1
} else if(b === 'pages') {
return -1
} else {
return a.localeCompare(b)
}
})
var build = path.resolve(__dirname, './build')
execSync('rm -rf ' + build)
execSync('mkdir -p ' + build)
var bundle = path.resolve(build, './bundle.js')
execSync('touch ' + bundle)
function appendFile(fp) {
var content = fs.readFileSync(fp).toString()
content = '!(function() {' + content + '})();'
fs.appendFileSync(bundle, "/* " + fp + " */\r\n")
fs.appendFileSync(bundle, content + "\r\n")
}
function processFile(fp) {
var stat = fs.lstatSync(fp)
if(stat.isFile()) {
appendFile(fp)
} else if(stat.isDirectory()) {
var children = fs.readdirSync(fp)
children.forEach(function(child) {
var cfp = path.resolve(fp, child)
processFile(cfp)
})
}
}
// 优先注入namespace
var namespacepath = path.resolve(__dirname, './namespace.js')
appendFile(namespacepath)
// 然后把读取到的文件包装成为立即执行表达式
files.forEach(function(file) {
var fp = path.resolve(__dirname, './src', file)
processFile(fp)
})
现在的目录结构如下:
详细的代码在https://github.com/honchy/mock-server/tree/simple_impl
后端独立开发环境
笔者对后端的工程化理解不深,在我看来,前后端分离后有几个问题需要解决。
- 开发环境,生产环境等的区分和灵活切换。
前后端分离之后,开发节奏也相对解耦了。那么就有可能出现,多个前端对一套后端,或者多个后端对一套前端的情况。这就要求有比较灵活的环境切换能力。 - 接口模拟测试工具。
因为不需要前端页面来生成测试数据了,所以需要另外的一个工具来模拟接口请求和生成测试数据。
此方面涉及比较多的运维知识,对笔者来说太复杂了。另起文章来介绍好了。先埋个坑。
前后端重新关联
前后端关联就是把前端和后端的构建产物结合在一起。一般来说把前端资源copy到后端指定的目录中即可。需要解决的问题是:
- 约定。
前端的构建产物,以约定的路径和文件名放到指定的服务器上。后端根据前端开发人员提供的版本号等去服务器上下载下来,并且解压到指定的文件夹下。这块可以借助maven的私服来实现,也可以简单的通过文件存储来解决。 - 资源引用路径的协调。
前端目录结构和后端的目录结构的层次可能不一样,对有可能出现的路径不一致的情况需要考虑如何处理。