0.背景 为什么需要本地mock?
最终目标:提高联调效率。
1.前端在开发完成之后,如果后端接口还没有打通,那么前端需要在开发阶段仿造一批数据,这些数据一般都是直接写在组件中,然后等到联调的时候再把这些数据删除。这种增加删除的操作个人觉得还是很麻烦的。
2.此外,还有一种情况,当我们在某个开发阶段突然发现后端的测试服务器挂了,此时我们之前写在组件里的mock数据都早已删除,这个时候我们各个接口都是不通的,我们也不可能一一再去mock之前的接口数据,这个时候只能等待后端接口返回。(例如:列表中的点击按钮弹窗,后端接口list数据挂了,我们只有先去mock一下list数据,才能进入弹窗组件开发,这一系列mock操作之后10min过去了,然后开发一段时间,后端接口好了,我们再去删除。)哪天后端接口又挂了,这个时候前端只能在走一遍之前的mock数据,然后删除。个人觉得,针对这种场景,前端mock就是解决这种繁琐的重复操作的。
前置知识:
import fs from 'fs'; // 1.fs文件模块
import url from 'url'; // 2.url模块
import path from 'path'; // 3.path模块
1.mock方案
1.1 直接使用mockJS
1.2 dev服务器的中间件 - onBeforeSetupMiddleware
通过直接使用Mcokjs的缺点就在于,MockJs的原理是在前端XHR对象上进行拦截,没有真正的发出ajax请求。这就导致我们无法通过network控制台去检查返回的数据,而是每次通过console去查看mock数据值,然后再删除console。而通过本地dev服务器可以实现真正的mock,可以在控制台中看见返回的mock数据。
前提条件:使用dev-server首先保证前端发出的请求是打在dev-server上的,而不是直接通过axios发送到后端服务器上的。因为只有前端请求打在dev-server上,这样才能走我们在dev-server服务器上的中间件逻辑,否则这些逻辑就不会触发。
这就要求我们本地采用的跨域方案需要是通过server代理,而不是通过CORS(access-control-allow- origin)来实现。
1.2.1 实现目标
目标1: 响应返回可以显示在network中
目标2: 模块化管理Mock数据前端发出请求返回的Mock数据是在本地文件中读取的
目标3: 向前兼容:对于之前没有采用Mcok的接口是直接返回后端服务器中的数据,采用Mock的接口返回本地Mock数据
目标4: 启动Mock: mock使用时间是在前端开发完成等待联调的时候,如果联调阶段我们希望统一的打到后端服务器的接口上,那么我们就希望关闭本地的Mock接口,这个时候就需要一个环境变量来区分是否开启mock。通过script增加执行命令
需要我们新增一个环境变量,然后在dev服务器执行的时候先判断是否开启Mock,如果环境变量为是,那么就走mock逻辑,否则直接next(),打到后端服务器上。
如何在script字段上新增一个变量
1.3 websokcet实现mock
1.webpack-dev-serve
webpack中的dev-serve本质上是基于express加上webpack-dev-middleware(webpack-dev中间件)实现的。
// webpack-dev-server启动的基本过程伪代码如下
const express = require('express');
const webpackDevMiddleware= require('webpack-dev-middleware');
const app = express();
const devServer = app.use( webpackDevMiddleware(config) )
先加载express,然后加载wepback-dev-middleware中间件。这个webapack-dev中间件主要作用就是以监听模式启动webpack,将编译后的文件放入到内存中,然后与热更新模块API进行沟通便于本地更新。
需掌握知识:
(1) webpack-dev-server运行原理:cloud.tencent.com/developer/a…
(2) devServer配置项
解析路径
if (isLocalMock) {
// TODO:
devServer.before = function (app) {
function getLocalPath(url, method) {
let _path = path.resolve(__dirname, `mock${url}`);
if (method === 'GET'
&& fs.existsSync(_path + '.js')
&& fs.lstatSync(_path + '.js').isFile()) {
// nothing to do
} else {
switch (method) {
case 'GET':
_path = path.resolve(_path, 'read');
break;
case 'PUT':
_path = path.resolve(_path, 'update');
break;
case 'POST':
_path = path.resolve(_path, 'create');
break;
case 'DELETE':
_path = path.resolve(_path, 'delete');
break;
}
if (!(fs.existsSync(_path + '.js')
&& fs.lstatSync(_path + '.js').isFile())) {
return;
}
}
return _path;
}
app.use((req, res, next) => {
const path = getLocalPath(req.path, req.method); // req.method是大写字母
if (path) {
let data = '';
req.on('data', function (chunk) {
data += chunk;
});
req.on('end', function () {
req.rawBody = data;
req.jsonBody = data ? JSON.parse(data) : '';
delete require.cache[require.resolve(path)];
let mock = require(path);
if (Object.prototype.toString.call(mock).slice(8, -1) === 'Function') {
mock = mock({
params: req.jsonBody,
query: req.query
});
}
const body = mock.useMockJs ? Mock.mock(mock.body || mock) : (mock.body || mock);
res.status(mock.status || 200).json(body);
});
} else {
next();
}
});
}
}
path.resolve(): 目标是把一个路径或者路径片段解析为绝对路径为止。给定的路径序列处理的顺序是从右向左处理zhuanlan.zhihu.com/p/262922646 blog.csdn.net/kikyou_csdn…
相关知识点
1.软链接和硬链接
首先,不管是硬链接还是软链接,其共同点都是修改源文件,链接的另一方也生效
软链接:在linux系统中,软连接就和windows中桌面的快捷方式类似,都是通过一个软连接文件指向另一个真正的文件,真正文件的修改也会同样导致软连接文件打开后的结果(ln -s 文件名 链接文件名)
硬链接:硬链接不支持指向目录。也就是说“文件A是软链接指向真正的文件B,那么文件B的数据保存在磁盘中,那么文件B就是硬链接”,这个说法是错误的。文件B通过硬链接创建文件C,这个时候文件B和文件C都是指向同一个inode。如果文件B进行修改,那么对应的inode存储的内容就会修改,则文件c打开后就是修改后的数据。另一方面,文件B删除了那么打开文件c还是有之前的数据,因为inode还存在一个硬链接c,所以依然保留数据。因为硬链接不允许目录,即不支持文件夹(ln 文件 文件名)。
2.运行npm run命令发生了什么
bbs.huaweicloud.com/blogs/35266…
查找.bin目录,找到对应同命名的文件,这些文件是对应node-modules下面下载依赖包的软链接,
然后查找对应的js执行文件
这个时候,执行formula就是脚手架定义的命令,即执行bin/formula.js
3.脚手架构建中的commander
3.1 commander模块:是nodeJs中的一个模块,需要在node环境下运行
command函数接收三个参数:
- 命令名称
必须:命令后面可跟用<>或[]包含的参数;命令的最后一个参数可以是可变的,像实例中那样在数组后面加入...标志;在命令后面传入的参数会被传入到action的回调函数以及program.args数组中。 - 命令描述
可省略:如果存在,且没有显示调用action(fn),就会启动子命令程序,否则会报错 - 配置选项
可省略:可配置noHelp、isDefault等
#!/usr/bin/env node
const program = require('commander');
const create = require('../src/create');
console.log('123123')
// 命令:pro-cli create app -f,其中pro-cli全局命令是在bin字段定义的,create是二级命令通过command这个API定义,app是二级命令的参数,-f是option定义的选项。当然也可以不执行参数直接pro-cli create -f
program
.command('create <projectName>') // 这里输入对应的指令参数‘create’,后面的<name>参数标志,例如vu create app,中的app就是projectName,这个会在action中的回调函数参数中获取到。
.alias('c') // 配置参数的简写,pro-cli create 可以简写为“pro-cli c”
.description('create a new project')
.option('-f, --force', 'message') // 此时执行的命令全称为:pro-cli create app -f,action通过name拿到create app,options用来拿到后面的-f
.action(name => { // 这里的name就是获取指令参数后面的 参数变量
console.log("project name is " + name)
})
program.parse(process.argv)
// 注意:如果测试阶段,需要在执行aciton命令后,在控制台打印相关信息,一定要在pargram.parse函数中增加参数process.argv,否则会报错。(文献:https://juejin.cn/post/6959750919491682318#heading-2)
commander.command()方法就是用来设置二级命令
option方法定义commander的选项