参考vue-cli实现自己的命令行工具(demo)

准备工作

  1. 包可以使用命令行,是在bin目录下定义自己的脚本文件, 类型这样
  2. 在package.json中注册bin脚本
{
 "bin": {
    "demo": "bin/demo",
    "demo-init": "bin/demo-init"
  }
}
  1. 安装依赖
{
   "dependencies": {
    "async": "^3.2.0",
    "chalk": "^4.0.0",  // logger用
    "commander": "^5.0.0", // 开发命令行的主要包
    "consolidate": "^0.15.1",
    "handlebars": "^4.7.6", // 模板
    "inquirer": "^7.1.0",
    "metalsmith": "^2.3.0",
    "read-metadata": "^1.0.0", // 读取meta 文件
  }
}

开始开发

  1. 定义主入口文件
    const program = require('commander')
    
    program
        .version('1.0.0')
        .usage('<command> [options]')
        .command('init', '主命令的的-h展示内容') // 使用这种方式, 在使用init命令时,会调用同级目录的demo-init 文件夹
  1. init命令的实现, init命令要实现的是根据用户输入,拼装参数,copy模板操作
    首先定义meta文件,这里以vue-cli的文件为例子
const path = require('path')
const fs = require('fs')

const {
  sortDependencies,
  installDependencies,
  runLintFix,
  printMessage,
} = require('./utils')
const pkg = require('./package.json')

const templateVersion = pkg.version

const { addTestAnswers } = require('./scenarios')

module.exports = {
  metalsmith: {
    // When running tests for the template, this adds answers for the selected scenario
    before: addTestAnswers
  },
  helpers: {
    if_or(v1, v2, options) {

      if (v1 || v2) {
        return options.fn(this)
      }

      return options.inverse(this)
    },
    template_version() {
      return templateVersion
    },
  },

  prompts: { // 定义问题,根据问题保存用户选择
    name: {
      when: 'isNotTest',
      type: 'string',
      required: true,
      message: 'Project name',
    },
    description: {
      when: 'isNotTest',
      type: 'string',
      required: false,
      message: 'Project description',
      default: 'A Vue.js project',
    },
    author: {
      when: 'isNotTest',
      type: 'string',
      message: 'Author',
    },
    build: {
      when: 'isNotTest',
      type: 'list',
      message: 'Vue build',
      choices: [
        {
          name: 'Runtime + Compiler: recommended for most users',
          value: 'standalone',
          short: 'standalone',
        },
        {
          name:
            'Runtime-only: about 6KB lighter min+gzip, but templates (or any Vue-specific HTML) are ONLY allowed in .vue files - render functions are required elsewhere',
          value: 'runtime',
          short: 'runtime',
        },
      ],
    },
    router: {
      when: 'isNotTest',
      type: 'confirm',
      message: 'Install vue-router?',
    },
    lint: {
      when: 'isNotTest',
      type: 'confirm',
      message: 'Use ESLint to lint your code?',
    },
    lintConfig: {
      when: 'isNotTest && lint',
      type: 'list',
      message: 'Pick an ESLint preset',
      choices: [
        {
          name: 'Standard (https://github.com/standard/standard)',
          value: 'standard',
          short: 'Standard',
        },
        {
          name: 'Airbnb (https://github.com/airbnb/javascript)',
          value: 'airbnb',
          short: 'Airbnb',
        },
        {
          name: 'none (configure it yourself)',
          value: 'none',
          short: 'none',
        },
      ],
    },
    unit: {
      when: 'isNotTest',
      type: 'confirm',
      message: 'Set up unit tests',
    },
    runner: {
      when: 'isNotTest && unit',
      type: 'list',
      message: 'Pick a test runner',
      choices: [
        {
          name: 'Jest',
          value: 'jest',
          short: 'jest',
        },
        {
          name: 'Karma and Mocha',
          value: 'karma',
          short: 'karma',
        },
        {
          name: 'none (configure it yourself)',
          value: 'noTest',
          short: 'noTest',
        },
      ],
    },
    e2e: {
      when: 'isNotTest',
      type: 'confirm',
      message: 'Setup e2e tests with Nightwatch?',
    },
    autoInstall: {
      when: 'isNotTest',
      type: 'list',
      message:
        'Should we run `npm install` for you after the project has been created? (recommended)',
      choices: [
        {
          name: 'Yes, use NPM',
          value: 'npm',
          short: 'npm',
        },
        {
          name: 'Yes, use Yarn',
          value: 'yarn',
          short: 'yarn',
        },
        {
          name: 'No, I will handle that myself',
          value: false,
          short: 'no',
        },
      ],
    },
  },
  filters: { // 定义filter,根据用户输入来判断是否要copy文件
    '.eslintrc.js': 'lint',
    '.eslintignore': 'lint',
    'config/test.env.js': 'unit || e2e',
    'build/webpack.test.conf.js': "unit && runner === 'karma'",
    'test/unit/**/*': 'unit',
    'test/unit/index.js': "unit && runner === 'karma'",
    'test/unit/jest.conf.js': "unit && runner === 'jest'",
    'test/unit/karma.conf.js': "unit && runner === 'karma'",
    'test/unit/specs/index.js': "unit && runner === 'karma'",
    'test/unit/setup.js': "unit && runner === 'jest'",
    'test/e2e/**/*': 'e2e',
    'src/router/**/*': 'router',
  },
  complete: function(data, { chalk }) { // 构建完成后,用户选择了install,则自动install项目所需依赖
    const green = chalk.green

    sortDependencies(data, green)

    const cwd = path.join(process.cwd(), data.inPlace ? '' : data.destDirName)

    if (data.autoInstall) {
      installDependencies(cwd, data.autoInstall, green)
        .then(() => {
          return runLintFix(cwd, data, green)
        })
        .then(() => {
          printMessage(data, green)
        })
        .catch(e => {
          console.log(chalk.red('Error:'), e)
        })
    } else {
      printMessage(data, chalk)
    }
  },
}
module.exports = function generate(name, src, dest, done) {
    const opts = getOptions(name, src);
    const metalsmith = Metalsmith(src); // 定义工作目录
    const data = Object.assign(metalsmith.metadata(), {
        destDirName: name,
        inPlace: dest === process.cwd(),
        noEscape: true
    });

    metalsmith.use(askQuestions(opts.prompts)) // 设置询问问题
        .use(filterFiles(opts.filters)) // 根据问题答案,来操作filters
        .use(renderTemplateFiles(opts.skipInterpolation)) // cp模板

    metalsmith
        .clean(false)
        .source('.')
        .destination(dest)
        .build((err, files) => {
            done(err);
            if (typeof opts.complete === 'function') {
                const helpers = { chalk, logger, files }
                opts.complete(data, helpers)
            }
        })
}

猜你喜欢

转载自www.cnblogs.com/clearfix/p/12784885.html