先描述一個場景:當(dāng)我們在本地開發(fā)一個移動端的功能時修档,有時會面臨一個困境若专,即某些原生功能瀏覽器無法給予我們響應(yīng)脖含,必須要將代碼置身于真機環(huán)境豁翎,比如微信或自開發(fā)的app
等。這就需要頻繁的進行代碼迭代隅忿,以進行自測心剥。這樣導(dǎo)致一個煩惱,前端要一直打包還要丟到服務(wù)器上去背桐。
在線應(yīng)用比離線應(yīng)用要好一點的就在于不用進行版本更新优烧,只需要要清掉手機緩存,離線應(yīng)用要不斷的建立版本链峭,上傳代碼畦娄,然后再打開手機測試,有時候只是加一句 console
就得進行這些重復(fù)操作弊仪,挺煩心的熙卡。因為最近在搞vue-cli
的工具,就想著如何借助于vue-cli3來開發(fā)一個內(nèi)建命令幫助我們減輕負擔(dān)励饵,花了一天實現(xiàn)了驳癌,因此寫篇文章來總結(jié)一下。
首先因為研發(fā)的緣故役听,我這邊是基于我們開發(fā)的cli
去做的內(nèi)建命令颓鲜,所有基于這個cli
的項目都能使用表窘,文章最后會講一下如何在自己的項目里增加自定義命令而不是依賴我們的cli
,直接基于vue-cli3
即可甜滨。
我們先來看下官方對
service
插件的定義乐严,vue-cli-service
命令,如果是不了解vue-cli3
的人衣摩,估計乍看也想不起來這是什么東西昂验。當(dāng)我們打開項目下package.json
時,應(yīng)該會在script
對象里看到類似vue-cli-service serve
或vue-cli-service build
等命令昭娩,這里的serve/build
就是一個service
插件凛篙,是vue-cli-service
自帶的。我們要做的就是自定義一個service
從而可以在package.json
使用這個命令做一些騷操作栏渺。該怎么自建命令呢呛梆? 我們先找個官網(wǎng)的例子。
module.exports = (api, options) => {
api.registerCommand('build', (args) => {
// ...
})
}
vue-cli-service
的build
命令就是這么實現(xiàn)的(刪去了具體代碼)磕诊,api
是PluginAPI
實例填物,有一系列的方法,如根據(jù)不同環(huán)境修改webpack
鏈?zhǔn)脚渲玫撒眨@里只調(diào)用了api.registerCommand
這個方法向vue-cli-service
內(nèi)部注入額外的命令滞磺,serve/build
都是這樣實現(xiàn)的,我們現(xiàn)在來模仿著寫一下莱褒。
module.exports = (api, options) => {
api.registerCommand('uploadApp', {
description: 'upload local app code to manangement PC',
usage: 'vue-cli-service uploadApp [options]',
options: {
'--mode': 'specify env mode (default: development)'
},
}, async function uploadApp(args) {
})
}
和例子有些不一樣击困,多了個描述對象,問題不大广凸,重點還是在后面的這個異步函數(shù)uploadApp
阅茶。解釋下args
是什么呢,args
最大的作用就是將執(zhí)行vue-cli-service uploadApp
命令時的多余傳參從這里暴露給函數(shù)谅海。比如 vue-cli-service uploadApp --key dist
脸哀,args
對象里就包含了key:'dist'
鍵值對,可以直接用args.key
拿到具體的值扭吁,這個也是后面的關(guān)鍵之一撞蜂。
先來理一理思緒,目的:要實現(xiàn)一個本地命令即完成上傳本地zip到服務(wù)器的操作 ? 命令注冊已實現(xiàn)了侥袜,那么問題有以下幾點:
- 1.如何獲取要上傳的文件包名
- 2.如何上傳文件或者簡單點說如何進行接口調(diào)用
- 3.上傳的配置項如何傳遞
帶著這些問題蝌诡,我們一點點來解決。首先我們是基于cli
去做的系馆,我們的build
輸出默認都是輸出到dist
目錄下送漠,所以如果用戶能傳遞文件名,我們就能獲取到對應(yīng)的zip
包在dist
目錄下由蘑。之前說的args
讓我可以解決這個問題闽寡,在命令上顯示的傳遞一個 --key
這個key對應(yīng)的值就是文件名代兵,得到這個完整dist/${key}.zip
文件路徑名,我們就能通過fs
模塊獲取到文件流爷狈,剩下的就是通過FormData
上傳到服務(wù)器上去了植影。三步走的戰(zhàn)略還剩最后一步,如何配置上傳的配置項呢涎永?一個項目目錄下可能是有多個app
的思币,每個的配置肯定是不一樣的,這也是此次命令設(shè)計沒有考慮交互的原因羡微,本身就是為了減輕負擔(dān)而做的工具谷饿,如果使用還要不斷的輸入配置那也挺煩的。
回到第一張圖妈倔,官網(wǎng)對service
插件的介紹最后一句提到了我們可以在vue.config.js
里配置項目本地的選項來傳遞給插件博投。
module.exports = {
publicPath: "",
// 不需要生產(chǎn)環(huán)境的 source map,將其設(shè)置為 false 以加速生產(chǎn)環(huán)境構(gòu)建
productionSourceMap: false,
devServer: {
open: true,
port: "9595"
},
pluginOptions: {
_base: {
tenantId: null,
baseUrl: null,
name: null,
password: null,
},
dist: {
menuId: null,
}
}
};
這里我加了個pluginOptions
對象盯蝴,在注冊uploadApp
命令之前傳入了api
和options
兩個參數(shù)毅哗,options.pluginOptions
就能拿到script
命令所傳入的所有數(shù)據(jù)。當(dāng)然還有更簡單的捧挺,直接放在.env
文件里也是沒問題的虑绵,我們也能通過process.env
拿到。只是那里配置對象數(shù)據(jù)不太友好闽烙,而且我覺得放在pluginOptions
里 比較容易理解和使用翅睛。pluginOptions
下_base
我是希望定義一下標(biāo)準(zhǔn)接口所需要的固定數(shù)據(jù),如dist
就是和--key
一樣傳遞的文件名黑竞,也是對應(yīng)的module
模塊名宏所,這樣我們前期所需的安排都設(shè)計好了,可以愉快的擼碼了摊溶。
主要都是些業(yè)務(wù)代碼,對node
有了解的比較容易接受充石。實現(xiàn)邏輯就是如下圖莫换,通過用戶名和密碼獲取到 token
令牌,然后查詢對應(yīng)的menuId
獲取最新的version
骤铃,然后進行自增拉岁,獲取zip
包上傳到服務(wù)器得到地址,然后調(diào)接口實現(xiàn)版本發(fā)布惰爬。
const axios = require('axios');
const chalk = require('chalk')
const JSEncrypt = require('node-jsencrypt')
const FormData = require('form-data');
const fs = require('fs');
const ora = require('ora')
const spinner = ora()
let version = '0.0.0'
let token = ''
let zip = ''
let tenantId = ''
let menuId = ''
let fileName = ''
let baseUrl = ''
let name = ''
let pass = ''
module.exports = (api, options) => {
api.registerCommand('uploadApp', {
description: 'upload local app code to manangement PC',
usage: 'vue-cli-service uploadApp [options]',
options: {
'--mode': 'specify env mode (default: development)'
},
}, async function uploadApp(args) {
// 獲取傳遞過來的key即文件module名
fileName = args.key
// 獲取base信息
if (options.pluginOptions._base) {
tenantId = options.pluginOptions._base.tenantId
baseUrl = options.pluginOptions._base.baseUrl
name = options.pluginOptions._base.name
pass = options.pluginOptions._base.password
}
Object.keys(options.pluginOptions).forEach(item => {
if (item === fileName) {
menuId = options.pluginOptions[item].menuId
}
})
if (baseUrl) {
spinner.start(['uploading'])
// 不用用戶做任何操作
const tokenData = await getToken();
// 賦值token
token = tokenData.access_token;
const versionData = await getVersion();
// 列表不為空 則版本自增
if (versionData.content.length) {
let versionNow = versionData.content[0].menuVersion;
let versionList = versionNow.split('.')
// 先轉(zhuǎn)數(shù)值型再加1
versionList[2] = (+versionList[2]) + 1
version = versionList.join('.')
}
console.log(chalk.green('新建的版本是:' + version))
// 上傳子應(yīng)用包到服務(wù)器獲取地址
zip = await uploadZip()
const res = await createVersion();
console.log(chalk.green(res.message))
spinner.succeed()
} else {
console.log(chalk.res('請按規(guī)則配置vue.config.js'))
}
})
};
const getToken = () => {
let form = new FormData();
return new Promise((resolve, reject) => {
axios.create({
headers: form.getHeaders()
}).post(url, form).then(res => {
resolve(res.data)
}).catch(error => {
reject()
});
})
}
const getVersion = () => {
}
const uploadZip = () => {
let form = new FormData();
form.append('file', fs.createReadStream(`dist/${fileName}.zip`))
let headers = form.getHeaders(); //這個不能少
headers.Authorization = `bearer ${token}`; //自己的headers屬性在這里追加
return new Promise((resolve, reject) => {
axios.create({
headers,
}).post(url, form).then(res => {
resolve(res.data)
}).catch(error => {
reject()
});
})
}
const createVersion = () => {
}
module.exports.defaultModes = {
build: 'development'
}
部分具體的接口調(diào)用部分沒有顯示喊暖,只將重點的代碼貼出來來。要強調(diào)一點的是在 node
中使用new FormData()
是會報錯FormDate is undefined
撕瞧,因為node
中確實沒有FormData
陵叽,要引入form-data
模塊來實現(xiàn)FormData
的功能狞尔,還有使用axios
上傳formData
數(shù)據(jù)需要設(shè)置header
為form.getHeader()
,自定義的headers
可以掛載在這個對象之后巩掺。測試了使用node
的http
和request
都不是很ok
偏序,最后還是選擇了前端比較熟悉的axios
,只是使用有點不一樣胖替,具體使用可以查看上面的uploadZip
方法研儒。這樣一個service
插件就實現(xiàn)了,這里實現(xiàn)的是離線應(yīng)用上傳到服務(wù)器上独令,后面會考慮下在線的應(yīng)用如何直接通過密鑰上傳到服務(wù)器上端朵。
到此,在自己的packge.json
里的script
下加上一句命令"uploadDist": "cross-env vue-cli-service uploadApp --key dist"
燃箭,使用yarn uploadDist
就可以直接將本地的zip
包直接上傳到服務(wù)器上并建立版本冲呢,再也不用重復(fù)的操作了。
最后來講一下遍膜,如何在標(biāo)準(zhǔn)的vue-cli 3
項目下建立自己的命令碗硬,命令文件和上面的代碼可以是一致的,取名vue-cli-plugin-uploadApp.js
, 放在項目根目錄瓢颅,然后在package.json
下注入如下代碼
"vuePlugins": {
"service": [
"./vue-cli-plugin-uploadApp"
]
}
這樣就同樣可以在自己的項目下使用這個vue-cli-service uploadApp
了恩尾。如果看過我上一篇文章手擼腳手架的人應(yīng)該對Commander
這個庫比較熟悉,我們也可以基于Commander
來做這樣一個命令文件挽懦,但是這個就和vue-cli
沒什么關(guān)系了翰意,只是單純的寫一個node
腳本,有興趣的人也可以去實現(xiàn)一下信柿。