介绍

脚手架名称”@xxxx/lite”, 能够通过命令行自动化完成项目的初始化工作,节约开发者投入在项目配置、规范化、编译打包、项目layout处理等一系列初始化工作,大大提升开发效率,该篇文章主要通过功能&结构介绍cli的使用以及开发思路。

项目结构

快速生成文档目录结构,请安装tree https://blog.csdn.net/qq_20042935/article/details/125058490, 不嫌麻烦手写也行

# 安装 tree
brew install tree

# 在目标目录下执行, -I 用于忽略文件可用,链接 > 后是要生成的文件
tree -I node_modules > tree.md

切换了源就找不到该插件了

使用GitHub源又贼慢!!!

安装使用

# 全局安装需要提前切换npm源 到 name http://npm.xxxx.com/
# 没添加源 需要手动添加 nrm add name http://npm.xxxx.com/
nrm use name

# 全局安装
npm i -g package.name

# 创建项目
lite create xxxx

# 创建项目 并初始化仓库地址 或者 lite create 项目名称 --register 你的仓库地址
lite create 项目名称 -r 你的仓库地址

# 强制覆盖 也可用 --force
lite create 已有项目 -f

# 项目合并 也可用 --merge
lite create 已有项目名称 -m

# 查看版本
lite -V

主要插件

  • chalk: 用于多种颜色的日志输出
  • commander: 用于命令配置和参数解析
  • fs-extra: 文件操作相关工具库
  • glob: node的glob模块允许你使用 *等符号, 来写一个glob规则,像在shell里一样,获取匹配对应规则的文件.,这个glob工具基于javascript.它使用了 minimatch 库来进行匹配。我们可以利用它匹配出我们想要的目录下的一些文件
  • leven: 测量两字符串之间的差异<br/>最快的JS实现之一, 本项目 内主要是做输入建议
  • path:Node.js path 模块提供了一些用于处理文件路径的小工具,本项目主要是文件路径链接用于写入文件
  • shelljs: 用于调用系统命令,例如 cd、cp、ls、rm 、git等,兼容 Windows 和 Linux
  • validate-npm-package-name:作用就是验证项目名称 (npm 包名) 是否合法,很多的 cli 工具都有使用。
  • yargs-parser:将命令行参数转换为json
  • yeoman-generator:generator模块从而实现创建我们所需的项目结构
  • inquirer:  预设参数交互工具

功能列表

  1. 命令配置
  2. 预设参数
  3. 自定义模板
  4. 执行模板的相关信息写入,模板导入目标文件夹

命令配置

  1. 创建命令 create
    1. <app-name> 创建项目的目标文件夹名称
    2. -r <url>|| –registry <url> 项目初始化git地址
    3. -f || –force 强制覆盖本地相同文件夹内的文件
    4. -m || –merge 合并本地相同文件夹名称的文件
  2. 帮助命令 –help | -h
  3. 版本命令 –version | -V

使用commander,已创建命令为例

// es5
const program = require('commander')

program
  .command("create <app-name>")
  .description("使用xxxx cli创建一个项目")
  .option("-r", "--registry <url>", "项目初始化仓库地址")
  .option("-f", "--force", "强制覆盖目标文件夹的文件")
  .option("-m", "--merge", "合并已存在目标文件夹的文件")
  .action((name, cmd) => {
			// 执行命令回调
  });

预设参数

  1. 项目title
  2. 项目系统名称可以理解为字母+下划线的名称
  3. 项目初始版本
  4. 项目描述

预设参数主要使用inquirer进行维护

例如需要一个输入文本

// inquirer是一个异步函数,返回promise
const inquirer = require('inquirer')

inquirer.prompt([{
  type: "input",
  message: "系统简称:",
  name: "systemName",
  validate: (val) => {
    if (val && val.trim()) {
      if (/[A-Za-z_][A-Za-z_0-9]/.test(val)) {
        return true;
      } else {
        return "请输入数字字母下划线、横线组成的name";
      }
    }
    return "请输入项目系统名称例如project_name";
  },
}])

使用教程传送门:https://www.npmjs.com/package/inquirer

