工欲善其事必先利其器(制作CLI)

前言

在上篇 工欲善其事必先利其器(配置Vue3 + ts项目模板) 中,我们以 Vue3 + ts 为例配置一个简单的项目模板,加上工欲善其事必先利其器(前端代码规范)篇,整个配置还是挺繁琐的,为了方便使用统一规范的模板,这篇我们来学习制作简易的CLI

准备

初始化一个项目,项目结构如下

├─index.js // 主入口
├─package.json
├─lib

处理命令行

自行处理的话可以参照create-vu,这里选择commander命令行工具,可以非常简单的定义想要的指令,交互式命令选择 prompts

首先是处理 project name

const { program } = require('commander');
const prompts = require('prompts')

program.version(require('./package.json').version)
  .option('-n, --name <string>', 'project name')
  .action(async function (cmdArg) {
    let { name } = cmdArg

    if (!name) {
      const answer = await prompts([{
        type: 'text',
        name: 'name',
        message: 'Project name:',
        initial: 'project'
      }
      ])
      name = answer.name
    }
  }).parse(process.argv); 

执行 node index.js 运行测试,可以看到Project name 的交互提示,也可以执行node index.js -n testName,测试 -n 指令

这样简单的入口就完成了

下载远程模板

接下来我们使用 download-git-repo 下载在上篇配置好的模板vue3-template

  • lib/download.js,封装下载函数,ora需要装 5.x 及以下版本,否则需要使用 ESModule 方式使用
const { promisify } = require('util');
const ora = require('ora')

const download = promisify(require('download-git-repo'));

module.exports.clone = async function (repo, desc) {
  const process = ora(`download...${repo}\n`);
  process.start();

  await download(repo, desc);
  process.stop();
}

  • index.js 改造如下
const { promisify } = require('util');
const ora = require('ora')

const download = promisify(require('download-git-repo'));

module.exports.clone = async function (repo, desc) {
  const process = ora(`download...${repo}\n`);
  process.start();

  await download(repo, desc);
  process.stop();
}

执行node index.js -n testName 测试,可以看到把vue3-template下载到 testName 文件夹下

至此,一个简单的克隆模板的 cli 就制作完成了

根据用户选择生成模板

当然也有想要自行选择工具的需要,可以提供集成的工具选择,再根据用户选择生成模板

选择各种规范

以规范为例,在 工欲善其事必先利其器(前端代码规范) 中,我们了解了各种规范的配置流程,那么这里就简单了,只需要收集选择的工具,把相应的配置写入文件即可

实现流程如下:

  1. 收集用户选择
  2. 处理用户选择,也就是待生成文件的内容
  3. 最后根据处理好的数据写入对应的文件
  • lib/init.js
const fs = require("fs");

const {
  initESLint,
  initStylelint,
  initPrettier,
  initGitHooks,
  initESLintFile,
  initPrettierFile,
  initStylelintFile,
  initGitHooksFile,
  renderESLint,
  renderStylelint,
  renderGitHooks,
  renderPrettier,
  renderPackage,
} = require("../src/index.js");

let projectConfig = {};


async function init(projectName) {
  projectConfig = {
    files: {
      pkg: {
        name: projectName,
        version: "0.0.0",
        description: "",
        main: "index.js",
        scripts: {
          start: "node index.js",
        },
        license: "ISC",
        devDependencies: {
          eslint: "^8.32.0",
          "eslint-config-prettier": "^8.6.0",
          "eslint-plugin-jsdoc": "^39.6.9",
          "eslint-plugin-prettier": "^4.2.1",
        },
      },
    },
  };
  projectConfig.name = projectName
  const cwd = process.cwd();
  projectConfig.cwd = cwd;
  projectConfig.projectPath = `${cwd}\/${projectName}`;

  projectConfig.eslint = await initESLint();
  projectConfig.stylelint = await initStylelint();
  projectConfig.prettier = await initPrettier();
  projectConfig.gitHooks = await initGitHooks();
  initFiles(projectConfig)
  renderTemplate(projectConfig)
}

function initFiles(config) {
  initESLintFile(config);
  initStylelintFile(config);
  initPrettierFile(config);
  initGitHooksFile(config);
}

function renderTemplate(config) {
  const { projectPath } = config;
  if (!fs.existsSync(projectPath)) {
    fs.mkdirSync(projectPath);
  }
  renderESLint(config);
  renderStylelint(config);
  renderPrettier(config);
  renderGitHooks(config);
  renderPackage(config);
}
module.exports = {
  init
}

流程处理

然后就是每个工具对应的流程处理,因为大体相同,这里就以 Stylelint 为例进行说明

询问要使用的 CSS 拓展语言


const styleList = [
  { title: "scss", value: "scss" },
  { title: "less", value: "less" },
];

async function initStylelint() {
  const { css } = await prompts([
    {
      type: "multiselect",
      name: "css",
      message: "Stylelint",
      choices: styleList,
    },
  ]);

  return css.reduce((a, b) => {
    a[[b]] = b;
    return a;
  }, {});
}

提供 lessscss 选择,可多选

准备需要写入的文件信息

