別人休息我努力,悄悄寫個(gè) cli 工具,必須提升效率,skr~
版權(quán)聲明:本人文章僅在掘金平臺(tái)發(fā)布,請(qǐng)勿抄襲搬運(yùn),轉(zhuǎn)載請(qǐng)注明作者及原文鏈接 ??
閱讀提示:網(wǎng)頁(yè)版帶有主題和代碼高亮,閱讀體驗(yàn)更佳 ??
如果你不想看文章,可以直接閱讀源碼:xwg-cli[1],如果有收獲請(qǐng)點(diǎn)個(gè) star。
開門見山,先看效果:
xwg \--help:
image.png
xwg \--version:
image.png
xwg create demo:
image.png
image.png
image.png
xwg create \--help:
image.png
再來(lái)看看模板的質(zhì)量如何:
每份模板都是我精心準(zhǔn)備的,具備較為完善的工程化能力,包括 eslint,commitlint,git cz,CHANGELOG 等。但是也沒有過(guò)多地干涉你的使用,并不是完全強(qiáng)制的。例如,雖然我配置了 eslint,但是沒有強(qiáng)制規(guī)則,你可以自己增減。
koa:
搭建了完整的 koa 開發(fā)結(jié)構(gòu),內(nèi)置了一個(gè) User 模塊,開箱即用。像 sequelize、log4js 等功能都是完備可用的,是不是非常用心,有木有!!
image.png
uniapp + vue2 + uview:
uniapp 這份 vue2 版本的模板,基于我自己的商業(yè)項(xiàng)目進(jìn)行改造,像請(qǐng)求模塊都是內(nèi)置好的。另外,這個(gè)模板還加入了 uview-ui,省去了你尋找 UI 添加組件庫(kù)的步驟,關(guān)鍵是添加 UI 組件庫(kù)真的有不少坑,用了這個(gè)模板直接快人一步,少踩起碼 10 個(gè)坑。
image.png 使用簡(jiǎn)介
在介紹開發(fā)代碼之前,想必你也想先嘗試下效果。
首先,確保你的 node 版本是 14.18+ 或者 16+。
執(zhí)行 yarn add xwg-cli \-g,重新打開終端執(zhí)行 xwg \--version 看看是否成功安裝。目前支持的命令有:
xwg \--help
xwg \--version
xwg create <項(xiàng)目名>
其實(shí) --help,--version 是自帶的(后面會(huì)詳細(xì)講),這兩個(gè)命令更嚴(yán)謹(jǐn)?shù)恼f(shuō)法是參數(shù),參數(shù)一般就是 -- 開頭,只有 create 才算是命令,目前 cli 僅完成了創(chuàng)建項(xiàng)目的能力,其他的能力正在思考建設(shè)中。
如果你不想下載,也可以執(zhí)行 npx xwg-cli create demo 嘗試創(chuàng)建項(xiàng)目。想必你又有疑問(wèn),為什么我的 cli 叫作 xwg-cli,但是我執(zhí)行的時(shí)候是 xwg xxx,為什么不是 xwg-cli xxx?這些問(wèn)題都將在我們接下來(lái)的文章中解答。
接下來(lái)的內(nèi)容會(huì)比較長(zhǎng),請(qǐng)坐好小板凳。
如何搭建項(xiàng)目的開發(fā)環(huán)境cli 工具本質(zhì)是一個(gè) npm 庫(kù),那么我們就應(yīng)該按照庫(kù)的模式搭建項(xiàng)目開發(fā)環(huán)境。如果你不知道如何搭建,請(qǐng)參考我的文章:開發(fā)一個(gè) npm 庫(kù)應(yīng)該做哪些工程配置?[2]。如果你不是很想從頭搭建開發(fā)環(huán)境,那么,你可以直接 fork 我的倉(cāng)庫(kù):xwg-cli[3]。
我的項(xiàng)目使用了 ts,但是不復(fù)雜,基本和 js 差不多,但是這仍然要求你具備比較好的 ts 知識(shí),否則閱讀文章會(huì)有一點(diǎn)難度。
需要用到的第三方庫(kù)| 庫(kù)名 | 作用 |
|---|---|
commander |
解析用戶在終端輸入的命令及參數(shù),例如 create、--help 等 |
chalk |
為終端輸出的文字增加各種各樣的顏色 |
inquirer |
給用戶提供各種交互,包括輸入、選擇等,例如提示用戶選擇項(xiàng)目的創(chuàng)建類型,其可以提供輸入框、radio、checkbox 等強(qiáng)大的交互控件 |
ora |
增加 loading,例如下載模板時(shí)增加 loading |
fs-extra |
fs 模塊的增強(qiáng),支持更強(qiáng)大的文件讀寫操作 |
download-git-repo |
下載模板 |
chalk
我下載的 chalk 的大版本是 4,類型定義可能和其它版本不一樣,具體請(qǐng)以你下載的版本為準(zhǔn)。
import chalk from 'chalk';
console.log(chalk.red('這會(huì)輸出紅色的字'))
console.log(chalk.blue('這會(huì)輸出藍(lán)色的字'))
console.log(chalk.cyan('這會(huì)輸出青色的字'))
console.log(chalk.cyan.bold('這會(huì)輸出青色加粗的字'))
console.log(chalk.cyan.italic('這會(huì)輸出青色斜體的字'))
chalk 改變字體顏色的所有方法定義:
declare type ForegroundColor =
| 'black'
| 'red'
| 'green'
| 'yellow'
| 'blue'
| 'magenta'
| 'cyan'
| 'white'
| 'gray'
| 'grey'
| 'blackBright'
| 'redBright'
| 'greenBright'
| 'yellowBright'
| 'blueBright'
| 'magentaBright'
| 'cyanBright'
| 'whiteBright';
chalk 改變字體背景色的所有方法定義:
declare type BackgroundColor =
| 'bgBlack'
| 'bgRed'
| 'bgGreen'
| 'bgYellow'
| 'bgBlue'
| 'bgMagenta'
| 'bgCyan'
| 'bgWhite'
| 'bgGray'
| 'bgGrey'
| 'bgBlackBright'
| 'bgRedBright'
| 'bgGreenBright'
| 'bgYellowBright'
| 'bgBlueBright'
| 'bgMagentaBright'
| 'bgCyanBright'
| 'bgWhiteBright';
chalk 改變字體屬性所有方法的定義:
declare type Modifiers =
| 'reset'
| 'bold'
| 'dim'
| 'italic'
| 'underline'
| 'inverse'
| 'hidden'
| 'strikethrough'
| 'visible';
了解這些基本已經(jīng)足夠你開發(fā)使用了。
commander
import { program } from 'commander';
program.name 定義命令的名稱。
program.name(chalk.cyan('xwg'))
program.name(chalk.cyan('demo'))
image.png
image.png
program.usage 定義命令的用法。
program.name(chalk.cyan('xwg')).usage(`${chalk.yellow('<command>')} [options]`);
image.png
program.version 定義 --version。
program.version(
`\r\n ${chalk.cyan.bold(VERSION)}
${chalk.cyan.bold(BRAND_LOGO)}`
);
image.png
program.on 監(jiān)聽某個(gè)參數(shù)的執(zhí)行,并執(zhí)行回調(diào)。
program.on("--help", function () {
console.log(`\r\n終端執(zhí)行 ${chalk.cyan.bold("xwg <command> --help")} 獲取更多命令詳情\r\n`);
});
當(dāng)我們監(jiān)聽某個(gè)命令時(shí),可以這么寫:
program
.command('create <project-name>') // 這里不能使用 chalk
.description(chalk.cyan('創(chuàng)建新項(xiàng)目'))
.option('-f, --force', chalk.red('如果目錄已存在將覆蓋原目錄,請(qǐng)謹(jǐn)慎使用,這會(huì)先刪除你已存在的項(xiàng)目再進(jìn)行創(chuàng)建,可能會(huì)存在意外情況'))
.action(這里是一個(gè)回調(diào)函數(shù),執(zhí)行這個(gè)命令的操作);
command 監(jiān)聽命令,description 設(shè)置命令的描述,option 是設(shè)置命令的可選參數(shù),比如這里設(shè)置了 --force ,意思就是是否強(qiáng)制覆蓋目錄,這個(gè)參數(shù)是可選的。 所以 --help , --version 其實(shí)也是參數(shù)。 action 就是這個(gè)命令真正的執(zhí)行的方法,這里我們定義了一個(gè) create 函數(shù),該方法最后就會(huì)執(zhí)行這個(gè)函數(shù),在這個(gè)函數(shù)中我們?nèi)?zhí)行一系列的包括詢問(wèn)用戶、添加 loading、下載模板等操作。
program.parse(process.argv); 是固定寫法,在最后執(zhí)行,就是將 process.argv 傳遞給 parse,parse 會(huì)解析用戶在終端輸入的一切命令還有參數(shù),并進(jìn)行執(zhí)行。
來(lái)看完整版:
const runner = () => {
program.name(chalk.cyan('demo')).usage(`${chalk.yellow('<command>')} [options]`);
program.version(
`\r\n ${chalk.cyan.bold(VERSION)}
${chalk.cyan.bold(BRAND_LOGO)}`
);
program
.command('create <project-name>') // 這里不能使用 chalk
.description(chalk.cyan('創(chuàng)建新項(xiàng)目'))
.option('-f, --force', chalk.red('如果目錄已存在將覆蓋原目錄,請(qǐng)謹(jǐn)慎使用,這會(huì)先刪除你已存在的項(xiàng)目再進(jìn)行創(chuàng)建,可能會(huì)存在意外情況'))
.action(() => {
// 執(zhí)行操作
});
program.on("--help", function () {
console.log(`\r\n終端執(zhí)行 ${chalk.cyan.bold("xwg <command> --help")} 獲取更多命令詳情\r\n`);
});
program.parse(process.argv);
};
inquirer
具體配置參見官方文檔:www.npmjs.com/package/inq…[4]
貼太多代碼影響閱讀體驗(yàn),想看具體實(shí)現(xiàn)細(xì)節(jié)和類型定義的可以去看源碼。
import Inquirer from 'inquirer';
export const prompt = async (prompts: any[]) => {
return await new Inquirer.prompt(prompts);
};
/** 詢問(wèn)要?jiǎng)?chuàng)建的項(xiàng)目類型 */
export const askCreateType = async () => {
const { projectType } = await prompt([
// 返回值為 Promise
// 具體配置參見:https://www.npmjs.com/package/inquirer#questions
{
type: "list",
name: "projectType",
message: "請(qǐng)選擇你要?jiǎng)?chuàng)建的項(xiàng)目類型",
choices: [
{ name: "vue", value: 'vue' },
{ name: "react", value: 'react' },
{ name: "uniapp", value: 'uniapp' },
{ name: "koa", value: 'koa' },
// { name: "nest", value: 'nest' },
{ name: "library", value: 'library' },
],
},
]);
return projectType;
};
ora
import ora from "ora";
import chalk from "chalk";
const spinner = ora('loading');
spinner.start(); // 開啟加載
spinner.succeed('成功');
spinner.fail("請(qǐng)求失敗,正在重試...");
download-git-repo
在網(wǎng)上看到其他文章說(shuō)該庫(kù)不支持 Promise,需要 util 模塊 promisify 一下:
import util from 'node:util';
import download from 'download-git-repo';
export const downloadGitRepo = util.promisify(download);
// 下面是偽代碼
downloadGitRepo(`direct: 倉(cāng)庫(kù)地址`, 存放的目標(biāo)地址, { clone: true })
一些前置知識(shí)
node_modules 的 bin 目錄
我們下載的一些 cli 工具,會(huì)放在 node_modules 下一個(gè) .bin 的目錄中。雖然其文件沒有后綴,但其實(shí)就是 js 文件,它會(huì)去找對(duì)應(yīng)的代碼執(zhí)行,下面是 bin 目錄下 nodemon 命令的文件內(nèi)容:
image.png
當(dāng)我們執(zhí)行命令時(shí) node 會(huì)從這個(gè)目錄中找到我們要執(zhí)行的命令。那為什么這些包會(huì)在這里生成一個(gè)命令文件呢?主要是因?yàn)榭梢栽?package.json 中配置字段 bin:
image.png
我們這里配置一個(gè)命令 xwg,它在執(zhí)行時(shí)會(huì)去尋找 bin 文件夾下的 cli.js 文件進(jìn)行執(zhí)行。這也就是為什么我的包名是 xwg-cli,但是我卻可以通過(guò) xwg 來(lái)執(zhí)行命令。我們還可以創(chuàng)建多個(gè),不同的命令對(duì)應(yīng)不同的執(zhí)行文件:
"bin": {
"xwg": "./bin/cli.js",
"ikun": "./bin/ikun.js"
},
必要的注釋
接下來(lái)也是一個(gè)非常重要的點(diǎn):
image.png
可以看到,在 cli.js 的首行,有一行注釋:#! /usr/bin/env node。這個(gè)注釋可不是多余,所有的 node cli 必須在開頭包含此注釋,否則命令就沒法正常運(yùn)行,這句注釋就是指該命令在 node 環(huán)境下運(yùn)行,所以請(qǐng)務(wù)必不要省略此注釋!
本地測(cè)試
我們可以現(xiàn)在 bin/cli.js 下隨便寫點(diǎn)什么:
#! /usr/bin/env node
console.log('跑起來(lái)了')
基于當(dāng)前項(xiàng)目打開你的終端,運(yùn)行 npm link,鏈接你的本地包,此時(shí)我們終端運(yùn)行 xwg 試試:
image.png
如果沒有權(quán)限請(qǐng)記得給你的項(xiàng)目賦予管理員權(quán)限。
工程化配置tsconfig.json
noEmit 必須是 false,否則 tsc 編譯后不輸出文件。
include 必須包含 types/**/*,否則在 types 下的聲明不起效果。
{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ESNext", "DOM"],
"moduleResolution": "Node",
"strict": true,
"sourceMap": false,
"resolveJsonModule": true,
"isolatedModules": false,
"esModuleInterop": true,
"noEmit": false,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"skipLibCheck": true
},
"include": ["src", "types/**/*"]
}
nodemon
由于每次更改都要重新執(zhí)行一遍 node ./lib/index.js 過(guò)于麻煩,所以需要借助于 nodemon。
下載 nodemon:yarn add nodemon \-D。
新建 nodemon.json:
{
"watch": ["src"], // 監(jiān)聽 src 目錄
"ext": "ts", // 匹配擴(kuò)展名為 ts 的文件
"exec": "rimraf lib && tsc --outDir lib --module CommonJS"
}
watch:監(jiān)聽 src 目錄。
ext:匹配擴(kuò)展名為 ts 的文件。
exec:rimraf 是一個(gè) npm 包,作用是刪除目錄文件,優(yōu)點(diǎn)是跨平臺(tái)兼容性好。該命令的意義是:tsc 編譯 src 下的 ts 文件并制定輸出目錄為 lib (--outDir lib),并指定編譯目標(biāo)的模塊化規(guī)范是 commonjs(--module CommonJS)。
因?yàn)橛?nodemon.json 的配置文件,我們?cè)?package.json 中可以簡(jiǎn)寫:
"scripts": {
"dev": "nodemon",
},
每次我們執(zhí)行 npm run dev 實(shí)際上就是執(zhí)行 nodemon,nodemon 會(huì)尋找 nodemon.json 并執(zhí)行里面的配置,每次當(dāng)我們修改 src 的 ts 文件它就會(huì)刪除 lib 并幫助我們重新 tsc 編譯一遍。
image.png
- bin 文件我們存放命令的執(zhí)行入口
- lib 的代碼是 src 經(jīng)過(guò) tsc 編譯為 commonjs 的代碼
- src 是我們編寫核心代碼的地方
- test 用于存放單元測(cè)試
- types 用于存放 ts 類型聲明
- .editorconfig 編輯器配置
- .eslintignore eslint 忽略文件
- .eslintrc eslint 配置文件
- commitlin.config.js commit 提交信息校驗(yàn)的配置文件,commit message 不對(duì)會(huì)報(bào)錯(cuò)
- nodemon.json 配置 nodemon,幫助我們保存代碼后熱更新,不用再執(zhí)行
node lib/index.js - tsconfig.json ts 配置文件
bin/cli.js
#! /usr/bin/env node
/**
* xwg cli
* @author xiwenge <[email protected]>
* @create 2023/06/24
*/
'use strict';
const runner = require('../lib/index').default;
runner();
不建議把大量的代碼堆在 bin 目錄下,可能很多教程會(huì)這么做,但是這樣拆分代碼不太簡(jiǎn)潔美觀。 我們將所有核心代碼放入 lib 目錄下,從 lib 引入核心代碼來(lái)進(jìn)行運(yùn)行。
在這里我們引入 runner 函數(shù)進(jìn)行執(zhí)行。
src/index.ts
import runner from './runner';
export default runner;
src/runner.ts
這里,我們把 command 相關(guān)的代碼拆出去。
import { program } from 'commander';
import chalk from 'chalk';
import {
create
} from './commands';
import { BRAND_LOGO, VERSION } from './const';
const runner = () => {
program.name(chalk.cyan('xwg')).usage(`${chalk.yellow('<command>')} [options]`);
program.version(
`\r\n ${chalk.cyan.bold(VERSION)}
${chalk.cyan.bold(BRAND_LOGO)}`
);
create();
program.on("--help", function () {
console.log(`\r\n終端執(zhí)行 ${chalk.cyan.bold("xwg <command> --help")} 獲取更多命令詳情\r\n`);
});
program.parse(process.argv);
};
export default runner;
src/const.ts
此目錄用于存放我們需要使用到的靜態(tài)變量。這種藝術(shù)字可以訪問(wèn) ascii 藝術(shù)字[5],我們這里使用的字體是 ANSI Shadow。
/**
* 靜態(tài)變量
* @author xiwenge <[email protected]>
* @create 2023/06/25
*/
import fs from 'fs-extra';
import path from 'node:path';
import util from 'node:util';
import download from 'download-git-repo';
/** 當(dāng)前根目錄 */
export const ROOT_DIR = path.resolve(__dirname, '../');
const { version } = fs.readJSONSync(path.resolve(ROOT_DIR, 'package.json'));
/** https://tooltt.com/art-ascii/ font: ANSI Shadow */
export const BRAND_LOGO = `
██╗ ██╗██╗ ██╗ ██████╗ ██████╗██╗ ██╗
╚██╗██╔╝██║ ██║██╔════╝ ██╔════╝██║ ██║
╚███╔╝ ██║ █╗ ██║██║ ███╗ ██║ ██║ ██║
██╔██╗ ██║███╗██║██║ ██║ ██║ ██║ ██║
██╔╝ ██╗╚███╔███╔╝╚██████╔╝ ╚██████╗███████╗██║
╚═╝ ╚═╝ ╚══╝╚══╝ ╚═════╝ ╚═════╝╚══════╝╚═╝
`;
/** 當(dāng)前版本號(hào) */
export const VERSION = version;
export const getRepoURL = (tag: string) => {
return `https://gitee.com/redstone-1/${tag}.git`;
};
export const downloadGitRepo = util.promisify(download);
封裝loading和prompt
loading
import ora from "ora";
import chalk from "chalk";
import { LoadingOther } from '../types';
/**
* 睡覺函數(shù)
* @param {Number} delay 睡眠時(shí)間
*/
const sleep = (delay: number) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(false);
}, delay);
});
};
/**
* 加載中方法
* @param message - 提示信息
* @param callback - 執(zhí)行的回調(diào)
* @param projectName - 項(xiàng)目名
* @returns
*/
export const loading = async (message: string, callback: () => any, other: LoadingOther): Promise<any> => {
const spinner = ora(message);
spinner.start(); // 開啟加載
try {
const res = await callback();
// 加載成功
spinner.succeed(
`${chalk.black.bold('下載成功!執(zhí)行以下命令打開并運(yùn)行項(xiàng)目:')}
\r\n ${chalk.gray.bold(`cd ${other?.projectName}`)}
\r\n ${chalk.gray.bold('npm install')}
\r\n ${chalk.gray.bold('npm run dev')}
\r\n ${chalk.gray.bold('問(wèn)題、意見、建議請(qǐng)反饋至:https://github.com/Redstone-1/xwg-cli/issues')}
`
);
return res;
} catch (error) {
// 加載失敗
spinner.fail("請(qǐng)求失敗,正在重試...");
await sleep(1000);
// 重新拉取
return loading(message, callback, other);
}
};
prompt
import Inquirer from 'inquirer';
export default async (prompts: any[]) => {
return await new Inquirer.prompt(prompts);
};
askUser.ts
將所有詢問(wèn)用戶的操作都封裝在此,這里僅貼出示例:
import prompt from "../../utils/prompt";
// ========== library ==========
/** 詢問(wèn)要?jiǎng)?chuàng)建的項(xiàng)目類型 */
export const askCreateType = async () => {
const { projectType } = await prompt([
// 返回值為 Promise
// 具體配置參見:https://www.npmjs.com/package/inquirer#questions
{
type: "list",
name: "projectType",
message: "請(qǐng)選擇你要?jiǎng)?chuàng)建的項(xiàng)目類型",
choices: [
{ name: "vue", value: 'vue' },
{ name: "react", value: 'react' },
{ name: "uniapp", value: 'uniapp' },
{ name: "koa", value: 'koa' },
// { name: "nest", value: 'nest' },
{ name: "library", value: 'library' },
],
},
]);
return projectType;
};
...
create 命令
新建 commands 目錄存放各類命令
image.png
拆分 action
將 action 的回調(diào)函數(shù) create 拆分出去:
image.png
create.ts
這里主要做了一件事,判斷要?jiǎng)?chuàng)建的項(xiàng)目目錄名是否存在,分別走入不同的分支邏輯:
import path from 'path';
import fs from 'fs-extra';
import dirExistCall from './dirExistCall';
import downloadRepo from './downloadRepo';
/**
* 創(chuàng)建新項(xiàng)目
* @param projectName - 項(xiàng)目名
* @param options - 命令參數(shù)
*/
export default async (projectName: string, options: any) => {
// 獲取當(dāng)前工作目錄
const cwd = process.cwd();
// 拼接得到項(xiàng)目目錄
const targetDirectory = path.join(cwd, projectName);
// 判斷目錄是否存在
if (fs.existsSync(targetDirectory)) {
await dirExistCall(options, projectName, targetDirectory);
} else {
await downloadRepo(projectName, targetDirectory);
}
};
dirExistCall.ts
當(dāng)目錄已經(jīng)存在時(shí),需要詢問(wèn)用戶是否覆蓋。
import fs from "fs-extra";
import downloadRepo from './downloadRepo';
import { askOverwrite } from './askUser';
/**
* 如果目錄已經(jīng)存在時(shí)調(diào)用
* @param options - 命令參數(shù)
* @param targetDirectory - 目標(biāo)路徑
*/
export default async (options: any, projectName: string, targetDirectory: string) => {
// 判斷是否使用 --force 參數(shù)
if (options.force) {
// 刪除重名目錄
await fs.remove(targetDirectory);
await downloadRepo(projectName, targetDirectory);
} else {
const { isOverwrite } = await askOverwrite();
// 選擇 Overwirte
if (isOverwrite) {
// 先刪除掉原有重名目錄
await fs.remove(targetDirectory);
await downloadRepo(projectName, targetDirectory);
}
}
};
downloadRepo.ts
當(dāng)用戶將所有的選擇都確認(rèn)后執(zhí)行下載模板的操作。下面給出部分代碼示例,請(qǐng)從下往上閱讀:
import {
askCreateType,
askNeedTypeScript,
askIsAgreeCli,
askVueVersion,
askNeedUviewUI,
} from "./askUser";
import { loading } from "../../utils/loading";
import { getRepoURL, downloadGitRepo } from "../../const";
import { TProjectType } from '../../types';
import chalk from "chalk";
/**
* 下載 vue 模板
* @param projectName - 項(xiàng)目名稱
* @param targetDirectory - 目標(biāo)存儲(chǔ)路徑
*/
const downloadVueTemplate = async (projectName: string, targetDirectory: string) => {
let repoURL = '';
const needTypeScript = await askNeedTypeScript();
if (needTypeScript) {
repoURL = getRepoURL('vue-template-typescript');
}
if (!needTypeScript) {
repoURL = getRepoURL('vue-template');
}
await loading('正在下載模板,請(qǐng)稍后...', () => downloadGitRepo(`direct:${repoURL}`, targetDirectory, { clone: true }), { projectName });
};
/**
* 執(zhí)行創(chuàng)建命令
* @param projectType - 項(xiàng)目類型 "library" | "react" | "vue" | "uniapp" | "koa" | nest""
* @param projectName - 項(xiàng)目名稱
* @param targetDirectory - 目標(biāo)存儲(chǔ)路徑
*/
const execCreate = async (projectType: TProjectType, projectName: string, targetDirectory: string) => {
switch (projectType) {
case 'library':
await downloadLibraryTemplate(projectName, targetDirectory);
break;
case 'vue':
await downloadVueTemplate(projectName, targetDirectory);
break;
case 'react':
await downloadReactTemplate(projectName, targetDirectory);
break;
case 'uniapp':
await downloadUniappTemplate(projectName, targetDirectory);
break;
case 'koa':
await downloadKoaTemplate(projectName, targetDirectory);
break;
case 'nest':
console.log(chalk.gray.bold(`\r\n 開發(fā)中,敬請(qǐng)期待...\r\n`));
break;
}
};
/**
* 創(chuàng)建項(xiàng)目
* @param projectName - 項(xiàng)目名稱
* @param targetDirectory - 目標(biāo)存儲(chǔ)路徑
*/
export default async (projectName: string, targetDirectory: string) => {
console.log(chalk.red.bold(`\r\n 請(qǐng)注意:本 cli 下大部分模板采用 vite 構(gòu)建,node 版本需要 14.18+ 或 16+ 或更高\(yùn)r\n`));
const projectType = await askCreateType();
await execCreate(projectType, projectName, targetDirectory);
};
到這里我們就完成了 create 命令,現(xiàn)在執(zhí)行 xwg create xxx 就可以創(chuàng)建項(xiàng)目了。
貼了太多代碼看起來(lái)可能不是很順暢,建議直接去看源碼,更能快速上手學(xué)習(xí)。
在其他的一些高級(jí) cli 教程里,使用 babel 進(jìn)行各種配置文件的生成和寫入,這是更高級(jí)的內(nèi)容,暫時(shí)做不了。目前比較呆的處理方式是寫一堆模板,不同的配置下載不同的模板。引入 babel 可以減少模板倉(cāng)庫(kù)數(shù)量,根據(jù)用戶的選擇動(dòng)態(tài)的刪除、插入各種文件,具備更強(qiáng)大更靈活的處理能力,但是目前不具備 babel 的運(yùn)用能力,后續(xù)再考慮進(jìn)行升級(jí)。若有愿意共建的朋友,歡迎 Pr。
關(guān)于本文
https://juejin.cn/post/7256702654579310652