执行模板拉去动作

主流程如下:

  1. 读取预设参数
  2. shell执行clone流程
  3. 无权限读取则从本地拉取数据
  4. 将文件导入目标文件夹
  5. 进入目标文件夹,写入预设参数主要是package.json 以及 .env

shelljs命令传送门: http://shouce.jb51.net/shell/

const shell = require('shelljs')
// 构建流程create 是一个函数,此操作属于函数内部操作
const args = await inquirer.prompt(promptList)

// shell 执行git拉取
shell.exec(
`
	# 从项目模板的master分支拉取
	git clone ${remote}
`, (error, stdout, stderr) => {
  // error 错误码, stdout 进程码, stderr 退出码
  // 克隆鉴权失败
  if (err !== 0) {
    // 执行本地备份文件拷贝
    createByBackUp(targetDir, args, projectName)
    return
  }
  
  shell.exec(
    `
      // 删除模板git
      rm -rf ${targetDir}/.git
      // 进入目标文件夹
      cd ${targetDir}
      echo '📦  Installing additional dependencies...'
			// 静默初始化git
      git init -q
			// 如果输入了 -r 新项目git地址
      ${options.registry && "git remote add origin " + options.registry}
      // 判断优先使用yarn
      ${hasYarn() ? "yarn" : "npm i"}
    `, (err, stdout, stderr) => {
          if (err !== 0) {
            createByBackUp(targetDir, actions, projectName);
            return;
          }
          // 写入配置文件信息
          console.log(`✨  Writing configuration information to the project.`);
          await writeConfig(targetDir, actions);

          console.log(
            `🎉  Successfully created project ${chalk.yellow(name)}.`
          );

          console.log(
            `👉  Get started with the following commands:\n\n` +
              (targetDir === process.cwd()
                ? ``
                : chalk.cyan(` ${chalk.gray("$")} cd ${name}\n`)) +
              chalk.cyan(` ${chalk.gray("$")} yarn dev`)
          );
        }
  )
}
)

本分文件写入

const createByBackUp = (targetDir, actions, name) => {
  console.log(`🍊  使用备份数据创建 ${chalk.yellow(targetDir)}.`);
  // 创建根目录
  mkdirp.sync(name);
  const generator = new Generator({
    name,
    env: { cwd: targetDir },
    resolved: require.resolve("../lib/generators"),
  });

  generator.run(async () => {
    await writeConfig(targetDir, actions);

    console.log(`🎉  Successfully created project ${chalk.yellow(name)}.`);

    console.log(
      `👉  Get started with the following commands:\n\n` +
        (targetDir === process.cwd()
          ? ``
          : chalk.cyan(` ${chalk.gray("$")} cd ${name}\n`)) +
        chalk.cyan(` ${chalk.gray("$")} yarn \n`) +
        chalk.cyan(` ${chalk.gray("$")} yarn serve`)
    );
  });
};

依赖管理器判断

const { execSync } = require('child_process')

let _hasYarn
exports.hasYarn = () => {
    if (_hasYarn != null) {
      return _hasYarn
    }
    try {
      execSync('yarn --version', { stdio: 'ignore' })
      return (_hasYarn = true)
    } catch (e) {
      return (_hasYarn = false)
    }
}

项目结构构建器

const Generator = require('yeoman-generator');
const glob = require('glob');
const { statSync } = require('fs');
const { basename } = require('path');

module.exports = class extends Generator {
  constructor(opts) {
    super(opts);
    this.name = basename(opts.env.cwd);
  }

  writing() {
    glob
      .sync('**/*', {
        cwd: this.templatePath(),
        dot: true
      })
      .forEach(file => {
        const filePath = this.templatePath(file);
        if (statSync(filePath).isFile()) {
          this.fs.copyTpl(
            this.templatePath(filePath),
            this.destinationPath(file.replace(/^_/, '.')),
            { name: this.name }
          );
        }
      });
  }
};

TODO

  • 优先使用pnpm资源管理
  • 使用ts重构
  • 轻量化项目结构

发表回复

您的电子邮箱地址不会被公开。