造轮子-通过阅读vue-cli 1.0.0源码,我开发了自己的脚手架

242 阅读3分钟

笔者最早接触脚手架要追溯到2020年,当时gitchat这个网站还在,而且有点如日中天的意思。某天我恰好冲浪到这个网站,之后还在上面分享了一些东西,赚了些喝咖啡的钱。不过貌似现在这个网站已经关闭了,哎,还有些许遗憾。

也是在gitchat上我正式开始接触脚手架,犹记得是花钱买的一个小课,作者是快狗打车的架构师。课程的最后就是讲脚手架的实现。

时间慢慢来到现在,最近几年除了开发也会整理一些东西,也想着真正实现一些自己的开发上的想法:其中之一就有脚手架。在我整理的过程中我发现一件有趣的事情:当初gitchat脚手架的源代码其实来自vue-cli的源码,当然不是vue-cli 1.0.0版的源代码,而是差不多2.0.0前后的源代码。

大家可以自行查看vue-cli早期尤其3.0.0的之前的源代码,3.0.0是一个分界线。查看地址:github.com/vuejs/vue-c…,之前是单独一个仓库管理,3.0.0开始使用lerna,也开始变得很复杂。

vue-cli 1.0.0源码是非常简单的,然后随着迭代逐步复杂,或许认识到这可以给予我们信心:万丈高楼平地起,只要敢于开始,坚持下去或许也可以写一些复杂应用。

天下文章一大抄,代码多少有些相似。就开发这件事来说,普通人都是从模仿或者就是从抄开始的,但有想法有进取心的程序员绝不能止步于抄,在我看来要有自己的想法,要能够迭代抄,或者至少要判断出抄来的适不适合自己。

本文这个脚手架是借鉴了或者就是抄了vue-cli的早期源代码,但也根据自己的实际需求做了一些改变:模板位置本地化,模板多样化;可直接选择依赖管理工具,可以选择多种工程模板。

zhen-cli介绍

zhen-cli是脚手架的名字。其核心有几个关键的库:

  1. commander nodejs命令行框架主要用来注册命令行命令
  2. chalk 设置命令行输出字符串颜色
  3. inquirer 与命令行界面进行交互操作
  4. consolidate 模板引擎库引来渲染数据
  5. metalsmith 静态网站生成器
  6. execa 执行命令行命令比如 npm install

核心流程

流程.png

下面逐一介绍

可全局安装体验:

npm install zhen-cli -g

1,首先输入命令行命令

zhen create test-project

通过commander拿到项目名称:test-project

 /**
     * Help. 
     */

   program.on('--help', function () {
    console.log('  Examples:')
    console.log()
    console.log(chalk.gray('    # create a new project with a template'))
    console.log('    $ zhen create [projectName]')
    console.log()
  })

    /**
     * Help.如果只输入命令本身会返回帮助文档。
     */
    function help() {
        program.parse(process.argv);
        if (program.args.length < 1) return program.help();
    }
    help();

这里如果只输入zhenzhen create会在命令行显示help内容。

   /**
     * Settings.
     */
    // 项目名叫什么 第二个参数作为目录名
    const rawName = program.args[0];
    // 是否在当前目录 默认my-project
    const inPlace = rawName === ".";
    // 模板最终会下载到什么地方
    const to = path.resolve(rawName);

    process.on("exit", () => {
        console.log();
    });
    // 第一步:询问用户是否是在当前目录下创建项目; 如果要存放的目录已经存在提示已存在
    if (inPlace || exists(to)) {
        // 异步交互 和命令行工具进行交互 简单交互 如果是真则执行下一步 
        // inquirer这是一个简单功能
        inquirer
            .prompt([{
                type: "confirm",
                message: inPlace ? "在当前目录创建项目?" : "目录已经存在,仍要继续?",
                name: "ok"
            }])
            .then(answers => {
                if (answers.ok) {
                    run();
                }
            })
            .catch(logger.fatal);
    } else {
        // console.log(home, tmp, to)
        run();
    }

如果输入zhen create .,会在当前目录生成工程文件

2,选择配置

image.png

依赖管理支持:npm,cnpm,yarn。模板支持:vue2-js、react-js、express-js,后期根据实际需要添加

3,下载远程模板/使用本地模板

最早获取模板我也采用的是从github仓库下载的方式,使用download-git-repo。使用这种方式有一些问题。由于网络问题,有时候下载会不成功。模板和脚手架分属不同的git仓库,管理起来比较麻烦。后来我索性直接将模板放在脚手架里面。

download-git-repo下载的实现方式可以参考文末其他相关文章。

4,渲染数据生成文件

渲染数据使用的是metalsmith结合handlebarsmetalsmith是静态网站生成器,支持插件对数据进行过滤

  metalsmith
    .use(askQuestions(opts.prompts))
    .use(filterFiles(opts.filters))
    .use(renderTemplateFiles)
    .clean(false)
    .source(".")
    .destination(dest)
    .build(err => {
      done(err);
      console.log('\n正在下载依赖...\n')
      
      const PACKAGE_MANAGER_CONFIG = {
        npm: ['install', '--loglevel', 'error'],
        cnpm: ['install', '--loglevel', 'error'],
        yarn: []
      }
      executeCommand(tool, PACKAGE_MANAGER_CONFIG[tool], path.join(process.cwd(), name)).then(()=>{
        // createSuccessInfo(name)
        logMessage(opts.completeMessage, data);
      })
    });

这块使用三个插件:

  1. askQuestions询问是项目名称、项目作者、项目描述等
  2. filterFiles过滤掉指定文件,比如不使用vuex的话,会直接过滤掉相关的文件
  3. renderTemplateFiles渲染模板数据

5,下载依赖

使用execa进行依赖下载,执行选择依赖管理工具对应的命令下载依赖。


function executeCommand (command,args,cwd) {
 
  return new Promise((resolve, reject) => {
  
    const child = execa(command, args, {
      cwd,
      stdio: ['inherit', 'inherit', command === 'yarn' ? 'pipe' : 'inherit']
    })

    if (command === 'yarn') {
      child.stderr.on('data', buf => {
        const str = buf.toString()
        if (/warning/.test(str)) {
          return
        }

        // progress bar
        const progressBarMatch = str.match(/\[.*\] (\d+)\/(\d+)/)
        if (progressBarMatch) {
          // since yarn is in a child process, it's unable to get the width of
          // the terminal. reimplement the progress bar ourselves!
          renderProgressBar(progressBarMatch[1], progressBarMatch[2])
          return
        }

        process.stderr.write(buf)
      })
    }

    child.on('close', code => {
      if (code !== 0) {
        reject(new Error(`command failed: ${command} ${args.join(' ')}`))
        return
      }
      resolve()
    })
  })
}

6,仓库地址

github.com/zhensg123/z…

最后适合自己的才最好。最新的vue-cli源代码已经不再采用3.0.0之前的模板管理方案:模板是静态的。且最新实现的vue-cli功能多样,对模板的拆分很细,模板根据配置选择动态生成。按照这种方式管理多种模板会很复杂。

目前的这个脚手架可以对多个框架下的模板进行生成管理,且模板可以扩展,已经满足了当初的设计需求,那么这样就不错了。

掘金脚手架相关文章

别人休息我努力,悄悄写个 cli 工具,必须提升效率,skr~

🛠如何快速开发一个自己的项目脚手架?

【中高级前端必备】手摸手教你撸一个脚手架

【手把手】15分钟搭一个企业级脚手架

手把手教你写一个脚手架