學(xué)習(xí)目標(biāo)
用node寫實用的cli工具,是我們工程化的一個必經(jīng)之路蝗敢,本文也能激起大家學(xué)習(xí)node的興趣捷泞,
本文實現(xiàn)一個vue腳手架,這個腳手架的主要實現(xiàn)的功能就是:
- 自動克隆github項目
- 自動安裝依賴
- 自動npm run serve
- 自動打開瀏覽器
- 我們在view文件夾下面添加xxx.vue文件的時候寿谴,router.js自動生成
一锁右、創(chuàng)建工程
創(chuàng)建文件
安裝依賴
npm i commander download-git-repo ora handlebars figlet clear chalk open -s
編寫kkb.js
文件
#!/usr/bin/env node
//指定解釋器類型
console.log ('Hello My-Cli');
編寫package.json
文件,新增bin屬性讶泰,kkb
就是我們注冊的命令
{
"name": "vue-auto-router-cli",
"version": "1.0.0",
"description": "",
"main": "index.js",
"bin": {
"kkb": "./bin/kkb.js"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"chalk": "^4.1.0",
"clear": "^0.1.0",
"commander": "^7.2.0",
"download-git-repo": "^3.0.2",
"figlet": "^1.5.0",
"handlebars": "^4.7.7",
"open": "^8.0.5",
"ora": "^5.4.0"
}
}
將我們編寫的cli工具安裝到全局骡湖,(就跟npm install xxx -g
一樣),在項目根目錄下面運行以下命令
npm link
驗證
window
+r
打開一個新的終端峻厚,執(zhí)行kkb命令响蕴,是否配置成功,成功則輸出Hello My-Cli
二惠桃、編寫程序
1.使用commander
定制命令行
-
command
相當(dāng)于注冊了一個init命令name就是后面跟的參數(shù)浦夷,命令具體的操作在action
里面寫,commander會將命令后面的參數(shù)傳到這個action接收的這個函數(shù)參數(shù)里面
#!/usr/bin/env node
//指定解釋器類型
const program = require ('commander');
program.version (require ('../package.json').version); //指定版本號
program.command ('init <name>').description ('初始化項目中...').action (payload => {
console.log (payload);
}); //相當(dāng)于注冊一個命令
program.parse (process.argv); //process描述的是主進程 process.argv是命令后面的參數(shù)辜王,整個program是通過解析后面的參數(shù)來完成的
執(zhí)行命令kkb init project
輸出:project
2.打印一個歡迎界面
文件目錄
編輯kkb.js
#!/usr/bin/env node
//指定解釋器類型
const program = require ('commander');
program.version (require ('../package.json').version); //指定版本號
program
.command ('init <name>')
.description ('初始化項目...')
.action (require ('../lib/init.js')); //相當(dāng)于注冊一個命令
program.parse (process.argv); //process描述的是主進程 process.argv是命令后面的參數(shù)劈狐,整個program是通過解析后面的參數(shù)來完成的
新建init.js文件
const {promisify} = require ('util'); //promisify 將異步函數(shù)轉(zhuǎn)換為Promise類型的;
const figlet = promisify (require ('figlet')); //藝術(shù)字;
const chalk = require ('chalk'); //粉筆;
const clear = require ('clear'); //清屏;
const log = content => console.log (chalk.red (content)); //封裝一個log方法,用chalk染色;
module.exports = async name => {
clear ();首先清屏
const data = await figlet ('Welcome My Cli');
log (data);
};
運行kkb init name
3.實現(xiàn)克隆github項目的功能
- 使用
download-git-repo
這個包 - ora:進度條
新建download.js文件
const {promisify} = require ('util');
const ora = require ('ora'); //進度條
const download = promisify (require ('download-git-repo'));
module.exports = async (repo, name) => {
const process = ora ('下載中...' + name);
process.start ();
await download (repo, name);
process.succeed ();
};
編輯init.js文件
const {promisify} = require ('util'); //promisify 將異步函數(shù)轉(zhuǎn)換為Promise類型的;
const figlet = promisify (require ('figlet')); //藝術(shù)字;
const chalk = require ('chalk'); //粉筆;
const clear = require ('clear'); //清屏;
const log = content => console.log (chalk.red (content)); //封裝一個log方法呐馆,用chalk染色;
const download = require ('./download');
module.exports = async name => {
clear ();
const data = await figlet ('Welcome My Cli');
log (data);
log ('開始克隆項目');
await download ('github:su37josephxia/vue-template', name);
};
運行kkb init vue-template
命令肥缔,成功克隆項目
4.安裝依賴
項目成功克隆之后,接下來常規(guī)操作安裝依賴汹来,運行npm install
命令续膳,然后npm run serve
啟動,那么在nodejs里面我們?nèi)绾螌懩_本讓他自動執(zhí)行呢收班?
- 使用Promise封裝spawn方法坟岔,創(chuàng)建一個子進程讓他去執(zhí)行
npm install
這個命令。 - 因為子進程執(zhí)行摔桦,我們是看不見的社付,所以通過pipe(管道)對接到主進程,讓他執(zhí)行過程能在我們終端顯示出來邻耕,你也可以把
proc.stdout.pipe (process.stdout); proc.stderr.pipe (process.stderr);
這倆句注釋掉鸥咖,結(jié)果就是控制臺不會打印任何信息,但項目依然能啟動兄世。如此啼辣,顯而易見。 - 為什么要用
npm.cmd
碘饼,可以參考這篇文章熙兔。 - 關(guān)于
child_process
這個模塊你可以自己下去仔細學(xué)習(xí)下悲伶,這個包很重要,這篇文章不做贅述住涉。
const {promisify} = require ('util'); //promisify 將異步函數(shù)轉(zhuǎn)換為Promise類型的;
const figlet = promisify (require ('figlet')); //藝術(shù)字;
const chalk = require ('chalk'); //粉筆;
const clear = require ('clear'); //清屏;
const log = content => console.log (chalk.red (content)); //封裝一個log方法麸锉,用chalk染色;
const open = require ('open');
// const download = require ('./download');
// 封裝spawn方法
const spawn = async (...args) => {
const {spawn} = require ('child_process');
return new Promise (resolve => {
const proc = spawn (...args);
proc.stdout.pipe (process.stdout);
proc.stderr.pipe (process.stderr);
proc.on ('close', () => {
resolve ();
});
});
};
module.exports = async name => {
clear ();
const data = await figlet ('Welcome My Cli');
log (data);
// 克隆項目
// log ('開始克隆項目');
// await download ('github:su37josephxia/vue-template', name);//克隆github項目
// 安裝依賴
log ('開始安裝依賴');
await spawn ('npm.cmd', ['install'], {cwd: `./${name}`});
};
5.啟動項目并且打開瀏覽器
- open:使用系統(tǒng)瀏覽器打開一個網(wǎng)址;
const {promisify} = require ('util'); //promisify 將異步函數(shù)轉(zhuǎn)換為Promise類型的;
const figlet = promisify (require ('figlet')); //藝術(shù)字;
const chalk = require ('chalk'); //粉筆;
const clear = require ('clear'); //清屏;
const log = content => console.log (chalk.red (content)); //封裝一個log方法舆声,用chalk染色;
const open = require ('open');
// const download = require ('./download');
// 封裝spawn方法
const spawn = async (...args) => {
const {spawn} = require ('child_process');
return new Promise (resolve => {
const proc = spawn (...args);
proc.stdout.pipe (process.stdout);
proc.stderr.pipe (process.stderr);
proc.on ('close', () => {
resolve ();
});
});
};
module.exports = async name => {
clear ();
const data = await figlet ('Welcome My Cli');
log (data);
// 克隆項目
// log ('開始克隆項目');
// await download ('github:su37josephxia/vue-template', name);//克隆github項目
// 安裝依賴
// log ('開始安裝依賴');
// await spawn ('npm.cmd', ['install'], {cwd: `./${name}`});
// 打開瀏覽器安裝運行
open ('http://localhost:8080');
await spawn ('npm.cmd', ['run', 'serve'], {
cwd: `./${name}`,
});
};
6.自動生成router.js
和App.vue
中的router-link
我們?nèi)粘i_發(fā)項目的時候花沉,每次新加一個頁面都要編輯router.js和App.vue里面加一個鏈接,這樣的重復(fù)操作給我們帶來了很大的心智負擔(dān)媳握,所以我們接下來要實現(xiàn)的就是運行命令碱屁,自動生成。
看一下此時的目錄結(jié)構(gòu)
在我們克隆的項目vue-template
中新建一個tempalte文件夾蛾找,以及文件App.vue.hbs
和router.js.hbs
娩脾,這倆個文件將來要給handelbars這個包使用。
//App.vue.hbs
<template>
<div id="app">
<div id="nav">
<router-link to="/">Home</router-link>
{{#each list}}
| <router-link to="/{{name}}">{{name}}</router-link>
{{/each}}
</div>
<router-view/>
</div>
</template>
<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
//router.js.hbs文件
import Vue from 'vue'
import Router from 'vue-router'
import Home from './views/Home.vue'
Vue.use(Router)
export default new Router({
mode: 'history',
base: process.env.BASE_URL,
routes: [
{
path: '/',
name: 'home',
component: Home
},
{{#each list}}
{
path: '/{{name}}',
name: '{{name}}',
component: () => import('./views/{{file}}')
},
{{/each}}
]
})
lib
文件夾下面創(chuàng)建refresh.js
const fs = require ('fs');
const handlebar = require ('handlebars'); //
module.exports = async () => {
const list = fs.readdirSync ('./vue-template/src/views').map (v => ({
name: v.replace ('.vue', '').toLowerCase (),
file: v,
})); //文件集合
compile (
{list},
'./vue-template/src/router.js',
'./vue-template/template/router.js.hbs'
); //生成router.js
compile (
{list},
'./vue-template/src/App.vue',
'./vue-template/template/App.vue.hbs'
);//生成App.vue
function compile (meta, filePath, templatePath) {
if (fs.existsSync (templatePath)) {
const content = fs.readFileSync (templatePath).toString ();
const data = handlebar.compile (content) (meta);
fs.writeFileSync (filePath, data);
console.log (`${filePath}創(chuàng)建成功`);
}
}
};
編輯kkb.js打毛,新增一個命令kkb refresh
#!/usr/bin/env node
//指定解釋器類型
const program = require ('commander');
program.version (require ('../package.json').version); //指定版本號
// 初始化項目
program
.command ('init <name>')
.description ('初始化項目...')
.action (require ('../lib/init.js')); //相當(dāng)于注冊一個命令
// 刷新路由文件
program
.command ('refresh')
.description ('自動生成路由...')
.action (require ('../lib/refresh'));
program.parse (process.argv); //process描述的是主進程 process.argv是命令后面的參數(shù)柿赊,整個program是通過解析后面的參數(shù)來完成的
views下面新增一個文件,執(zhí)行kkb refresh
命令幻枉,我們會看到router.js
和App.vue
自動生成