从0构建一个cli
構建一個自己的cli
- 前言
- 腳手架作用是什么?
- cli中用到的基本js庫
- 接下來通過一個實例cli來介紹這一些庫
- 一,配置項目的運行環境
- 二,搭建命令行命令
- 三,封裝一個文件用來執行命令
- 四,創建其他命令
- 五,命令行 代碼美化
- 六,詢問用戶,并獲得用戶的選擇結果
- 七,整合用戶的選擇結果
- 八,通過axios發送請求,獲得想要的數據(非必需)
- 久,拉取遠端庫里面的項目模版
- 十,cli創建完成,發布
- commander介紹
- inquirer介紹
- 總結
前言
其實很早我就想了解vue-cli react-cli 的工作原理了,想了解本質是什么,作用又是什么,運用了啥。其實使用這一些工具不難,但是內心總會有疑惑,不理解其原理,總感覺自己有點慌,不知道大家有沒有這樣的感悟。正好最近在實習的公司接觸到他們自己搭建的腳手架和內部封裝的基礎項目模版,所以最近就自己看博客,邊理解,自己跟著寫了一個cli,功能不多,但是一個cli應該擁有的功能都實現了。自己想的有一些功能還沒有弄上去,所以這一篇文章先用來記錄所用到的功能庫和具體步驟,防止自己忘記,好記性不如爛筆頭。
腳手架作用是什么?
想必大家也有想過這個問題吧,說實話,沒去認真搜過幾篇文章,還真不知道cli是干什么的。vue項目文件是怎么來的了?為啥輸入命令我們就能創建一個自己命名的項目文件了?我們所敲的命令是怎么形成的了?總不會空穴來風吧,哈。
cli的作用:
其實cli腳手架就是從遠端庫(GitHub)里面拉取(請求)我們需要的模版文件,根據用戶在命令行中的選項,生成對應的項目,然后返回給用戶,通過相應的js庫文件,在本地創建項目。
cli中用到的基本js庫
1, commander 用來創建命令行命令
2, inquirer 用來創建命令行選擇,并獲得用戶的選擇結果
3, chalk 命令行美化庫
4, cross-spawn 跨系統運行
5, axios 請求文件庫
6, ora 控制臺動畫效果
7, download-git-repo git拉取文件
8, fs-extra fs模塊的擴展
接下來通過一個實例cli來介紹這一些庫
一,配置項目的運行環境
1,創建一個文件夾
2,npm init 實例化一個package.json 文件(這里確保自己電腦是安裝了node)
3,然后在package.json 文件中加入
4,在根目錄創建對應的文件bin>cli.js
5, cli文件中添加node環境運行的代碼(沒必要手動 node + 文件)
在文件中可以試著答應一個hello world,然后保存
6,使用npm link 鏈接全局,bin目錄下的文件為運行文件
npm link 的作用:作用
7,然后在命令行中運行 song 就可以看到運行結果。
二,搭建命令行命令
通過第三方庫進行創建
npm install commander --save因為運行的文件是cli,所以命令行命令的創建理所當然的在cli文件中編寫。
#! /usr/bin/env nodeconst program = require('commander')program// 定義命令和參數.command('create <app-name>').description('create a new project')// -f or --force 為強制創建,如果創建的目錄存在則直接覆蓋.option('-f, --force', 'overwrite target directory if it exist').action((name, options) => {// 打印執行結果console.log('name:',name,'options:',options)})program// 配置版本號信息.version(`v${require('../package.json').version}`).usage('<command> [option]')// 解析用戶執行命令傳入參數 program.parse(process.argv);在命令行運行song 進行查看
其實寫到這里,感覺創建命令不難,只是使用了特定的庫文件。
三,封裝一個文件用來執行命令
在根目錄創建一個lib 文件夾,新建一個create.js 文件,然后在cli.js全局執行文件中引入,在命令的action執行函數中執行這個create.js文件。
思考:
在創建項目目錄文件時,思考有沒有重名的文件夾已經存在,是創建過另外一個名字的文件夾,還是覆蓋,還是取消。
當 { force: true } 時,直接移除原來的目錄,直接創建
當 { force: false } 時 詢問用戶是否需要覆蓋
對文件進行操作,那么就得引入文件操作模塊fs-extra
fs-extra是node fs模塊的升級版本,這里用它來判別是否已經存在文件
node fs不支持promise,fs-extra支持
修改create.js 文件
// lib/create.jsconst path = require('path') const fs = require('fs-extra')module.exports = async function (name, options) {// 執行創建命令// 當前命令行選擇的目錄const cwd = process.cwd();// 需要創建的目錄地址const targetAir = path.join(cwd, name)// 目錄是否已經存在?if (fs.existsSync(targetAir)) {// 是否為強制創建?if (options.force) {await fs.remove(targetAir)} else {// TODO:詢問用戶是否確定要覆蓋}} }四,創建其他命令
和上面cli 文件中的配置一樣
五,命令行 代碼美化
npm install chalk --savechalk庫提供了很多對應顏色的API,可以通過自己想要的顏色,設置對應的API就行
chalk官網:
chalk
六,詢問用戶,并獲得用戶的選擇結果
這里在命令行創建選擇行為和獲得用戶的結果使用inquirer庫
npm install inquirer --save例子:
// lib/create.jsconst path = require('path')// fs-extra 是對 fs 模塊的擴展,支持 promise 語法 const fs = require('fs-extra') const inquirer = require('inquirer')module.exports = async function (name, options) {// 執行創建命令// 當前命令行選擇的目錄const cwd = process.cwd();// 需要創建的目錄地址const targetAir = path.join(cwd, name)// 目錄是否已經存在?if (fs.existsSync(targetAir)) {// 是否為強制創建?if (options.force) {await fs.remove(targetAir)} else {// 詢問用戶是否確定要覆蓋let { action } = await inquirer.prompt([{name: 'action',type: 'list',message: 'Target directory already exists Pick an action:',choices: [{name: 'Overwrite',value: 'overwrite'},{name: 'Cancel',value: false}]}])if (!action) {return;} else if (action === 'overwrite') {// 移除已存在的目錄console.log(`\r\nRemoving...`)await fs.remove(targetAir)}}} }現在運行song,就可以看到命令行出現原則了
七,整合用戶的選擇結果
通過上一步的inquirer執行后,可以獲得用戶的選擇結果,我們可以通過用戶的選擇結果,在進行處理
八,通過axios發送請求,獲得想要的數據(非必需)
久,拉取遠端庫里面的項目模版
這里以git倉庫為例子,首先確保自己按裝了git環境,然后下載download-git-repo工具包,通過這個工具包就可以實現從遠端拉取文件,并放置在我們創建好的項目文件夾下面。由于download-git-repo這個工具包不支持promise,所以通過node的util模塊將它promise化。方便操作。
npm install download-git-repo --savepromise化
// lib/Generator.js... const util = require('util') const path = require('path') const downloadGitRepo = require('download-git-repo') // 不支持 Promise// 添加加載動畫 async function wrapLoading(fn, message, ...args) {... }class Generator {constructor (name, targetDir){...// 對 download-git-repo 進行 promise 化改造this.downloadGitRepo = util.promisify(downloadGitRepo);}...// 下載遠程模板// 1)拼接下載地址// 2)調用下載方法async download(repo, tag){// 1)拼接下載地址const requestUrl = `zhurong-cli/${repo}${tag?'#'+tag:''}`;// 2)調用下載方法await wrapLoading(this.downloadGitRepo, // 遠程下載方法'waiting download template', // 加載提示信息requestUrl, // 參數1: 下載地址path.resolve(process.cwd(), this.targetDir)) // 參數2: 創建位置}// 核心創建邏輯// 1)獲取模板名稱// 2)獲取 tag 名稱// 3)下載模板到模板目錄// 4)模板使用提示async create(){// 1)獲取模板名稱const repo = await this.getRepo()// 2) 獲取 tag 名稱const tag = await this.getTag(repo)// 3)下載模板到模板目錄await this.download(repo, tag)// 4)模板使用提示console.log(`\r\nSuccessfully created project ${chalk.cyan(this.name)}`)console.log(`\r\n cd ${chalk.cyan(this.name)}`)console.log(' npm run dev\r\n')} }module.exports = Generator;說明:通過之前的步驟我們是可以獲得,模版項目的git倉庫的地址的,所以通過只要將這個地址傳入這個工具包實例就行。
十,cli創建完成,發布
通過上面的步驟就可以實現一個簡單的cli,完善package.json文件之后,我們可以通過在npm官網上注冊,然后npm publish 發布,
commander介紹
commander函數為全局提供了一個全局對象,通過這個對象去進行操作
const {program} = require('commander'); //也可以通過實例化獲得 import {Command} from 'commander/esm.mjs'; const program = new Command()全局對象的API
command 選項
option()
Commander 使用.option()方法來定義選項,同時可以附加選項的簡介。每個選項可以定義一個短選項名稱(-后面接單個字符)和一個長選項名稱(–后面接一個或多個單詞),使用逗號、空格或|分隔。
解析后的選項可以通過Command對象上的.opts()方法獲取,同時會被傳遞給命令處理函數??梢允褂?getOptionValue()和.setOptionValue()操作單個選項的值。
對于多個單詞的長選項,選項名會轉為駝峰命名法(camel-case),例如–template-engine選項可通過program.opts().templateEngine獲取。
多個短選項可以合并簡寫,其中最后一個選項可以附加參數。 例如,-a -b -p 80也可以寫為-ab -p80,甚至進一步簡化為-abp80。
–可以標記選項的結束,后續的參數均不會被命令解釋,可以正常使用。
默認情況下,選項在命令行中的順序不固定,一個選項可以在其他參數之前或之后指定。
常用選項類型,boolean 型選項和帶參數選項
有兩種最常用的選項,一類是 boolean 型選項,選項無需配置參數,另一類選項則可以設置參數(使用尖括號聲明在該選項后,如–expect )。如果在命令行中不指定具體的選項及參數,則會被定義為undefined。
requireOption(). // 用來設置必填選項
command()創建命令
program//接收兩個參數,命令名稱,參數.command('clone <source> [destination]')//命令的描述.description('clone a repository into a newly created directory')//命令執行的回調.action((source, destination) => {console.log('clone command called');});command另外一種參數制定方式:
.argument(“1”,‘2’,’<3>’,’[4]’)
尖括號表示必須參數,方括號表示可選參數
.hook() 執行生命周期函數
program.option('-t, --trace', 'display trace statements for commands').hook('preAction', (thisCommand, actionCommand) => {if (thisCommand.opts().trace) {console.log(`About to call action handler for subcommand: ${actionCommand.name()}`);console.log('arguments: %O', actionCommand.args);console.log('options: %o', actionCommand.opts());}});preAction:在本命令或其子命令的處理函數執行前
postAction:在本命令或其子命令的處理函數執行后
command 的其他API
1,–help -h 是默認的回展示所有的信息
2,addHelpText 額外添加幫助信息,參數1:命令,參數2:描述
3,showHelpAfterError 命令行命令出錯的處理
4,on 用來監聽命令和選項
5,version 用來制定npm包的版本
官方網址:
commander
inquirer介紹
inquirer是什么?
Inquirer是基于諾言的npm軟件包,用于Node項目中,以創建用于基于查詢的任務的CLI(命令行界面)工具。 詢問用戶問題,驗證用戶輸入并根據給定的響應進行操作非常好。
例子:
// promise 風格 var inquirer = require('inquirer'); inquirer.prompt([//用來配置選擇/* Pass your questions in here */]).then((answers) => {//回調函數,可以獲得用戶的選擇結果// Use user feedback for... whatever!!}).catch((error) => {// 處理錯誤if (error.isTtyError) {// Prompt couldn't be rendered in the current environment} else {// Something else went wrong}});選擇框配置這里就不細講了,可以參考inquirer網站:
inquirer官網
對應中文博客
總結
通過上面的大致介紹(沒有細致到每一步),我們應該可以大致了解cli創建的過程里吧。主要的思想就是,通過創建命令,然后執行(commander),通過inquirer庫獲得用戶的選擇結果,然后整合用戶的選擇,使用axios獲得參數或者直接判斷是拉取哪種模版,然后獲得git的地址,最后通過download-git-repo拉取git上的代碼。
參考文獻:
從0手動構建自己的cli
仿vue-cli構建腳手架
總結
- 上一篇: 九州风神推出 CH560 机箱:可查看
- 下一篇: 理想汽车新增车辆“睡眠辅助系统”专利,可