記得一開始接觸腳手架時,就以爲這是個很神奇的東西。不用複雜的配置,不用去查文檔,拿@vue/cli
爲例,直接一行vue create my-project
命令,按一下回車,ok。接下來就是愉快的等待時間,看着項目快速的自動生成出來,與以前一項一項手動配置相比,效率不是快了一星半點。這簡直就是解放生產力的工具。項目與項目之間有不少配置和結構都是能夠複用的,並且項目的類型是有限的,能夠枚舉出來的,因此再搭配上相應的模板的話,就能夠快速開始一個新項目了。vue
目前採用最多而且耦合度最低的一種思路是腳手架工具與模板項目分離,即模板項目放遠程倉庫或者什麼地方,腳手架工具將模板拉取到本地,再根據相關配置命令生成相應的項目。一句話就解釋完了,好像沒什麼難度。這一篇主要分析腳手架工具,至於模板就是另外一個話題了,下一篇《Re從零開始的路由模塊編寫》會講到。接下來看看這個工具是實現的細節。node
咱們最終使用時,應該要如同@vue/cli
同樣,能在命令行中全局使用。這就不得不提到npm
配置文件package.js
中的bin
字段了。git
//就像這樣去配置,myCli是自定義的命令名,'./bin/myCli.js'則是引導文件
{
"bin": {
"myCli": "./bin/myCli.js"
}
}
複製代碼
在npm install
時npm會分析這個字段github
node_modules
外,還會在npm安裝目錄的bin文件下建立一個軟鏈,鏈向node_modules/my-cli/bin/myCli.js
,此時在PATH
中也會加入相關的路徑映射,在使用myCli create myProject
時,至關於在執行node /npm安裝路徑/bin/myCli create myProject
。node_modules/.bin
下建立軟鏈,鏈向node_modules/my-cli/bin/myCli.js
,在npm run
時,會將相關路徑映射加入到PATH
中。這樣就能作到在命令行中使用這個全局命令了web
先來看看項目結構sql
結構並不複雜npm
sc.js
就是上面介紹的引導文件cli.js
將匹配到的命令分發到不一樣的處理文件(cmd
目錄下一堆增刪改查處理程序)format.js
工具函數templates.json
保存相關配置記錄這一步須要處理的功能點有幾個json
仔細一想,從命令行獲的命令和分步處理輸入的命令彷佛不是那麼容易完成,但問題不大。後端
站在巨人的肩膀上,在完成前3步以前,先介紹一個很關鍵的插件。prompts
這個包對命令行相關處理進行了很好的封裝,配置簡單,支持分步輸入。安全
// 簡單的配置便可得到相關參數
(async () => {
const preOption = [{
type: 'text',
name: 'project',
message: '項目名稱?',
validate: value => value ? true : '請輸入內容'
}, {
type: 'text',
name: 'url',
message: '項目倉庫地址?',
validate: value => value ? true : '請輸入內容'
}]
const preResponse = await prompts(preOption);
})()
複製代碼
// 還能設置命令提示信息
program
.version(packageInfo.version)
program
.command('init')
.description('初始化一個項目')
.alias('i')
.action(() => {
require('./cmd/init').init();
});
program
.command('add')
.description('新建一個項目')
.alias('a')
.action(() => {
require('./cmd/add').add();
});
複製代碼
這裏最核心的功能就是自動初始化項目了,除此以外還有對項目列表及配置的增刪改查,畢竟咱們的模板不止一個,須要維護的一個項目列表。下面看一下幾段核心的代碼。
(async () => {
const preOption = [{
type: 'text',
name: 'project',
message: '項目名稱?',
validate: value => value ? true : '請輸入內容'
}]
format.table(templates)
const preResponse = await prompts(preOption);
if (!Object.keys(preResponse).length) {
console.log(chalk.yellow('程序中斷'))
process.exit();
}
const {
project
} = preResponse
if (!templates[project] || !project) {
console.log(chalk.red('模板名不爲空或模板不存在'))
process.exit()
}
const {
url,
branch
} = templates[project]
spinner.start('正在初始化項目');
exec(`git clone ${url} ${project} && cd ${project} && git checkout ${branch}`, (err) => {
// 刪除 git 文件
exec('cd ' + project + ' && rm -rf .git', (err, out) => {
spinner.succeed('模板拉取成功')
process.exit()
});
});
})()
複製代碼
(async () => {
const option = [{
type: 'text',
name: 'project',
message: '項目名稱?',
validate: value => value ? true : '請輸入內容'
}, {
type: 'text',
name: 'url',
message: '項目倉庫地址?',
validate: value => value ? true : '請輸入內容'
}, {
type: 'text',
name: 'branch',
message: '項目分支?',
initial: 'master',
validate: value => value ? true : '請輸入內容'
}, {
type: 'text',
name: 'des',
message: '項目描述?',
validate: value => value ? true : '請輸入內容'
}]
const response = await prompts(option);
if (!Object.keys(response).length) {
console.log(chalk.yellow('程序中斷'))
process.exit();
}
const {
project
} = response
if (templates[project] || !project) {
console.log(chalk.red('模板名不爲空或模板已存在'))
process.exit()
}
templates[project] = response
fs.writeFile(__dirname + '/../../templates.json', JSON.stringify(templates), 'utf-8', (err) => {
console.log(chalk.green('添加成功'))
process.exit();
});
})();
複製代碼
(async () => {
const option = [{
type: 'text',
name: 'project',
message: '項目名稱?',
validate: value => value ? true : '請輸入內容'
}]
const response = await prompts(option);
if (!Object.keys(response).length) {
console.log(chalk.yellow('程序中斷'))
process.exit();
}
const {
project
} = response
if (!templates[project] || !project) {
console.log(chalk.red('模板名不爲空或模板不存在'))
process.exit()
}
if (project in templates) {
delete templates[project]
fs.writeFile(__dirname + '/../../templates.json', JSON.stringify(templates), 'utf-8', (err) => {
console.log(chalk.green('刪除成功'))
format.table(templates)
process.exit();
});
} else {
console.log(chalk.red('沒有該模板'))
process.exit();
}
})()
複製代碼
例如像更新功能就同上面的差很少,有些細節能夠查看完整代碼。固然還有不少功能沒有實現,這只是個簡易版本,幫助理解像相似@vue/cli
這樣的腳手架工具是怎麼作的。
不會吧,不會吧,2020年了竟然有人不會在npm上發佈項目,直接npm publish
就行了,什麼竟然報錯?詳情可看《Re從零開始的組件庫構建與發佈流程》
倉庫地址 https://github.com/GoldWorker/slucky-cli
其實這樣的工具仍是很簡單的,實現上借用prompts
這個包過程跳過了相對繁瑣的處理命令行的部分。固然若是想要像真正的@vue/cli
那樣的功能,只須要繼續添加相關的命令與處理過程便可。