前端工程化:如何使用 TS 開發一個 CLI 腳手架


當我們使用 react 或 Vue 開發項目時,我們可以通過使用 create-react-app,vue-cli 這樣的命令行工具快速創建一個項目的腳手架。那么 create-react-app 這樣的工具是怎樣開發的呢?

創建項目

首先我們需要創建一個新的項目用於開發我們的 CLI 項目。

  1. 先創建一個目錄,就叫它 mycli 好了。然后進入目錄,使用 npm init 創建基本的 package.json。
  2. 修改 name 字段,這就是我們通過 npm install 安裝的包名,就叫它 @tirion/cli 好了。
  3. 新增 bin 字段,在這個字段中我們需要為使用的命令進行命名,以及這個命令運行的文件。使用的命令就叫 create-tirion-app 好了,運行的文件為 ./bin/cli.js。

那么現在這個 package.json 的配置就如下:

{
  "name": "@tirion/cli",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "bin": {
    "create-tirion-app": "./bin/cli.js"
  },
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

創建命令

現在,我們在命令行中輸入 create-tirion-app,並沒有這個命令。那么怎么將這個命令添加到全局呢?
在創建命令之前我們先簡單的編寫下 cli.js,內容如下:

#!/usr/bin/env node

console.log("tirion cli")

第一行是 shebang,學過 shell 的應該都知道。下面就是正式的內容了。
好了,基本的內容寫好了,下面就開始創建一個命令,其實很簡單,只需要運行一下 yarn link 就行了,我們就可以在命令行中使用 create-tirion-app,並會輸出 tirion cli。當要移除此命令時在此項目中使用 yarn unlink 即可。

注意是直接在項目中使用 yarn unlink,而不是使用 yarn unlink @tirion/cli

安裝依賴

通常開發一個 cli 項目的依賴有 commanderjs, inquirerjs, shelljs, ora, chalk 等。
另外2022年了,也該使用 ts 來開發項目了,但是網上找的一些開發 cli 的教程都是使用的 js,而我們通過 shebang 也只能指定使用 node 來運行,那么怎樣才能使用 ts 來編寫我們的 cli 呢?

首先需要安裝 typescript 並配置 tsconfig.json,然后就再安裝 ts-node,在 cli.js 中引入 ts-node 就能使用 ts 來編寫了,如:

#!/usr/bin/env node

console.log("tirion cli")

require('ts-node/register');  // 先引入 ts-node/register,后面就可以使用 ts 了
require('../scripts/init.ts');  // 引入 ts 文件

當然,如果能要求用戶全局安裝了 ts-node 也可以使用 shebang #!/usr/bin/env ts-node 來指定 ts-node 運行。

由於 ts-node 是運行時編譯,速度會較慢,但是通常運行個腳本還是能接受。如果使用 esbuild-register 會有一定的速度提升,如果要更快的速度可能就需要使用 rollup, webpack 等打包成 js 來運行了。

這里再介紹下使用 esbuild 運行,需要先安裝 esbuild 和 esbuild-register,只安裝 esbuild-register 不行。然后 cli.js 中這樣寫:

#!/usr/bin/env node

console.log("tirion cli")

const { register } = require('esbuild-register/dist/node');
const { unregister } = register({
  // ...options
});

require('../scripts/init.ts');

unregister();

不能只 require('esbuild-register/dist/node');,調用了 register 后才能使用 ts。

參考資料:使用 TypeScript 來編寫 cli 程序

另外,由於我們使用 TS 來開發,所以應該把這些工具庫相關的類型定義包也安裝上,如 @types/node, @types/inquirer 等。

編寫代碼

准備工作一切就緒,下面就可以開始編寫業務代碼了。
首先我們在 cli.js 中導入了 init.ts,那么就先編寫 init.ts。

// init.ts

import inquirer from 'inquirer';

import downloadTemplate from './downloadTemplate';
import install from './install';

import { PkgManager } from './types';

const init = async() => {
    const appName = await downloadTemplate();

    const res = await inquirer.prompt<{ pkgManager: PkgManager }>({
        type: 'list',
        message: '選擇依賴包管理工具',
        name: 'pkgManager',
        choices: ['yarn', 'npm']
    });
    
    install(res.pkgManager, appName);
}

init();

上面的代碼中,因為 commander, inquirer 的方法很多是異步的,所以封裝了一個函數使用 async await 來實現同步編程。

首先我們引入了 downloadTemplate,並在 init 中進行調用,這個函數的目的是下載 git 上的模板。

下面就是 downloadTemplate 的代碼:

// downloadTemplate.ts

import { program } from 'commander';
import shelljs from 'shelljs';
import chalk from 'chalk';

import pkg from '../package.json';
import config from './config';

const downloadTemplate = (): Promise<string> => {
    return new Promise<string>(resolve => {
        program
            .version(pkg.version)
            .argument('<appName>', '創建的應用名稱')
            .action((appName: string) => {
                if (!shelljs.which('git')) {
                    shelljs.echo(chalk.bgRed('錯誤:請先安裝 git'));
                    shelljs.exit(1);
                }
                shelljs.exec(
                    `git clone ${config.gitRepoUrl} ${appName}`,
                    code => {
                        if (code !== 0) {
                            shelljs.echo(chalk.bgRed('錯誤:拉取模板失敗'));
                            shelljs.exit(1);
                        }
                        resolve(appName);
                    }
                );
            })
            .parse(process.argv);
    });
};

export default downloadTemplate;

上面的代碼中我們通過 shelljs.which 判斷 git 命令是否存在,不存在則輸出錯誤並退出,存在則使用 shelljs.exec 執行一個 shell 命令,執行成功后調用 resolve 方法繼續往下執行。

從 init.ts 中可以看出繼續執行會使用 inquirer.prompt 方法進行詢問使用哪個包管理器,選擇后調用 install 方法進行安裝。下面是 install.ts 的內容:

// install.ts
import shelljs from 'shelljs';
import chalk from 'chalk';
import { PkgManager } from './types';

const install = (pkgManager: PkgManager, appName: string) => {
    shelljs.cd('./' + appName);
    if (pkgManager === 'yarn') {
        shelljs.exec('yarn', code => {
            if (code !== 0) {
                shelljs.echo(chalk.bgRed('錯誤:使用 yarn 安裝依賴失敗'));
            }
        });
    } else {
        shelljs.exec('npm install -d', code => {
            if (code !== 0) {
                shelljs.echo(chalk.bgRed('錯誤:使用 npm 安裝依賴失敗'));
            }
        });
    }
}

export default install;

首先使用 shelljs.cd 進入項目目錄,然后根據選擇的包管理器執行相應的 shell 命令安裝依賴。依賴安裝完成后這個簡單的 cli 工具就完成了。

如果要開發更強大的 cli 工具,可以通過 inquirer 進行詢問,根據不同的選擇下載不同的模板;不同的選擇對模板文件進行操作等。這些就看個人的想象力了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM