vue-cli3 之自定義service

先描述一個場景:當(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即可甜滨。

官方定義.png

我們先來看下官方對service插件的定義乐严,vue-cli-service命令,如果是不了解vue-cli3的人衣摩,估計乍看也想不起來這是什么東西昂验。當(dāng)我們打開項目下package.json時,應(yīng)該會在script對象里看到類似vue-cli-service servevue-cli-service build等命令昭娩,這里的serve/build就是一個service插件凛篙,是vue-cli-service自帶的。我們要做的就是自定義一個service從而可以在package.json使用這個命令做一些騷操作栏渺。
該怎么自建命令呢呛梆? 我們先找個官網(wǎng)的例子。

module.exports = (api, options) => {
  api.registerCommand('build', (args) => {
    // ...
  })
}

vue-cli-servicebuild命令就是這么實現(xiàn)的(刪去了具體代碼)磕诊,apiPluginAPI實例填物,有一系列的方法,如根據(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命令之前傳入了apioptions兩個參數(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ā)布惰爬。

image.png
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è)置headerform.getHeader(),自定義的headers可以掛載在這個對象之后巩掺。測試了使用nodehttprequest都不是很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)一下信柿。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末冀偶,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子渔嚷,更是在濱河造成了極大的恐慌进鸠,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件形病,死亡現(xiàn)場離奇詭異客年,居然都是意外死亡,警方通過查閱死者的電腦和手機漠吻,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進店門量瓜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人途乃,你說我怎么就攤上這事绍傲。” “怎么了耍共?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵烫饼,是天一觀的道長猎塞。 經(jīng)常有香客問我,道長枫弟,這世上最難降的妖魔是什么邢享? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮淡诗,結(jié)果婚禮上骇塘,老公的妹妹穿的比我還像新娘。我一直安慰自己韩容,他們只是感情好款违,可當(dāng)我...
    茶點故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著群凶,像睡著了一般插爹。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上请梢,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天赠尾,我揣著相機與錄音,去河邊找鬼毅弧。 笑死气嫁,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的够坐。 我是一名探鬼主播寸宵,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼元咙!你這毒婦竟也來了梯影?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤庶香,失蹤者是張志新(化名)和其女友劉穎甲棍,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體赶掖,經(jīng)...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡救军,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了倘零。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡戳寸,死狀恐怖呈驶,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情疫鹊,我是刑警寧澤袖瞻,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布司致,位于F島的核電站,受9級特大地震影響聋迎,放射性物質(zhì)發(fā)生泄漏脂矫。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一霉晕、第九天 我趴在偏房一處隱蔽的房頂上張望庭再。 院中可真熱鬧,春花似錦牺堰、人聲如沸拄轻。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽恨搓。三九已至,卻和暖如春筏养,著一層夾襖步出監(jiān)牢的瞬間斧抱,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工渐溶, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留辉浦,地道東北人。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓掌猛,卻偏偏與公主長得像盏浙,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子荔茬,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,779評論 2 354