function initScssConfig(config) {
  const {
    stylelint: { scss },
  } = config;
  if (scss) {
    config.files.stylelintrc.overrides.push({
      extends: ["stylelint-config-recommended-scss", "stylelint-config-recess-order"],
      files: ["**/*.scss"],
    });
    devDependencies = {
      "stylelint-config-recommended-scss": "^8.0.0",
      "stylelint-scss": "^4.3.0",
      "stylelint-config-standard-scss": "^6.1.0",
    };

    Object.assign(config.files.pkg.devDependencies, devDependencies);
  }
}

function initLessConfig(config) {
  const {
    stylelint: { less },
  } = config;
  if (less) {
    config.files.stylelintrc.overrides.push({
      extends: ["stylelint-config-recommended-less", "stylelint-config-recess-order"],
      customSyntax: "postcss-less",
      files: ["**/*.less"],
    });
    devDependencies = {
      less: "^4.1.3",
      "less-loader": "^11.0.0",
      "postcss-less": "^6.0.0",
      "stylelint-less": "^1.0.6",
    };

    Object.assign(config.files.pkg.devDependencies, devDependencies);
  }
}
function initStylelintFile(config) {
  config.files.stylelintrc = {
    extends: ["stylelint-config-standard", "stylelint-config-recess-order"],
    overrides: [],
    rules: {
      "no-empty-first-line": true,
      "selector-pseudo-class-no-unknown": [
        true,
        {
          ignorePseudoClasses: ["deep"],
        },
      ],
    },
  };
  const devDependencies = {
    "stylelint-config-recess-order": "^3.1.0",
    "stylelint-config-standard": "^29.0.0",
  };

  Object.assign(config.files.pkg.devDependencies, devDependencies);
  initScssConfig(config);
  initLessConfig(config);
}

就是根据选择调整 config 配置

写入文件

async function renderStylelint(config) {
  const { files, projectPath } = config;
  const { stylelintrc } = files;

  writeFile(`${projectPath}/.stylelintrc`, stylelintrc);
}

到这一个可选择配置流程基本就完成了,最后是接入主流程中

const { program } = require('commander');
const prompts = require('prompts')
const { downloadTemplate, useTemplate, canUseTemplatesName } = require('./lib/initTemplate')
const { init } = require('./lib/init')

program.version(require('./package.json').version)
  .option('-n, --name <string>', 'project name')
  .option('-t, --template <string>', 'use template name (support: vue3)')
  .action(async function (cmdArg) {
    let { name, template } = cmdArg

    if (!name) {
      const answer = await prompts([{
        type: 'text',
        name: 'name',
        message: 'Project name:',
        initial: 'project'
      }
      ])
      name = answer.name
    }
    if (canUseTemplatesName.includes(template)) {
      downloadTemplate(template, name)
      return
    }
    const { isUseTemplate } = await prompts([
      {
        type: "toggle",
        name: "isUseTemplate",
        message: "use template?",
        initial: true,
        active: "yes",
        inactive: "no",
      },
    ])
    if (isUseTemplate) {
      useTemplate(name)
      return
    }
    init(name)
    // console.log(cmdArg);
  }).parse(process.argv);


更详细的代码请参考moloch-create

动态模板

到这里,还有一个问题就是,如果需要选择框架如:vue、react等,对应配套的工具也都是变化,这种情况可以使用类似handlebars的库做动态生成的底层模板,篇幅原因,这个就不详细说明了

测试

package.json 需要配置 一些基本信息,如

{
  "name": "m-create",
  "version": "0.0.1",
  "main": "index.js"
}

然后执行 npm link 命令将包 link 到本地全局,执行 npx m-create进行测试

发布

  • 首先注册 npm 账号
  • 登录:npm login
  • 发布:npm publish

因为每次发布需要改版本,这里可以配个简单的脚本,

  • publish.sh
#!/bin/sh
set -e

# 交互式选择发布版本
VERSION=`npx select-version-cli`

# commit
# git add -A
# git commit -am "release: $VERSION"
npm version $VERSION --message "[release] $VERSION"
echo "push... v$VERSION"
# push
git push
# push tag
git push origin v$VERSION

# publish
npm publish

echo "✅ Publish completed"

package.json 增加 scripts 命令:

	"scripts": {
		"pub": "pnpm publish.sh"
	},

每次提交完代码,如果需要发布则执行 npm run pub 发布脚本,会提示版本选择并根据更改版本并自动提交代码,发布前会打上版本 tag

如果 sh 执行不了,可以选择Git Base

工欲善其事必先利其器(制作CLI)

或者可以配置系统变量: 系统属性 > 环境变量 > 系统变量 > Path
工欲善其事必先利其器(制作CLI)

如果还没熟悉整套流程的,尝试过程可能会有各种问题,也可以本地搭建私仓来做发布测试,私仓搭建可以参照 Docker 部署npm私仓 Verdaccio

最后

本篇讲解了一个简单的Cli简易实现,对应Cli已发布至 npm: m-create,可以去尝试看看,因为是未打包发布,所以会提示安装一些依赖,选择 y 即可

附录

制作CLI的相关工具有很多,这里简单列一些仅供参考

上一篇 工欲善其事必先利其器(配置Vue3 + ts项目模板)

原文链接:https://juejin.cn/post/7214318587431059514 作者:摩洛克

(0)
上一篇 2023年3月25日 下午8:00
下一篇 2023年3月25日 下午8:10

相关推荐

发表回复

登录后才能评论