【vue-cli3升級】老專案提速50%(二)

王玲波_柏林發表於2019-01-17

抽點時間碼字…

續上一篇《【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.envbuild 命令的時候不受影響的,樓主這個坑踩的很蛋疼…

# .env.devVUE_APP_API_ENV=devVUE_APP_BASE_API=xxx...複製程式碼

VUE_APP_ 開頭命名的變數VUE_APP_*就可以在專案中愉快的使用 process.env.VUE_APP_* 訪問了。

# .env.betaNODE_ENV=productionVUE_APP_API_ENV=betaVUE_APP_BASE_API=xxx...複製程式碼
# .env.prodNODE_ENV=productionVUE_APP_API_ENV=proVUE_APP_BASE_API=xxx...複製程式碼

mock整合

API文件還是頭疼啊,業務高速發展,文件缺失嚴重,文件依然 showdoc 書寫,不吐槽了…

本打算採取本地mock的形式,想想算了,需要編寫一堆檔案不說,隨著版本迭代,mock檔案會越來越大…

最終考慮實際情況,採用 easy-mock 的形式

easy-mock官網新建團隊專案:登入 =>
我的專案(團隊專案)=>
建立團隊 =>
建立專案

【vue-cli3升級】老專案提速50%(二)

建立完成後,點選進入專案:

【vue-cli3升級】老專案提速50%(二)

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 = trueconst 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版本控制檔案

run.sh:拉取遠端程式碼 =>
本地打包 =>
刪除版本控制外的歷史檔案 =>
推送遠端

為什麼要做版本控制?為了使用者無感知,為了隨時釋出,為了不加班(隨時釋出了還加什麼班?很實在,哈哈)…

釋出過程中,當使用者在我們的產品內溜達的時候不出錯~(沒有版本控制前老闆好幾次白屏了哦…)

build --no-clean 模式不清除dist資料夾,history.js 儲存5個版本號,build.sh控制遠端倉庫5個版本

上程式碼吧:

// task.jslet fs = require('fs')let path = require('path')let endOfLine = require('os').EOLmodule.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)
}
}複製程式碼
# run.sh# 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 fielif [ $1 ];
then # 引數不是包型別的,當中函式處理 echo ===\>
準備執行${1
}
函式 initContext beta func=$1 $func echo ===\>
task completefi複製程式碼

history.js 寫入版本號配圖:

【vue-cli3升級】老專案提速50%(二)

打包目標檔案 dist 配圖:

【vue-cli3升級】老專案提速50%(二)

可以看到很多個版本檔案吧~

今日份碼字結束

今日份碼字結束+1

(‘今日份碼字結束’).repeat(‘999’)

4點半啦,我要去趕高鐵了,參加表哥婚禮去~

結尾

差不多了,可以結束了,下一篇寫下 webpack4 的東西吧,畢竟打包優化靠著玩意兒~,夠硬

來源:https://juejin.im/post/5c403bcaf265da61587765c9

相關文章