<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          別人休息我努力,悄悄寫個(gè) cli 工具,必須提升效率,skr~

          共 24725字,需瀏覽 50分鐘

           ·

          2023-09-01 10:23

          版權(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

          ae9a33e0020847e80fba32e5c87f2173.webpimage.png

          xwg \--version

          2034511c7bfb347883b74257a4162ac7.webpimage.png

          xwg create demo

          4d6927a668c6e46453f21399e4bc5f32.webpimage.png 20a1198e1c013d00593da79c3854543d.webpimage.png 74d3ccfc14224d87f1878e6de311e886.webpimage.png

          xwg create \--help

          1b8b6315911d4804ea8f62ec9b7c6601.webpimage.png

          再來(lái)看看模板的質(zhì)量如何:

          每份模板都是我精心準(zhǔn)備的,具備較為完善的工程化能力,包括 eslintcommitlintgit czCHANGELOG 等。但是也沒有過(guò)多地干涉你的使用,并不是完全強(qiáng)制的。例如,雖然我配置了 eslint,但是沒有強(qiáng)制規(guī)則,你可以自己增減。

          koa:

          搭建了完整的 koa 開發(fā)結(jié)構(gòu),內(nèi)置了一個(gè) User 模塊,開箱即用。像 sequelizelog4js 等功能都是完備可用的,是不是非常用心,有木有!!

          4fcb41a5f83e4b3d01ca14ecdeeea148.webpimage.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è)坑。

          191cad5859c9a56590f9353f649c51ed.webpimage.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'))
          eac72cd261ead66ecf301b2d231e32a4.webpimage.png 28fbd487093f2f16a195d2536c51f908.webpimage.png

          program.usage 定義命令的用法。

                  
                  program.name(chalk.cyan('xwg')).usage(`${chalk.yellow('<command>')} [options]`);
          b9b0769b2644137916255b3f9f078047.webpimage.png

          program.version 定義 --version

                  
                  program.version(
            `\r\n  ${chalk.cyan.bold(VERSION)}
            ${chalk.cyan.bold(BRAND_LOGO)}`

          );
          ec9b9fc2f2e9683dd2ee811f705c9886.webpimage.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)容:

          79f18510067e1cf266fcefe16d21c57a.webpimage.png

          當(dāng)我們執(zhí)行命令時(shí) node 會(huì)從這個(gè)目錄中找到我們要執(zhí)行的命令。那為什么這些包會(huì)在這里生成一個(gè)命令文件呢?主要是因?yàn)榭梢栽?package.json 中配置字段 bin

          7b2b468892ca606f9f1c70f957145237.webpimage.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):

          b7be065e9c4179fd4d3f199cedb65b8b.webpimage.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 試試:

          d09e3fbc01f3a3e135c874f335b15122.webpimage.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 編譯一遍。

          文件目錄結(jié)構(gòu) ac2587a26196e8aec4c6734b999ed4e4.webpimage.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 目錄存放各類命令

          55f7344b7c853fd635f364949a3ab66a.webpimage.png

          拆分 action

          將 action 的回調(diào)函數(shù) create 拆分出去:

          3a50eb400e20e2572a458c33967ffde9.webpimage.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


          瀏覽 104
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  玖玖成人免费 | 天天看天天摸 | 日韩一区高清 | 久久免费激情视频 | 亚洲日韩在线视频 |