内容简介:抽点时间码字...续上一篇上一遍写到了项目中
抽点时间码字...
续上一篇 《【vue-cli3升级】老项目提速50%(一)》
上一遍写到了项目中 eslint
的错误处理,原谅我并不怎么会写文章,哈哈...
继续说明下本文只作为个人在实际工作中的经历总结...
本着不影响业务代码的原则和初心,继续这次升级改造工程的历程...
本文大致分为以下几个部分:
- 环境变量相关
- mock集成
- npm script
- vue.config.js:webpack优化、task任务执行、历史版本处理等
- shell文件部署远程服务器:执行task任务历史版本处理、打包推送远程服务器
环境变量相关
不得不说不认真仔细看文档的话,这个是个坑...
在 vue-cli3
项目中,删除了以往存放环境变量的 config
目录,改为:
.env # 在所有的环境中被载入 .env.local # 在所有的环境中被载入,但会被 git 忽略 .env.[mode] # 只在指定的模式中被载入 .env.[mode].local # 只在指定的模式中被载入,但会被 git 忽略 复制代码
原项目中共有三个环境 dev
beta
prod
,依次建立 .env.dev
.env.beta
.env.prod
文件, key=value
形式写入环境变量
需要特别注意:一定记得要以 VUE_APP_
开头命名变量,不然不会写入到 process.env
, build
命令的时候不受影响的,楼主这个坑踩的很蛋疼...
# .env.dev VUE_APP_API_ENV=dev VUE_APP_BASE_API=xxx ... 复制代码
以 VUE_APP_
开头命名的变量 VUE_APP_*
就可以在项目中愉快的使用 process.env.VUE_APP_*
访问了。
# .env.beta NODE_ENV=production VUE_APP_API_ENV=beta VUE_APP_BASE_API=xxx ... 复制代码
# .env.prod NODE_ENV=production VUE_APP_API_ENV=pro VUE_APP_BASE_API=xxx ... 复制代码
mock集成
API文档还是头疼啊,业务高速发展,文档缺失严重,文档依然 showdoc
书写,不吐槽了...
本打算采取本地mock的形式,想想算了,需要编写一堆文件不说,随着版本迭代,mock文件会越来越大...
最终考虑实际情况,采用easy-mock 的形式
easy-mock官网新建团队项目:登录 => 我的项目(团队项目)=> 创建团队 => 创建项目
创建完成后,点击进入项目:
easy-mock
描述就到这,简单易上手,各位有兴趣的自行操作去吧...
复制 Base URL
,写入之前的环境变量文件 .env.dev
VUE_APP_MOCK=false # mock全局开关 VUE_APP_MOCK_BASE_URL=https://www.easy-mock.com/mock/xxx # mock base url 复制代码
VUE_APP_MOCK:作为在项目dev模式中,是否开启mock的全局开关
VUE_APP_MOCK_BASE_URL:作为在项目dev模式中,请求url的baseUrl
接下来看下 src/api
,统一管理项目中的api请求(模块化,与后端微服务模块一一对应)
1、新建 example
模块: src/api/example.js
import { asyncAxios } from '@/plugin/axios' export const exampleApi = { baseUrl: 'example/', list (params = {}) { return asyncAxios(`${this.baseUrl}list`, params, { isMock: true }) }, detail (params = {}) { return asyncAxios(`${this.baseUrl}detail`, params, { isMock: true }) } } 复制代码
代码中从 @/plugin/axios.js
引入了 asyncAxios
方法,下面提供 axios.js
代码,组合起来看吧:
import store from '@/store' import axios from 'axios' import { Toast } from 'vant' import util from '@/libs/util' // 创建一个错误 const errorCreate = msg => { const err = new Error(msg) errorLog(err) throw err } // 记录和显示错误 const errorLog = err => { // 添加到日志 store.dispatch('xxx/log/add', { type: 'error', err, info: '数据请求异常' }) // 打印到控制台 if (process.env.NODE_ENV === 'development') { util.log.danger('>>>>>> Error >>>>>>') console.log(err) } // 显示提示 Toast({ message: err.message, type: 'error' }) } // 创建一个 axios 实例 const service = axios.create({ baseURL: process.env.VUE_APP_BASE_API, timeout: 5000 // 请求超时时间 }) // 请求拦截器 service.interceptors.request.use( config => { // 在请求发送之前做一些处理 const token = util.cookies.get('token') config.headers['X-Token'] = token // 处理mock if (process.env.VUE_APP_MOCK && config.isMock) { config.url = `${process.env.VUE_APP_MOCK_BASE_URL}/${config.url}` } return config }, error => { // 发送失败 console.log(error) Promise.reject(error) } ) // 响应拦截器 service.interceptors.response.use( response => { const dataAxios = response.data const { code } = dataAxios if (!code) return dataAxios switch (code) { case 0: case 10000: // 成功 return dataAxios.data case 'xxx': errorCreate(`[ code: xxx ] ${dataAxios.msg}: ${response.config.url}`) break default: // 不是正确的 code errorCreate(`${dataAxios.msg}: ${response.config.url}`) break } }, error => { if (error && error.response) { switch (error.response.status) { case 400: error.message = '请求错误'; break case 401: error.message = '未授权,请登录'; break case 403: error.message = '拒绝访问'; break case 404: error.message = `请求地址出错: ${error.response.config.url}`; break case 408: error.message = '请求超时'; break case 500: error.message = '服务器内部错误'; break case 501: error.message = '服务未实现'; break case 502: error.message = '网关错误'; break case 503: error.message = '服务不可用'; break case 504: error.message = '网关超时'; break case 505: error.message = 'HTTP版本不受支持'; break default: break } } errorLog(error) return Promise.reject(error) } ) export default service 复制代码
mock相关的关键代码就在于请求拦截器中:
if (process.env.VUE_APP_MOCK && config.isMock) { config.url = `${process.env.VUE_APP_MOCK_BASE_URL}/${config.url}` } 复制代码
判断全局mock开关和请求配置项中的isMock字段来控制是否启用mock接口
npm script
vue-cli-service
更多内容请查看文档
vue-cli-service serve [options] [entry] 选项: --open 在服务器启动时打开浏览器 --copy 在服务器启动时将 URL 复制到剪切版 --mode 指定环境模式 (默认值:development) --host 指定 host (默认值:0.0.0.0) --port 指定 port (默认值:8080) --https 使用 https (默认值:false) 复制代码
vue-cli-service build [options] [entry|pattern] 选项: --mode 指定环境模式 (默认值:production) --dest 指定输出目录 (默认值:dist) --modern 面向现代浏览器带自动回退地构建应用 --target app | lib | wc | wc-async (默认值:app) --name 库或 Web Components 模式下的名字 (默认值:package.json 中的 "name" 字段或入口文件名) --no-clean 在构建项目之前不清除目标目录 --report 生成 report.html 以帮助分析包内容 --report-json 生成 report.json 以帮助分析包内容 --watch 监听文件变化 复制代码
先上一份项目中 script
配置:
"scripts": { "dev": "npm run serve", "serve": "vue-cli-service serve --mode dev", "build": "vue-cli-service build --no-clean --mode dev", "build_app": "cross-env PAGE_ENV=app vue-cli-service build --no-clean --report --mode prod", "build_beta": "vue-cli-service build --no-clean --report --mode beta", "build_pro": "vue-cli-service build --no-clean --report --mode prod", "lint": "vue-cli-service lint --fix" } 复制代码
项目中使用了--mode(指定环境模式)、--no-clean(不清除dist文件,会在后面一键打包推送到远程服务器说明)、--report(生成report.html分析包内容),命令集成保持和老项目一致...
好像这部分也没啥好讲的了,原则就是保持和老项目一致的命令~~
vue.config.js
直接上完整代码吧,码字真累
const path = require('path') const CompressionWebpackPlugin = require('compression-webpack-plugin') const assetsDir = 'static' const resolve = dir => path.join(__dirname, dir) // posix兼容方式处理路径 const posixJoin = _path => path.posix.join(assetsDir, _path) const lastVersion = new Date().getTime() const isProd = process.env.NODE_ENV === 'production' // cdn开关 const OPENCDN = true const webpackHtmlOptions = { // dns预加载,优化接口请求 dnsPrefetch: [ 'https://aaa.exmaple.com', 'https://bbb.exmaple.com', 'https://ccc.exmaple.com', 'https://ddd.exmaple.com', 'https://eee.exmaple.com', 'https://fff.exmaple.com' ], externals: { 'vue': 'Vue', 'vue-router': 'VueRouter', 'vuex': 'Vuex', 'js-cookie': 'Cookies' }, cdn: { // 生产环境 build: { css: [ 'https://cdn.jsdelivr.net/npm/vant@1.5/lib/index.css' ], js: [ 'https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.min.js', 'https://cdn.jsdelivr.net/npm/vue-router@3.0.1/dist/vue-router.min.js', 'https://unpkg.com/vuex@3.0.1/dist/vuex.min.js', 'https://cdn.jsdelivr.net/npm/vant@1.5/lib/vant.min.js', 'https://cdn.jsdelivr.net/npm/js-cookie@2.1.3/src/js.cookie.min.js' ] } } } module.exports = { publicPath: '/', outputDir: 'dist', assetsDir, productionSourceMap: false, // 关闭生成环境sourceMap devServer: { open: false, host: '0.0.0.0', port: 3900 }, css: { // 增加版本号 extract: !isProd ? false : { filename: posixJoin(`css/${lastVersion}-[name].[contenthash:8].css`), chunkFilename: posixJoin(`css/${lastVersion}-[name].[contenthash:8].css`) } }, configureWebpack: config => { config.resolve.extensions = ['.js', '.vue', '.json'] if (isProd) { // 生成环境执行task任务,写入版本号 const task = require('./task') task.run(lastVersion) config.plugins.push( // 启用gzip new CompressionWebpackPlugin({ test: new RegExp('\\.(' + ['js', 'css'].join('|') + ')$'), threshold: 10240, minRatio: 0.8 }) ) // 开启cdn状态:externals不进入webpack打包 if (OPENCDN) { config.externals = webpackHtmlOptions.externals } } }, chainWebpack: config => { /** * 删除懒加载模块的 prefetch preload,降低带宽压力 */ config.plugins .delete('prefetch') .delete('preload') config.resolve.alias .set('vue$', 'vue/dist/vue.esm.js') .set('@', resolve('src')) // 清除警告 config.performance .set('hints', false) // 将版本号写入环境变量 config .plugin('define') .tap(args => { args[0]['app_build_version'] = lastVersion return args }) config .when(isProd, config => // 生产环境js增加版本号 config.output .set('filename', posixJoin(`js/${lastVersion}-[name].[chunkhash].js`)) .set('chunkFilename', posixJoin(`js/${lastVersion}-[id].[chunkhash].js`)) ) /** * 添加CDN参数到htmlWebpackPlugin配置中, 修改 public/index.html */ config.plugin('html').tap(args => { // 生产环境将cdn写入webpackHtmlOptions,在public/index.html应用 if (isProd && OPENCDN) { args[0].cdn = webpackHtmlOptions.cdn.build } // dns预加载 args[0].dnsPrefetch = webpackHtmlOptions.dnsPrefetch return args }) } } 复制代码
这里会涉及很多公司业务相关的,凑合着看看吧,特意加了注释说明一下下...有兴趣的留言讨论
webpackHtmlOptions
的应用在 public/index.html
体现( htmlWebpackPlugin.options
读取):
<!DOCTYPE html> <html> <head> <title>xxx</title> <meta charset="utf-8"> <meta content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no" name="viewport"> <!-- dns-prefetch,在vue.config.js配置 --> <% for (var i in htmlWebpackPlugin.options.dnsPrefetch) { %> <link rel="dns-prefetch" href="<%= htmlWebpackPlugin.options.dnsPrefetch[i] %>"> <% } %> <meta name="msapplication-tap-highlight" content="no"> <meta content="telephone=no" name="format-detection" /> <meta content="email=no" name="format-detection" /> <meta name="apple-mobile-web-app-capable" content="yes" /> <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" /> <meta name="apple-mobile-web-app-title" content="xxx"> <link rel="icon" href="<%= BASE_URL %>static/applogo.png" type="image/x-icon"> <!-- CDN css,在vue.config.js配置 --> <% for (var i in htmlWebpackPlugin.options.cdn&&htmlWebpackPlugin.options.cdn.css) { %> <link href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" rel="preload" as="style"> <link href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" rel="stylesheet"> <% } %> <!-- 使用CDN加速的JS文件,配置在vue.config.js下 --> <% for (var i in htmlWebpackPlugin.options.cdn&&htmlWebpackPlugin.options.cdn.js) { %> <link href="<%= htmlWebpackPlugin.options.cdn.js[i] %>" rel="preload" as="script"> <% } %> </head> <body> <div id="app"></div> <!-- <script charset="utf-8" type="text/javascript" src="//g.alicdn.com/de/prismplayer/2.7.1/aliplayer-min.js"></script> --> <!-- CDN js,在vue.config.js配置 --> <% for (var i in htmlWebpackPlugin.options.cdn&&htmlWebpackPlugin.options.cdn.js) { %> <script src="<%= htmlWebpackPlugin.options.cdn.js[i] %>"></script> <% } %> </body> </html> 复制代码
Task任务,shell文件打包远程推送
task.js:利用nodejs在dist目录中写入history.js版本控制文件
build.sh:拉取远程代码 => 本地打包 => 删除版本控制外的历史文件 => 推送远程
为什么要做版本控制?为了用户无感知,为了随时发布,为了不加班(随时发布了还加什么班?很实在,哈哈)...
发布过程中,当用户在我们的产品内溜达的时候不出错~(没有版本控制前老板好几次白屏了哦...)
build --no-clean
模式不清除dist文件夹, history.js
存储5个版本号,build.sh控制远程仓库5个版本
上代码吧:
let fs = require('fs') let path = require('path') let endOfLine = require('os').EOL module.exports = { maxHistoryNum: 5, historyFile: path.resolve(__dirname, './dist/history.js'), staticDir: path.resolve(__dirname, './dist/'), creataHistoryIfNotExist () { if (!fs.existsSync(this.historyFile)) { this.storeHistory([], 'a+') } }, // @done 将数据写到 history.js storeHistory (list, mode) { let historyFile = this.historyFile let outJson = 'module.exports = [' + endOfLine let listLen = list.length if (list && listLen > 0) { list.forEach((item, index) => { if (index === listLen - 1) { outJson += ` ${item}${endOfLine}` } else { outJson += ` ${item},${endOfLine}` } }) } outJson += ']' + endOfLine fs.writeFileSync(historyFile, outJson, { flag: mode }) }, // 递归删除目录中的文件 rmFiles (dirPath, regexp) { let files try { files = fs.readdirSync(dirPath) } catch (e) { return } if (regexp && files && files.length > 0) { for (let i = 0; i < files.length; i++) { let filename = files[i] let filePath = dirPath + '/' + files[i] if (fs.statSync(filePath).isFile() && regexp.test(filename)) { console.log('删除过期的历史版本->(' + regexp + '):' + filename) fs.unlinkSync(filePath) } else { this.rmFiles(filePath, regexp) } } } }, // @done cleanOldVersionFilesIfNeed (version) { let staticDir = this.staticDir let maxHistoryNum = this.maxHistoryNum let history = [] try { history = require(this.historyFile) } catch (e) { console.log(e) } // 加入最新的版本,老的的版本删除 history.push(version) // 如果历史版本数超过限制,则删除老的历史版本 let len = history.length if (len > maxHistoryNum) { let oldVersions = history.slice(0, len - maxHistoryNum) for (let i = 0; i < oldVersions.length; i++) { let ver = oldVersions[i] let reg = new RegExp(ver) this.rmFiles(staticDir, reg) } // 更新history文件 let newVersions = history.slice(len - maxHistoryNum) this.storeHistory(newVersions) } else { // 写入history文件 this.storeHistory(history) } }, // 入口 run (version) { this.creataHistoryIfNotExist() this.cleanOldVersionFilesIfNeed(version) } } 复制代码
# desc: 该脚本用于一键构建线上代码,并自动提交到远程git仓库 initContext(){ # 目标文件目录目录 source_dir=dist # 为app内嵌版本打包的参数 if [ $# -gt 0 ] && [ $1 = 'beta' ];then # 生产代码远程仓库地址 git_url=xx.git # 生产代码本地根目录 dest=".deploy/beta" # npm 的脚本名次 node_script=build_beta else # 生产代码远程仓库地址 git_url=xx.git # 生产代码本地根目录 dest=".deploy/pro" # npm 的脚本名次 node_script=build_pro fi } # 初始化git目录,pull最新代码 init(){ echo +++init start; if [ ! -d $dest ]; then git clone $git_url $dest fi # 记录现在的目录位置,最后要回来的 cur=`pwd` # 进入git目录 cd $dest # git checkout . git add . git stash # reset为线上最新版本,要先pull一下再reset。 git pull origin master git reset --hard origin/master # 然后再pull一下 git pull origin master # 回到原来的目录 cd $cur echo ---init end; } # 重置dist目录 resetDist(){ echo +++resetDist start rsync -a --delete --exclude='.git' $dest/. ./dist echo ---resetDist end } # 构建 build(){ echo +++build start npm run $node_script echo ---build end } # 检查是否成功 checkBuild(){ if [[ ! -f $source_dir/index.html || ! -d $source_dir/static ]]; then echo error else echo ok fi } # 复制代码到$dest目录 cpCode(){ echo +++cpCode start # 复制代码,所有文件包含隐藏文件 rsync -r --delete --exclude='.git' $source_dir/. $dest echo ---cpCode end } # 提交到远程git仓库 commit(){ echo +++commit start # 记录现在的目录位置,最后要回来的 cur=`pwd` # 进入git目录 cd $dest # 提交的字符串 commit_str="commited in `date '+%Y-%m-%d_%H:%M:%S'`" git add . git commit -am "${commit_str}" git push origin master # 回到原来的目录 cd $cur echo ---commit end } # 显示帮助信息 help(){ echo ./run.sh build "#"构建代码 echo ./run.sh init "#"初始化git仓库 echo ./run.sh commit "#"提交到git echo ./run.sh "#"执行全部任务 echo ./run.sh hello "#"hello echo ./run.sh test "#"test echo ./run.sh beta "#"一键构建和提交beta版本 # app内嵌版本 echo ----app内嵌版本-------- echo ./run.sh app "#"一键构建和提交app版本 echo ----帮助信息-------- echo ./run.sh help "#"帮助 } # 测试用的 test(){ echo "a test empty task" } # 入口 if [[ $# -lt 1 || $1 = 'app' || $1 = 'beta' || $1 = 'beta1' || $1 = 'beta2' ]]; then # 无参数则打pro包,否则打相应类型的包 if [ $# -lt 1 ];then type=pro else type=$1 fi echo ===\>准备构建${type}版 initContext $type && init && resetDist # 构建代码 buildRes=$(build) # 检查构建结果 echo -e "$buildRes" if [[ $buildRes =~ "ERROR" ]]; then echo "$(tput setaf 1)xxx\>build error,task abort$(tput sgr0)" else # 代码构建成功才继续。 checkRes=$(checkBuild) if [ $checkRes == "ok" ];then cpCode && commit echo "$(tput setaf 2)===\>task complete$(tput sgr0)" else echo "$(tput setaf 1)xxx\>build error,task abort$(tput sgr0)" fi fi elif [ $1 ]; then # 参数不是包类型的,当中函数处理 echo ===\>准备执行${1}函数 initContext beta func=$1 $func echo ===\>task complete fi 复制代码
history.js
写入版本号配图:
打包目标文件 dist
配图:
可以看到很多个版本文件吧~
今日份码字结束
今日份码字结束+1
('今日份码字结束').repeat('999')
4点半啦,我要去赶高铁了,参加表哥婚礼去~
结尾
差不多了,可以结束了,下一篇写下 webpack4
的东西吧,毕竟打包优化靠着玩意儿~,够硬
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 【vue-cli3升级】老项目提速50%(一)
- YOLOv3 精度再次提高 4.3%,训练提速 40%!PaddleDetection 全面升级
- 主题模型工具包 Gensim 3.4.0 发布,提速再提速
- 亚洲数字身份识别技术发展提速
- 优化总结:有哪些 APP 启动提速方法?
- 部署Envoy代理来为Monzo提速
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
函数式算法设计珠玑
Richard Bird / 苏统华、孙芳媛、郝文超、徐琴 / 机械工业出版社 / 2017-4-1 / 69.00
本书采用完全崭新的方式介绍算法设计。全书由30个珠玑构成,每个珠玑单独列为一章,用于解决一个特定编程问题。这些问题的出处五花八门,有的来自游戏或拼图,有的是有趣的组合任务,还有的是散落于数据压缩及字串匹配等领域的更为熟悉的算法。每个珠玑以使用函数式编程语言Haskell对问题进行描述作为开始,每个解答均是诉诸于函数式编程法则从问题表述中计算得到。本书适用于那些喜欢学习算法设计思想的函数式编程人员、......一起来看看 《函数式算法设计珠玑》 这本书的介绍吧!
正则表达式在线测试
正则表达式在线测试
RGB HSV 转换
RGB HSV 互转工具