咱們業務中能夠經過Vue-cli腳手架快速生成vue項目,一樣咱們也能夠開發一款cli腳手架用於快速生成咱們平常提煉出來的業務基礎模型/架構。本文將詳細講解腳手架如何開發,所涉及到的技術細節和坑以及各類第三方包的講解,確保即便是小白同窗也能夠照着作出來本身的cli。html
裝逼大法!提高逼格!!升職加薪!!!贏娶白富美!!!!你還在等什麼?快快開始吧~~~前端
言歸正傳,首先思考一下咱們的腳手架要幫助咱們作什麼事情?好比,這裏咱們就實現一個vta-cli
,經過在終端運行一個vta create my-app
就能夠初始化咱們日出提煉出來的一套Vue+Ts+ElementUi
的RBAC系統的基礎項目架構。有了這個目標,咱們就能夠拆解要實現的步驟了:vue
vta
vta create my-app
命令運行後,檢查當前文件名的存在與否狀況👇下面咱們就一步一步講解具體實現過程,跟上隊別掉隊哈~~~node
# 終端建立項目根文件夾並進入到根文件夾 mkdir vta-cli && cd vta-cli # 建立bin和src文件夾 mkdir bin src # bin下建立init.js做爲腳本的入口文件 cd bin && touch init.js # 並在init.js中鍵入以下內容: #!/usr/bin/env node console.log('Hello,my bin!') # 初始化npm的包管理文件, 根目錄下執行 # 該命令會詢問你不少配置參數,若是不想詢問直接在後面加-y參數便可 npm init 複製代碼
項目基本的目錄文件夾出來了,說下具體的目錄做用,bin文件夾用於存放咱們的命令入口文件,init.js做爲入口文件(命名隨你),src做爲咱們真正實現腳本命令邏輯的地方:linux
package.json
文件,在裏面添加咱們的腳本命令。打開咱們的package.json
文件,在裏面添加bin
字段:{ "bin": { "vta": "bin/init.js" }, } 複製代碼
這就是咱們定義了vta這個能夠在終端運行的腳本命令,即運行vta
這個命令的時候,程序會去運行咱們配置的bin/init.js
這個腳本文件。其實,根據npm的機制,當install
一個包的時候,會自動去查詢其定義的bin命令,並把他添加到node_modules/.bin
文件中去,做爲shell的命令能夠去執行。所以當你的包安裝到局部的項目中,那麼其bin中的命令就是局部可運行的,安裝到全局中則變成了全局能夠運行的命令。ios
說明一下,並非必定要是js文件,其實在linux系統中一切皆文件,是沒有後綴名的規定的,至少爲了讓「人」好識別而已。git
重點強調一下:init.js文件的第一行,必定是第一行,咱們添加了#!/usr/bin/env node
代碼,是指定了咱們腳本的運行環境,和自定在咱們運行vta命令的時候添加了node命令做爲前綴,即實際運行的是node vta
。github
# 終端運行命令(需在當前項目根目錄下) npm link 複製代碼
注意,npm link是當咱們當前包link到本地的全局中,就比如如咱們安裝依賴時使用了-g
參數把一些包裝到了全局環境同樣,是用來方便咱們本地開發時測試的,他可讓咱們開發的時候自動熱更新。若是不清楚npm link的
小夥伴,能夠去npm官網查查npm link的
用法再繼續往下學習。vue-cli
可是,我想說的時候,不少小夥伴在這塊可能會踩坑:typescript
package.json
的配置中把node_modules
等無關的文件夾去掉(或者指定咱們須要的),也能夠經過.gitignore
等配置文件忽略掉也能夠,或者.npmrc
等。在哪裏設置均可以,由於npm配置取值是有一套前後順序的規則,有興趣的話能夠移步npm文檔查閱。這裏演示一下如何在package.json
文件的配置:{ "files": [ "./bin", "./src" ], } 複製代碼
咱們經過在package.json文件中指定files文件夾目錄,即告訴npm咱們實際應該包含的真正文件有哪些,好比咱們只須要bin和src
文件夾,一些默認的文件像package.json
啊,其餘的一些基礎配置文件啊,即便你不添加,也會被默認包含進來的。這也是當咱們把這個包發佈到npm
所須要配置的,也就是須要哪些文件發佈到npm倉庫上。
注意,也能夠經過排除的字段,exclude
。可是,不少時候指定咱們須要哪些文件,可能更爲方便哦!再強調一遍,node_modules必定要排除掉,否則npm link會巨慢並且會失敗的機率大,當心踩坑~~
# 終端運行 vta # 那麼腳本執行後,便會看到終端的輸出 # 說明腳本執行成功了 複製代碼
再次強調,init.js首行必定要添加沙棒,以下:
#!/usr/bin/env node console.log('運行測試') 複製代碼
commander.js
是nodejs命令行界面的一個完整解決方案。能夠幫助咱們定義各類命令行命令/參數等等。好比咱們想定義create命令啊,或者-v做爲版本號查詢的參數等等。那就先看下怎麼使用吧:
cnpm install commander -S
複製代碼
// 在init.js中引入 const { Command } = require('commander'); // 導入當前根目錄下的package.json文件, // 爲了獲取對應的字段值,好比版本version const package = require('../package'); // 初始化 const program = new Command(); 複製代碼
// // 如此, program .version(package.version, '-v, --version', 'display version for vta-cli') .usage('<command> [options]'); // 複製代碼
經過調用version方法,定義命令行命令版本的功能,咱們即可以在命令行輸入vta -v
獲得當前的版本信息。
調用usage方法,是定義的咱們的輔助命令(help)的提示的文案標題,相似於定義table的表頭的感受,以下圖,當咱們輸入vta -h時,就是定義的藍色框框內展現的部分:
注意,這裏version方法
的第三個參數,是咱們定義的說明內容,如上圖的紅色部分。help
默認也是這個值
/** * 定義vta的參數 */ program .option('-y, --yes', 'run default action') .option('-f, --force', 'force all the question'); /** * 能夠經過判斷,當用戶輸入了對應的這些參數時, * 咱們能夠作一些操做: */ if (program.force) { // do something.. } 複製代碼
經過option方法,定義咱們的命令行參數,比如vta -f
,等同於vta --force
。注意,第一個參數是定義命令行參數,包含一個短的名稱(1個字符)和一個長的名稱,不能多了。第二個參數,就是定義的說明內容。注意,判斷部分的代碼,只能使用長的名稱,不能判斷短的,例如program.f
。
建立子命令是重要的一部分,好比咱們使用vue create my-app
建立項目時, create
就是vue命令的子命令,my-app
是命令參數。這裏咱們也定義一個子命令:
/** * 調用command方法,建立一個create命令, * 同時create命令後面必須跟一個命令參數 * 若是你在終端運行vta create不加名稱,則會報錯提示用戶 */ program.command('create <name>') // 定義該命令的描述 .description('create a vta template project') // 爲該命令指定一些參數 // 最後咱們均可以解析到這些參數,而後根據參數實現對應邏輯 .option('-f, --force', '忽略文件夾檢查,若是已存在則直接覆蓋') /** * 最後定義咱們的實現邏輯 * source表示當前定義的name參數 * destination則是終端的cmd對象,能夠從中解析到咱們須要的內容 */ .action((source, destination) => { /** * 好比咱們這裏把實現邏輯放在了另外一個文件中去實現, * 方便代碼解耦, * 由於destination參數比較雜亂,其實仍是在此處先解析該參數對應再傳入使用吧 * 能夠定義一個解析的工具函數 */ new CreateCommand(source, destination) }); 複製代碼
如圖,看下destination
對象究竟是什麼?仍是滿多的內容。咱們須要關注的就是紅色框框的這部分,這裏就是咱們定義的該命令的全部參數的列表,咱們變量該列表,取圖中藍色的部分的值,解決--
後面的部分,而後做爲key到整個cmd對象中取匹配,其值就是用戶輸入的參數的值。
好比,可能會定義一個解析的工具函數:
/** * parseCmdParams * @description 解析用戶輸入的參數 * @param {} cmd Cammander.action解析出的cmd對象 * @returns { Object } 返回一個用戶參數的鍵值對象 */ exports.parseCmdParams = (cmd) => { if (!cmd) return {} const resOps = {} cmd.options.forEach(option => { const key = option.long.replace(/^--/, ''); if (cmd[key] && !isFunction(cmd[key])) { resOps[key] = cmd[key] } }) return resOps } 複製代碼
上述的解析方法實現方式和咱們vue-cli的差很少。
/** * 切記parse方法的調用,必定要program.parse()方式, * 而不是直接在上面的鏈式調用以後直接xxx.parse()調用, * 否則就會做爲當前command的parse去處理了,從而help命令等都與你的預期不符合了 */ try { program.parse(process.argv); } catch (error) { console.log('err: ', error) } 複製代碼
最後必定要解析,不解析是拿不到對應參數program.parse(process.argv)
,也就是不會執行對應的命令等行爲的。切記!切記!切記!!!更詳細的命令請查詢commander文檔。
從上面的步驟咱們能夠看出,咱們已經定義好了vta create <name>
的命令了,即當咱們運行vta create my-app
命令的時候,就會初始化咱們定義的CreateCommand
類了。下面咱們看看入如何實現這個邏輯:咱們首先建立src/command/CreateCommand.js
這個文件來實現咱們的邏輯:
/** * class 項目建立命令 * * @description * @param {} source 用戶提供的文件夾名稱 * @param {} destination 用戶輸入的create命令的參數 */ class Creator { constructor(source, destination, ops = {}) { this.source = source this.cmdParams = parseCmdParams(destination) this.RepoMaps = Object.assign({ repo: RepoPath, // 配置文件中放置的遠程地址常量 temp: path.join(__dirname, '../../__temp__'), target: this.genTargetPath(this.source) }, ops); this.gitUser = {}; this.spinner = ora(); this.init(); } // 其餘實例方法 // ... } // 最終導出這個class module.exports = Creator; 複製代碼
咱們看下這個構造函數咱們用來作了什麼事情,首先就是把實例化時傳進來的參數賦值給this對象,供後面其餘實例方法中去使用。而後定義了RepoMaps
屬性設置咱們的一些基礎參數,像項目模板的地址repo
、咱們本地cli項目內部臨時存放的項目模板的地址temp
、和最終咱們須要把項目安裝到的目標地址taregt
。由於項目最終會安裝到終端運行的地址下的位置,而你的腳手架包是被安裝在其餘地址的。
而後定義了gitUser用於存放用戶的git信息,後面會經過自動執行命令獲取相關的信息,而後最後咱們會把信息塞到package.json文件中。
this.spinner = ora();
就是實例化一個菊花圖,當咱們在執行命令的時候能夠調用this.spinner方法進行菊花轉呀轉!
下面咱們來實現這個init初始化的方法吧:
// 初始化函數 async init() { try { // 檢查目標路徑文件是否正確 await this.checkFolderExist(); // 拉取git上的vue+ts+ele的項目模板 // 存放在臨時文件夾中 await this.downloadRepo(); // 把下載下來的資源文件,拷貝到目標文件夾 await this.copyRepoFiles(); // 根據用戶git信息等,修改項目模板中package.json的一些信息 await this.updatePkgFile(); // 對咱們的項目進行git初始化 await this.initGit(); // 最後安裝依賴、啓動項目等! await this.runApp(); } catch (error) { console.log('') log.error(error); exit(1) } finally { this.spinner.stop(); } } 複製代碼
從上面代碼註釋能夠看到,咱們的init方法,就是把一系列操做一次調用執行便可。最後先看一下配置文件吧:
exports.InquirerConfig = { // 文件夾已存在的名稱的詢問參數 folderExist: [{ type: 'list', name: 'recover', message: '當前文件夾已存在,請選擇操做:', choices: [ { name: '建立一個新的文件夾', value: 'newFolder' }, { name: '覆蓋', value: 'cover' }, { name: '退出', value: 'exit' }, ] }], // 重命名的詢問參數 rename: [{ name: 'inputNewName', type: 'input', message: '請輸入新的項目名稱: ' }] } // 遠程Repo地址 // 你們開發階段,若是沒有本身的項目,能夠先調用個人這個地址練習 // 也能夠隨便一個地址練習均可以 exports.RepoPath = 'github:chinaBerg/vue-typescript-admin' 複製代碼
後面咱們將看看這一系列方法該如何實現?
首先介紹一下咱們的小菊花吧!咱們在執行各類操做的時候,好比拉模板數據等等,都是會有必定等待實際的,那麼這個等待過程,咱們能夠在終端有個小菊花轉轉轉,這樣 會給用戶更好的體驗,讓用戶知道當前腳本在執行加載,如圖(最左側有個小菊花在轉轉轉~~~):
ora就是這一款終端使用的菊花圖工具,下面看看如何使用吧!
cnpm install ora -S
複製代碼
const ora = require('ora'); // ora參數建立spinner文字內容 // 也能夠傳遞一個對象,設置spinner的週期、顏色等 // 調用start方法啓動,最終返回一個實例 const spinner = ora('Loading start') // 開啓菊花轉轉 spinner.start(); // 中止 spinner.stop() // 設置文案,後者菊花的color spinner.text = '正在安裝項目依賴文件,請稍後...'; spinner.color = 'green'; // 顯示轉成功的狀態 spinner.succeed('package.json更新完成'); 複製代碼
注意,文案的顏色,仍是得靠chalk輔助。後面會介紹chalk。上個圖片演示一下實際的運用:
更多詳細的用戶請查閱ora文檔。
chalk是一款可讓咱們的控制檯打印出各類顏色/背景的內容的工具,由此咱們能夠鮮明的區分各類提示內容,以下圖(就問你騷不騷???):
# 終端運行 cnpm i chalk -S 複製代碼
const chalk = require('chalk'); // 好比,這裏定義一個log對象 exports.log = { warning(msg = '') { console.warning(chalk.yellow(`${msg}`)); }, error(msg = '') { console.error(chalk.red(`${msg}`)); }, success(msg = '') { console.log(chalk.green(`${msg}`)); } } 複製代碼
好比,上面咱們封裝了最簡單的log方法,用於打印各類類型的信息時展現帶顏色的內容。還有一點,咱們說一下上面提到了的如何配合ora使用吧:
const chalk = require('chalk'); const ora = require('ora'); const spinner = ora('Loading start') // 開啓菊花轉轉 spinner.start(chalk.yellow('打印一個yellow色的文字')); 複製代碼
用法比較簡單,很少說了,更多用法仍是查閱文檔吧!
在詳細說明各個步驟實現的方式以前,咱們先說一下在cli中使用的文件操做的庫。node自己有fs操做,那麼咱們爲何還要引入fs-extra庫呢?是由於他徹底能夠用來取代fs的庫,省去了mkdirp``rimraf``ncp
等庫等安裝引入。用於拷貝、讀取、刪除等文件操做,並且提供了更多的功能等等。
cnpm install fs-extra
複製代碼
具體的api的方法,請查閱文檔fs-extra,後面講解各個步驟具體實現的時候也會說起到。
到了咱們運行vta create my-app
的時候了,這時候咱們就要考慮了,若是當前位置已經存在了同名的文件夾,那麼咱們確定是不能直接覆蓋的,而是要給用戶選擇,好比覆蓋、從新建立一個新的文件夾、退出,以下圖:
而後根據用戶的不一樣選擇做出對於的操做。下面咱們看這個文件夾檢查的具體實現:
checkFolderExist() { return new Promise(async (resolve, reject) => { const { target } = this.RepoMaps // 若是create附加了--force或-f參數,則直接執行覆蓋操做 if (this.cmdParams.force) { await fs.removeSync(target) return resolve() } try { // 不然進行文件夾檢查 const isTarget = await fs.pathExistsSync(target) if (!isTarget) return resolve() const { recover } = await inquirer.prompt(InquirerConfig.folderExist); if (recover === 'cover') { await fs.removeSync(target); return resolve(); } else if (recover === 'newFolder') { const { inputNewName } = await inquirer.prompt(InquirerConfig.rename); this.source = inputNewName; this.RepoMaps.target = this.genTargetPath(`./${inputNewName}`); return resolve(); } else { exit(1); } } catch (error) { log.error(`[vta]Error:${error}`) exit(1); } }) } 複製代碼
具體講解:
vta create my-app
的時候有沒有在後面加-f
的參數,若是添加了參數則是告訴咱們忽略檢查直接日後走,就是默認覆蓋的操做。經過調用fs.removeSync(target);
方法進行移除須要覆蓋的文件;await fs.pathExistsSync(target)
邏輯進行判斷當前文件夾名稱是否已經存在,若是不存在則resolve告訴程序執行文件夾檢查成功以後的程序。說到命令行交互,就要提到一個比較程序的庫inquirer,這是一個用於node環境下進行命令行交互的庫,支持單選、多選、用戶輸入、confirm詢問等等操做。
cnpm i inquirer -S
複製代碼
const inquirer = require('inquirer'); // 定義詢問的參數 // type表示詢問的類型,是單選、多選、確認等等 // name能夠理解爲當前交互的標識符,其值爲交互的結果 const InquirerConfig = { // 文件夾已存在的名稱的詢問參數 folderExist: [{ type: 'list', name: 'recover', message: '當前文件夾已存在,請選擇操做:', choices: [ { name: '覆蓋', value: 'cover' }, { name: '建立一個新的文件夾', value: 'newFolder' }, { name: '退出', value: 'exit' }, ] }], // 重命名的詢問參數 rename: [{ name: 'inputNewName', type: 'input', message: '請輸入新的項目名稱: ' }] } // 使用 // 經過當前標識符獲取交互的結果 // 好比,以下是一個單選的演示 const { recover } = await inquirer.prompt(InquirerConfig.folderExist); // 若是用戶選中的是「覆蓋」選項 if (recover === 'cover') { await fs.removeSync(target); return resolve(); // 若是用戶選中的是「建立新文件夾」選中 } else if (recover === 'newFolder') { // 再次建立一個用戶輸入的交互操做 // 讓用戶輸入新的文件夾名稱 const { inputNewName } = await inquirer.prompt(InquirerConfig.rename); this.RepoMaps.target = this.genTargetPath(`./${inputNewName}`); return resolve(); // 若是用戶選的是「退出」選項 } else { exit(1); } 複製代碼
在進行了文件夾監測完成以後,就應該是要下載咱們在git上的項目資源了。下載資源咱們是經過download-git-repo這個庫來實現的。
cnpm install download-git-repo -S
複製代碼
const path = require('path'); const downloadRepo = require('download-git-repo'); // 下載repo資源 downloadRepo() { // 菊花轉起來~ this.spinner.start('正在拉取項目模板...'); const { repo, temp } = this.RepoMaps return new Promise(async (resolve, reject) => { // 若是本地臨時文件夾存在,則先刪除臨時文件夾 await fs.removeSync(temp); /** * 第一個參數爲遠程倉庫地址,注意是類型:做者/庫 * 第二個參數爲下載到的本地地址, * 後面還能夠繼續加一個配置參數對象,最後一個是回調函數, */ download(repo, temp, async err => { if (err) return reject(err); // 菊花變成對勾 this.spinner.succeed('模版下載成功'); return resolve() }) }) } 複製代碼
主要邏輯就是把資源下載到咱們當前的臨時文件夾位置,若是臨時文件夾已經存在了那麼就先刪除臨時文件夾。
上面經過git上資源的下載,咱們是下載到了cli目錄內的臨時文件內,那麼咱們還須要把資源移動到咱們指定的位置,而且刪除沒必要要的資源。因此咱們這邊會在utlis裏面封裝一個公共函數,用於資源的拷貝:
/** * copyFiles 拷貝下載的repo資源 * @param { string } tempPath 待拷貝的資源路徑(絕對路徑) * @param { string } targetPath 資源放置路徑(絕對路徑) * @param { Array<string> } excludes 須要排除的資源名稱(會自動移除其全部子文件) */ exports.copyFiles = async (tempPath, targetPath, excludes = []) => { const removeFiles = ['./git', './changelogs'] // 資源拷貝 await fs.copySync(tempPath, targetPath) // 刪除額外的資源文件 if (excludes && excludes.length) { await Promise.all(excludes.map(file => async () => await fs.removeSync(path.resolve(targetPath, file)) )); } } 複製代碼
// 拷貝repo資源 async copyRepoFiles() { const { temp, target } = this.RepoMaps await copyFiles(temp, target, ['./git', './changelogs']); } 複製代碼
這裏,咱們移除了項目中自己含有的./git
、./changelogs
等文件,由於這些是該git項目須要的內容,而咱們實際是不須要的。
經過上面的操做,咱們已經把資源拷貝到咱們的目標地址了。那麼咱們還想自動把package.json中的name、version、author等字段更新成咱們須要的,應該怎麼作呢?
/** * updatePkgFile * @description 更新package.json文件 */ async updatePkgFile() { // 菊花轉起來! this.spinner.start('正在更新package.json...'); // 獲取當前的項目內的package.json文件的據對路徑 const pkgPath = path.resolve(this.RepoMaps.target, 'package.json'); // 定義須要移除的字段 // 這些字段自己只是git項目配置的內容,而咱們業務項目是不須要的 const unnecessaryKey = ['keywords', 'license', 'files'] // 調用方法獲取用戶的git信息 const { name = '', email = '' } = await getGitUser(); // 讀取package.json文件內容 const jsonData = fs.readJsonSync(pkgPath); // 移除不須要的字段 unnecessaryKey.forEach(key => delete jsonData[key]); // 合併咱們須要的信息 Object.assign(jsonData, { // 以初始化的項目名稱做爲name name: this.source, // author字段更新成咱們git上的name author: name && email ? `${name} ${email}` : '', // 設置非私有 provide: true, // 默認設置版本號1.0.0 version: "1.0.0" }); // 將更新後的package.json數據寫入到package.json文件中去 await fs.writeJsonSync(pkgPath, jsonData, { spaces: '\t' }); // 中止菊花 this.spinner.succeed('package.json更新完成!'); } 複製代碼
這一塊,上面代碼註釋已經寫的很是清晰了,看一遍應該就曉得過程邏輯了吧!!!至於其中獲取用戶git信息的邏輯,後面立刻會講解到!!!
如今咱們看下如何獲取git信息的,咱們定義了一個公共的方法getGitUser:
/** * getGitUser * @description 獲取git用戶信息 */ exports.getGitUser = () => { return new Promise(async (resolve) => { const user = {} try { const [name] = await runCmd('git config user.name') const [email] = await runCmd('git config user.email') // 移除結尾的換行符 if (name) user.name = name.replace(/\n/g, ''); if (email) user.email = `<${email || ''}>`.replace(/\n/g, '') } catch (error) { log.error('獲取用戶Git信息失敗') reject(error) } finally { resolve(user) } }); } 複製代碼
咱們都知道,在終端想查看用戶的git信息,那麼只須要鍵入git config user.name
便可,git config user.email
能夠獲取用戶的郵箱。那麼咱們一樣的在腳本中也執行這樣的命令不就能夠獲取到了嗎?
那麼剩下的就是如何在終端執行shell命令呢?
node是經過開啓一個子進程來執行腳本命令的,child_process說明是node提供的一個開啓子進程的方法。因而咱們能夠封裝一個方法用於執行子進程:
// node的child_process能夠開啓一個進程執行任務 const childProcess = require('child_process'); /** * runCmd * @description 運行cmd命令 * @param { string } 待運行的cmd命令 */ const runCmd = (cmd) => { return new Promise((resolve, reject) => { childProcess.exec(cmd, (err, ...arg) => { if (err) return reject(err) return resolve(...arg) }) }) } 複製代碼
因此上述獲取git詳情的操做其實就是調用的這個方法,讓node開啓一個子進程去運行咱們的git命令,而後將結果返回出來。
// 初始化git文件 async initGit() { // 菊花轉起來 this.spinner.start('正在初始化Git管理項目...'); // 調用子進程,運行cd xxx的命令進入到咱們目標文件目錄 await runCmd(`cd ${this.RepoMaps.target}`); // 調用process.chdir方法,把node進程的執行位置變動到目標目錄 // 這步很重要,否則會執行失敗(由於執行位置不對) process.chdir(this.RepoMaps.target); // 調用子進程執行git init命令,輔助咱們進行git初始化 await runCmd(`git init`); // 菊花停下來 this.spinner.succeed('Git初始化完成!'); } 複製代碼
這一塊也是調用的咱們封裝的方法執行git命令而已。可是必定要注意、process.chdir(this.RepoMaps.target);
變動進程的執行位置,若是變動目錄失敗會拋出異常(例如,若是指定的 directory 不存在)。這步操做很是重要,切記!!切記!!!詳細能夠查閱process.chdir說明
最後咱們就須要自動暗轉項目依賴了。本質也是調用子進程執行npm命令就能夠了。這裏咱們直接指定了使用淘寶的鏡像源,小夥伴們也能夠擴展,根據用戶的選擇指定npm、yarn和其餘鏡像源等等,盡情發揮吧!!!
// 安裝依賴 async runApp() { try { this.spinner.start('正在安裝項目依賴文件,請稍後...'); await runCmd(`npm install --registry=https://registry.npm.taobao.org`); await runCmd(`git add . && git commit -m"init: 初始化項目基本框架"`); this.spinner.succeed('依賴安裝完成!'); console.log('請運行以下命令啓動項目吧:\n'); log.success(` cd ${this.source}`); log.success(` npm run serve`); } catch (error) { console.log('項目安裝失敗,請運行以下命令手動安裝:\n'); log.success(` cd ${this.source}`); log.success(` npm run install`); } } 複製代碼
vta-cli腳手架git源碼地址,有興趣的小夥伴能夠查閱代碼實現。也可使用vta-cli快速初始化Vue+Ts+ElementUi
的RBAC後臺管理系統的基礎架構。安裝vta-cli
的方法:
# 安裝cli npm i vta-cli -g # 初始化項目 vta create my-app 複製代碼
vue-typescript-admin項目模板將會很快完善起來!!!也歡迎小夥伴們一塊兒貢獻代碼哦~~
關於cli開發的講解,到這就基本結束了!!!上面涵蓋了常見的技術實現方案和注意細節,項目能夠無痛上手的~~~有興趣的小夥伴們能夠照着封裝本身的cli,把業務通用的場景解決方案抽離處理,提高本身的開發效率吧!最後,我是大家的老朋友愣錘,歡迎👏👏點贊👍👍收藏💗💗哦~~~
點贊👍、收藏👋、分享防走丟哦!!!須要的時候能夠拿出來對着開發~~~
此處將留做後續更多和腳手架開發相關的優秀庫的展現地址,後續會繼續更新~~~
本文使用 mdnice 排版