使用 typescript 快速開發一個 cli
阿新 • • 發佈:2020-12-08
cli 的全稱 command-line interface(命令列介面),也就是前端同學常用的腳手架,比如 [yo](https://yeoman.io/)、[vue cli](https://cli.vuejs.org/zh/guide/)、[react cli](https://github.com/facebook/create-react-app) 等。
cli 可以方便我們快速建立專案,下圖是引用 vue cli 的介紹:
![vue cli guide](https://img2020.cnblogs.com/blog/423657/202012/423657-20201202150232018-1502238969.png)
## 建立專案
執行下面的命令,建立一個專案:
```sh
npm init
```
執行命令完成後,可以看到專案根目錄只有一個 package.json 檔案。
![demo1](https://img2020.cnblogs.com/blog/423657/202012/423657-20201202185344046-40286989.png)
在 package.json 檔案增加 bin 物件,並指定入口檔案 dist/index.js。
> 在命令列執行需要在入口檔案的第一行增加 `#!/usr/bin/env node`,告訴系統用 node 執行這個檔案。
```json
{
"name": "cli-demo",
"version": "0.0.1",
"description": "cli demo",
"keywords": [
"cli"
],
"bin": {
"cli-demo": "dist/index.js"
}
...
}
```
## 安裝依賴
命令列工具,也會涉及到使用者互動的動作,那麼 node.js 是怎麼實現呢?早有大佬提供了非常好的庫,我們只要拿過來用,主要有兩個庫:
- [commander](https://github.com/tj/commander.js):完整的 node.js 命令列解決方案。
- [inquirer](https://github.com/SBoudrias/Inquirer.js):互動式命令列工具。
將這兩個庫安裝到專案裡:
```sh
yarn add commander inquirer
```
由於是用 typescript 開發,再通過 rollup 打包,先安裝相關的依賴庫:
```sh
yarn add typescript rollup rollup-plugin-terser rollup-plugin-typescript2 @types/inquirer -D
```
## 配置
由於是用 typescript 開發,首先需要配置一下 tsconfig.json。
```json
{
"compilerOptions": {
"target": "ES6",
"module": "ESNext",
"sourceMap": false,
"declaration": false,
"outDir": "./dist",
"moduleResolution": "Node",
"esModuleInterop": true,
"resolveJsonModule": true,
"removeComments": false,
"importHelpers": true,
"strict": true,
"lib": ["ES6", "DOM"]
},
"include": ["src"]
}
```
接下來在根目錄增加一個 rollup.config.js,把 typescript 程式碼編譯成 javascript 程式碼。前面提到的要在第一行增加 `#!/usr/bin/env node` 來告訴系統用 node 執行,那麼可以在 rollup.config.js 的 `banner` 選項,把 `#!/usr/bin/env node` 寫在最前面。
```js
import typescript from 'typescript'
import json from '@rollup/plugin-json'
import { terser } from 'rollup-plugin-terser'
import typescript2 from 'rollup-plugin-typescript2'
import { dependencies } from './package.json'
const external = Object.keys(dependencies || '')
const globals = external.reduce((prev, current) => {
const newPrev = prev
newPrev[current] = current
return newPrev
}, {})
const defaultConfig = {
input: './src/index.ts',
output: {
file: './dist/index.js',
format: 'cjs',
banner: '#!/usr/bin/env node',
globals
},
external,
plugins: [
typescript2({
exclude: 'node_modules/**',
useTsconfigDeclarationDir: true,
typescript,
tsconfig: './tsconfig.json'
}),
json(),
terser()
]
}
export default defaultConfig
```
## 實現一個簡單的 cli
在根目錄建立一個 `src` 資料夾,然後再建立一個 `index.ts`。
### 新增引用
新增引用並例項化 `Command` 物件。
```ts
import { Command } from 'commander'
import pkg from '../package.json'
const program = new Command(pkg.name)
```
### 自定義命令
實現一個可互動的自定義命令,模擬在終端(命令列)的登入功能。使用 `command` 方法建立一個命令,`description` 可以用來描述這個命令的作用,登入處理邏輯則寫在 `action` 方法裡。最後使用 `parse(process.argv)` 方法,解析命令。更多詳細介紹和使用,可移步:[https://github.com/tj/commander.js/blob/master/Readme_zh-CN.md](https://github.com/tj/commander.js/blob/master/Readme_zh-CN.md)。
```ts
program
.command('login')
.description('模擬登入。')
.action(() => {
handleLogin()
})
program.parse(process.argv)
```
互動的話,用到前面說的 `inquirer` 庫,接收輸入的使用者名稱和密碼。選項的 `type` 的值有 `input`、`password`、`number`、`checkbox`、`editor`、`list`、`rawList`、`expand`、`confirm`,選項 `name` 是 `inquirer.prompt` 方法返回的物件,選項 `validate` 可用來驗證輸入是否符合規則。更多詳細介紹和使用,可移步:[https://github.com/SBoudrias/Inquirer.js/blob/master/README.md](https://github.com/SBoudrias/Inquirer.js/blob/master/README.md)
> 如果選項 `type` 是 `password`,可通過 `mask` 設定掩碼。
```ts
const handleLogin = () => {
// 配置互動的使用者名稱和密碼
const prompt = [
{
type: 'input',
name: 'userName',
message: '使用者名稱:',
validate: (value: string) => value.length > 0 || '使用者名稱不能為空'
},
{
type: 'password',
name: 'password',
message: '密碼:',
mask: '