咱們業務中能夠經過Vue-cli腳手架快速生成vue項目,一樣咱們也能夠開發一款cli腳手架用於快速生成咱們平常提煉出來的業務基礎模型/架構。本文將詳細講解腳手架如何開發,所涉及到的技術細節和坑以及各類第三方包的講解,確保即便是小白同窗也能夠照着作出來本身的cli。javascript
裝逼大法!提高逼格!!升職加薪!!!贏娶白富美!!!!你還在等什麼?快快開始吧~~~html
言歸正傳,首先思考一下咱們的腳手架要幫助咱們作什麼事情?好比,這裏咱們就實現一個vta-cli
,經過在終端運行一個vta create my-app
就能夠初始化咱們日出提煉出來的一套Vue+Ts+ElementUi
的RBAC系統的基礎項目架構。有了這個目標,咱們就能夠拆解要實現的步驟了:前端
vta
vta create my-app
命令運行後,檢查當前文件名的存在與否狀況👇下面咱們就一步一步講解具體實現過程,跟上隊別掉隊哈~~~vue
# 終端建立項目根文件夾並進入到根文件夾
mkdir vta-cli && cd vta-cli
# 建立bin和src文件夾
mkdir bin src
# bin下建立init.js做爲腳本的入口文件
cd bin && mkdir init.js
# 並在init.js中鍵入以下內容:
#!/usr/bin/env node
console.log('Hello,my bin!')
# 初始化npm的包管理文件, 根目錄下執行
# 該命令會詢問你不少配置參數,若是不想詢問直接在後面加-y參數便可
npm init
複製代碼
項目基本的目錄文件夾出來了,說下具體的目錄做用,bin文件夾用於存放咱們的命令入口文件,init.js做爲入口文件(命名隨你),src做爲咱們真正實現腳本命令邏輯的地方:java
package.json
文件,在裏面添加咱們的腳本命令。打開咱們的package.json
文件,在裏面添加bin
字段:{
"bin": {
"vta": "bin/init.js"
},
}
複製代碼
這就是咱們定義了vta這個能夠在終端運行的腳本命令,即運行vta
這個命令的時候,程序會去運行咱們配置的bin/init.js
這個腳本文件。其實,根據npm的機制,當install
一個包的時候,會自動去查詢其定義的bin命令,並把他添加到node_modules/.bin
文件中去,做爲shell的命令能夠去執行。所以當你的包安裝到局部的項目中,那麼其bin中的命令就是局部可運行的,安裝到全局中則變成了全局能夠運行的命令。node
說明一下,並非必定要是js文件,其實在linux系統中一切皆文件,是沒有後綴名的規定的,至少爲了讓「人」好識別而已。linux
重點強調一下:init.js文件的第一行,必定是第一行,咱們添加了#!/usr/bin/env node
代碼,是指定了咱們腳本的運行環境,和自定在咱們運行vta命令的時候添加了node命令做爲前綴,即實際運行的是node vta
。ios
# 終端運行命令(需在當前項目根目錄下)
npm link
複製代碼
注意,npm link是當咱們當前包link到本地的全局中,就比如如咱們安裝依賴時使用了-g
參數把一些包裝到了全局環境同樣,是用來方便咱們本地開發時測試的,他可讓咱們開發的時候自動熱更新。若是不清楚npm link的
小夥伴,能夠去npm官網查查npm link的
用法再繼續往下學習。git
可是,我想說的時候,不少小夥伴在這塊可能會踩坑:github
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,把業務通用的場景解決方案抽離處理,提高本身的開發效率吧!最後,我是大家的老朋友愣錘,歡迎👏👏點贊👍👍收藏💗💗哦~~~
點贊👍、收藏👋、分享防走丟哦!!!須要的時候能夠拿出來對着開發~~~
此處將留做後續更多和腳手架開發相關的優秀庫的展現地址,後續會繼續更新~~~