webpack打包
模块提取策略
提取出第三方依赖模块(通常是node_modules)、业务依赖公共模块(通常是项目的utils文件)、业务模块(通常是一个页面的js模块),webpack运行时模块。
打包策略1:
所有模块打包成一个文件。
简单粗暴。
打包策略2:
设定一个文件大小阈值,比如200KB。
大于200KB,提取出业务模块和其依赖模块两个文件。
小于200KB,业务模块和其依赖模块打包成一个文件。
打包策略3:
设定一个模块依赖阈值,比如依赖次数3。
一个模块被三个或以上模块依赖,就独立打包成一个文件。
否则,合并到被依赖模块一同打包。
打包策略4:
多个异步模块依赖的公共模块,
打包到首屏加载的业务依赖公共模块;
或者打包到独立的异步依赖公共模块,跟懒加载的异步模块并行加载。
模块加载策略
加载顺序如下:
- webpack运行时模块
- 业务模块
- 业务依赖公共模块
- 第三方依赖模块
同步加载场景:
当项目体积并不大,并且是内网环境,可以一次性加载所有模块,并做好缓存处理。
异步加载场景:
当项目体积较大,或者是外网环境,通常进行按需异步多次加载模块。
模块缓存策略
模块缓存依赖浏览器缓存。
浏览器缓存有强缓存和协商缓存。
强缓存依赖Cache-Control字段,协商缓存依赖ETag。
缓存的命中,依靠请求的地址一致。
针对这个机制,webpack的模块缓存策略在开发和生产环境不同。
- 开发阶段的chunkhash 是 build-specific ,即每次编译都不同。
- 生产环境的chunkhash 是 chunk-specific,是根据每个 chunk 的内容计算出的 hash。
webpack数据mock
使用webpack-api-mocker模块。
该模块包含了数据mock,代理转发功能。
参考 https://github.com/jaywcjlove/webpack-api-mocker
请求和响应的预处理
使用axios作为项目的接口的请求响应预处理,来设置请求响应的统一拦截。
常用配置如下:
import axios from 'axios'
axios.defaults.baseURL = ''
//依据http状态,统一封装响应处理
axios.interceptors.response.use((res)=>{
// 状态码200阶段
if (res.status !== 200) {
return Promise.reject(res)
}
}, (error) => {
// 超时或者指定状态码阶段
let status = error.response ? error.response.status : error.message
switch (status) {
case 400:
case 401:
case 500:
default:
}
return Promise.reject(error)
})
// 请求的封装
function _request (method) {
return function (url, pms) {
let param2th = {}
if (method === 'get') {
param2th.params = pms
} else if (method === 'delete') {
param2th.data = pms
} else {
param2th = pms
}
return new Promise((resolve, reject) => {
axios[method](url, param2th).then(response => {
if (response.data.status === 0) {
resolve(response)
} else {
Message.error(response.data.errMsg || '系统繁忙')
reject(response)
}
}).catch((error) => {
reject(error)
})
})
}
}
export const _post = _request('post')
export const _put = _request('put')
export const _get = _request('get')
export const _delete = _request('delete')
import {
_get,
_post,
_delete,
_put
} from './config'
const obj = {
get: _get,
post: _post,
delete: _delete,
put: _put
}
const factory = function (method) {
return function (url) {
return function (pms) {
return obj[method](url, pms)
}
}
}
const postfn = factory('post')
const getfn = factory('get')
const deletefn = factory('delete')
const putfn = factory('put')
const projectList = getfn('/project/list.json')
const projectEdit = postfn('/project/edit.json')
路由规划
下面是几个注意点:
无token
哪些页面需要token,哪些页面不需要。
无token访问需要token的页面,路由跳转到何处。
有token访问某些页面,需要自动跳转否。
token的权限细粒度控制。token过期
路由无匹配
子路由嵌入
动态路由加载
接口结构和UI组件结构适配
后台接口结构和ElementUI所需数据结构往往不一致。
如果不用GraphQL,通常需要前端自己写适配库。
import cloneDeep from "lodash/clonedeep";
/**
树的递归查找方法
* @constructor
* @param {object} data - 树结构数据
* @param {string} key - 查找的key
* @param {string} name - key对应的value所需要相等的值
* @returns {object} - 已找到的对象
*/
const findLeafObj = function (data, key, name) {
let obj = null;
let finded = false;
let getArray = function (data, key, name) {
if (finded) {
return;
}
for (let i in data) {
let item = data[i];
if (item[key] == name) {
obj = item;
finded = true;
break;
} else {
if (item.children) {
getArray(item.children, key, name);
}
}
}
};
getArray(data, key, name);
return obj;
};
/**
Select 选择器的数据结构转换方法
* @constructor
* @param {object} data - 后台数据
* @param {object} mapping - 对应的转换方式
* @returns {object} - 适合Select 选择器的数据结构
*/
const transformSelectOption = function (data, mapping) {
return data.map(function (item) {
return {
value: mapping.value(item),
label: mapping.label(item)
};
});
};
const filterNode = function (data, id, key, method) {
return data[method](function (item) {
return item[key] === id;
});
};
const transformCascaderData = function (data, id, level, _innerLevel) {
let lev = 99999;
if(level !== undefined){
if(level < 1){
throw "level不能小于1";
}
lev = level;
}
// 根据id找到节点对象
let cur = filterNode(data, id, "id", "find");
if (!cur) {
cur = {};
}
cur['_innerLevel'] = _innerLevel;
// 根据id找到子节点对象们
let children = filterNode(data, id, "parentId", "filter");
// 根据子节点对项们递归找子节点对象们
if ((lev > 0) && children.length) {
for (let child of children) {
let n = transformCascaderData(data, child.id, lev -1, _innerLevel+1);
if (cur.children) {
cur.children.push(n);
} else {
cur.children = [n];
}
}
}
return cur;
};
/**
树的层序遍历
* @constructor
* @param {array} tree - 后台数据
* 例如:[{
"id": 1,
"name": "集团",
"parentId": 0,
"parentName": ""
}]
* @param {number} level - 层序遍历的深度
* @param {object} keyObj - 节点自身标识和父节点标识
* @returns {array} - 指定深度的节点列表
*/
const layerTraverse = function(tree, level=1, keyObj={self:'id', parent: 'parentId'}){
let thatLevelArr = [];
if(level < 1){
return thatLevelArr;
}
const innerTraverse = function(tree, id, innerLev){
let cur = filterNode(tree, id, keyObj['self'], "find");
let children = filterNode(tree, id, keyObj['parent'], "filter");
if(innerLev == level){
thatLevelArr.push(cur);
}
if(children.length > 0){
for(let child of children){
innerTraverse(tree, child[keyObj['self']], innerLev + 1);
}
}
}
innerTraverse(tree, 0 , 0);
return thatLevelArr;
};
const setCascaderOption = function (data) {
for (let item of data) {
item['label'] = item['name'];
item['value'] = item['id'];
let children = item.children;
if (children) {
setCascaderOption(children);
}
}
return data;
};
const setCascaderOptionUser = function (data, UserKeyValue) {
let dataKey = 'name';
let dataValue = 'id';
let showKey = 'label';
let showValue = 'value';
if(UserKeyValue){
dataKey = UserKeyValue['dataKey'];
dataValue = UserKeyValue['dataValue'];
showKey = UserKeyValue['showKey'];
showValue = UserKeyValue['showValue'];
}
for (let item of data) {
item[showKey] = item[dataKey];
item[showValue] = item[dataValue];
let children = item.children;
if (children) {
setCascaderOption(children, UserKeyValue);
}
}
return data;
};
/**
Cascader 级联选择器的数据结构转换方法
* @constructor
* @param {object} data - 后台数据
* @param {string} id - 转换的节点入口
* @param {object} UserKeyValue - 用户指定接口数据,和UI所需数据的对应关系
{
dataKey: '', // 接口的key
dataValue: '', // 接口value
showKey: '', // UI所需的key
showValue: '', // UI所需的value
}
* @param {number} level - 需要遍历的深度,默认99999层,最小1层。
* @returns {object} - 适合Cascader 级联选择器的数据结构
*/
const getCascaderOption = function (data, id, UserKeyValue, level) {
const storageDirectory = cloneDeep(data);
const cascaderData = transformCascaderData(storageDirectory, id, level, 0);
const cascaderOptions = setCascaderOptionUser(cascaderData.children, UserKeyValue);
return cascaderOptions;
};
export {
filterNode,
findLeafObj,
transformSelectOption,
getCascaderOption,
layerTraverse,
}