阅读create-vite源码轻松编写cli工具

677 阅读2分钟

用create-vite创建过vite项目吗?没的话执行下边的命令感受下

$ npm create vite@latest
$ yarn create vite
$ pnpm create vite

也可以看下我画的图感受下流程图

flow.excalidraw.png

前置库/知识

  1. promptskolorist

    Lightweight, beautiful and user-friendly interactive prompts >_ Easy to use CLI prompts to enquire users for information

    const prompts = require('prompts');
    ​
    (async () => {
      const response = await prompts({
        type: 'number',
        name: 'age',
        message: 'How old are you?',
        validate: value => value < 18 ? `Nightclub is 18+ only` : true
      });
    ​
      console.log(response); // => { value: 24 }
    })();
    

example.gif

  1. kolorist

    Tiny library to put colors into stdin/stdout

    输出颜色,一图胜千言 screenshot.png

  2. minimist

    解析参数 parse argument options

    // 举个例子:执行node index.js npm create
    var argv = require('minimist')(process.argv.slice(2));
    console.log(argv); // { _: [ 'npm', 'creare' ] }
    

模板处理

// 定义 FRAMEWORKS 数据结构
const FRAMEWORKS = [
  {
    name: 'vue',
    color: yellow,
    variants: [
      {
        name: 'vue',
        display: 'JavaScript',
        color: yellow
      },
      {
        name: 'vue-ts',
        display: 'TypeScript',
        color: blue
      }
    ]
  }]
​

tempalte-excalidraw.png

以下代码是用户命令交互的主逻辑,剔除了很多逻辑判断,一目了然

const prompts = require('prompts');
(async () => {
result = await prompts(
    [
      {
        type: 'text',
        name: 'projectName',
        message: 'Project name:',
        initial: 'vite-project',
      },
      {
        type: 'confirm',
        name: 'overwrite',
        message: `vite-project is not empty. Remove existing files and continue?`
      },
      
      {
        type: 'text',
        name: 'packageName',
        message: 'Package name:',
        initial: 'vue',
        // validate: 'Invalid package.json name'
      },
      {
        type: 'select',
        name: 'framework',
        message: `xxx isn't a valid template. Please choose from below: `,
        initial: 0,
        choices:[{ title: 'vue', value: 'vue' },{ title: 'react', value: 'react' }] 
      },
      {
        type: 'select',
        name: 'variant',
        message: 'Select a variant:',
        // @ts-ignore
        choices: [{ title: 'vue', value: 'vue' },{ title: 'vue-ts', value: 'vue-ts' }] 
      }
    ],
    {
      onCancel: () => {
        throw new Error('' + ' Operation cancelled')
      }
    }
  )
})();

运行完上边的代码相信你会有这个感觉,就这,我感觉我也可以了

拷贝逻辑

// 拷贝
function copy(src, dest) {
  const stat = fs.statSync(src);
  if (stat.isDirectory()) {
    copyDir(src, dest);
  } else {
    fs.copyFileSync(src, dest);
  }
}
// 拷贝文件夹
function copyDir(srcDir, destDir) {
  fs.mkdirSync(destDir, { recursive: true });
  for (const file of fs.readdirSync(srcDir)) {
    const srcFile = path.resolve(srcDir, file);
    const destFile = path.resolve(destDir, file);
    copy(srcFile, destFile);
  }
}

其他

const renameFiles = {
  _gitignore: ".gitignore",
};

有一个细节在每个模板下有一个对_gitignore 文件的处理,为什么不直接写成.gitignore,具体原因还不是很清楚,猜测是.文件 在拷贝的时候可能在某些系统下有问题

总结

create-vite 是一个非常简单的cli工具,通过源码我们了解到主要是cli工具内部的实现大致思路

  1. 借助prompts库实现命令行交互
  2. 借助minimist获取输入的参数
  3. 借助kolorist改变文字颜色
  4. 借助fs模块对文件读写