美文网首页
模仿vue-cli,手写一个脚手架

模仿vue-cli,手写一个脚手架

作者: 超人s | 来源:发表于2020-09-24 15:24 被阅读0次

    vue-cli

    在vue的开发的过程中,经常会使用到vue-cli脚手架工具去生成一个项目。在终端运行命令vue create hello-world后,就会有许多自动的脚本运行。

    • 为什么会这样运行呢?
    • 我们自己是否也能写一个脚手架工具?
      带着这样的疑问,我们先来看看vue-cli。

    解读vue-cli

    首先我们可以来到vue-cli的安装目录:
    mac用户来到路径:/usr/local/lib/node_modules 可以看到(windows可以自行到全局安装的目录下查看)


    /usr/local/lib/node_modules.png

    此时用我们的编辑器 打开@vue文件:

    目录结构.png

    lib内放的是具体各种配置和各种类,对于我们来说,这个目录内的就是所谓的业务逻辑。
    bin内放的就是脚本命令的入口,调用lib的入口,入口在package.json内红框定义。我们姑且先放下业务逻辑,来看看这个入口文件。

    现在,我们先放下所有的疑虑,我们打开bin/vue.js。
    我们可以看到以下内容:

    bin_vue_js1.png
    bin_vue_js2.png
    看完之后有什么感觉?咦?怎么跟终端内输出的好像?没错,就是这样,这就是我们使用vue-cli的时候具体的命令。我们去终端输入vue
    发现了吗,终端内的具体命令全在vue.js内定义过了。
    program
      .command('create <app-name>')
      .description('create a new project powered by vue-cli-service')
    program
      .command('add <plugin> [pluginOptions]')
      .description('install a plugin and invoke its generator in an already created project')
    program
      .command('invoke <plugin> [pluginOptions]')
    
    image.png
    好了,剩下代码有兴趣的可以自行打开对应目录读下去,可以学习人家优秀的设计思想。对于本文来说,剩下的许多东西都是业务代码了。我们开头的疑问为什么会这样运行呢?已经了解了一个大概了。我们现在来看看我们自己是否也能写一个脚手架工具?,答案是肯定的。开始动手吧。

    手写一个自己的脚手架

    先来看看可能需要用到哪些npm的包:

    • commander:参数解析
    • inquirer:交互式命令行工具,有他就可以实现命令行的选择功能
    • chalk:输出文本颜色,为了美丽~
      后续可能随着模块的增加,会出现更多需要的包

    1. 创建项目

    npm init -y # 初始化package.json
    

    2. 创建文件目录

    • 在package.json内添加“bin”
    • bin下的文件没有格式,且第一行必须是#! /usr/bin/env node
      文件目录.png

    3. 链接包到全局

    npm link # // 取消链接 npm unlink
    

    有时候可能需要在上面命令后面拼接 --force,mac权限问题记得前面sudo
    可以去目录:/usr/local/lib/node_modules 查看,发现我们多了一个,同时在这时候终端输入一下试试,我们在package.json下叫的name叫superman-cli,所以我们的命令就是叫superman-cli:

    链接全局.png
    验证.png

    好了,基础配置初始化的工作全部结束了!

    4. 第一个命令

    首先安装包commander

    npm install commander --save
    

    在目录bin/superman内

    #! /usr/bin/env node
    
    // console.log(1)
    const program = require('commander')
    
    program
      .version(`Version is ${require('../package.json').version}`)
      .description('从0开始 手写脚手架')
      .usage('<command> [options]')
    
      program
      .parse(process.argv)
    
    测试版本命令.png

    测试有效!

    然后我们在前面Version命令下加入代码:

    program
      .command('create <app-name>')
      .description('create a new project')
      .option('-f, --force', 'Overwrite target directory if it exists')
      .option('-c, --clone', 'Use git clone when fetching remote preset')
      .action((name, cmd) => {
        console.log('name', name)
        console.log('cmd', cmd)
      })
    
    create命令.png
    仔细对比我们的代码合终端的输出,我们就可以看到我们写的很多东西都生效了。接下来我们就优化一下.action下的参数,毕竟一大堆也不好处理:
    program
      .command('create <app-name>')
      .description('create a new project')
      .option('-f, --force', 'Overwrite target directory if it exists')
      .option('-c, --clone', 'Use git clone when fetching remote preset')
      .action((name, cmd) => {
        const options = cleanArgs(cmd)
        console.log(options)
      })
    function camelize (str) {
      return str.replace(/-(\w)/g, (_, c) => c ? c.toUpperCase() : '')
    }
    function cleanArgs (cmd) {
      const args = {}
      // console.log(cmd)
      cmd.options.forEach(o => {
        const key = camelize(o.long.replace(/^--/, ''))
        // console.log(key)
        // console.log(cmd[key])
        // console.log(typeof cmd[key])
        if (typeof cmd[key] !== 'function' && typeof cmd[key] !== 'undefined') {
          args[key] = cmd[key]
        }
      })
      return args
    }
    

    具体不懂的也可以像我注释的console.log一样,慢慢看就明白了


    参数处理.png

    我们第一个命令已经完成一大半了,接下来就是我们这个create命令具体干什么事情。(在这个文件里,我们只管命令,就像vue-cli一样,这也是我们需要学习的地方,模块如何去处理)

    // 在上面.action内补充一行代码
      .action((name, cmd) => {
        const options = cleanArgs(cmd)
        console.log(options)
        require('../lib/create')(name, options)
      })
    

    同时去lib下创建文件create.js

    
    const path = require('path')
    // const fs = require('fs-extra')
    async function create (projectName, options) {
      console.log(projectName, options)
      const cwd = process.cwd(); // 获取当前命令执行时的工作目录
      const targetDir = path.join(cwd,projectName); // 目标目录
      console.log(targetDir)
    }
    
    module.exports = (...args) => {
      return create(...args)
    }
    

    继续执行superman-cli create hello -f,我们可以得到,force:true,如果新建的话,将来的目录会是/Users/chenjing/hello

    image.png
    接下来我们尝试创建目录hello,不过我们需要考虑几个问题:
    • 是否已经存在目录hello了?(使用fs-extra包)
    • 若存在是要删除覆盖还是停止操作?(这里就需要用到插件inquirer啦,进行选择)
    npm install fs-extra --save 
    npm install inquirer --save
    

    直接上代码:

    const path = require('path')
    const fsextra = require('fs-extra')
    const fs = require('fs')
    const Inquirer = require('inquirer')
    async function create (projectName, options) {
      console.log(projectName, options)
      const cwd = process.cwd(); // 获取当前命令执行时的工作目录
      const targetDir = path.join(cwd,projectName); // 目标目录
      console.log(targetDir)
      if (fsextra.existsSync(targetDir)) {
        if (options.force) {// 如果强制创建 ,删除已有的
          await fsextra.remove(targetDir);
          console.log('删除成功')
          createDir(projectName)
        } else {
          let { action } = await Inquirer.prompt([
            {
              name: 'action',
              type: 'list',
              message: 'Target directory already exists Pick an action:',
              choices: [
                {name:'Overwrite',value:'overwrite'},
                {name:'Cancel',value:false}
              ]
            }
          ])
          if (!action) {
            console.log('取消操作')
            return
          } else if (action === 'overwrite') {
            console.log(`\r\nRemoving....`);
            await fsextra.remove(targetDir)
            console.log('删除成功')
            createDir(projectName)
          }
        }
      } else {
        createDir(projectName)
      }
    }
    function createDir (projectName) {
      fs.mkdir(`./${projectName}`, function (err) {
        if (err) {
          console.log('创建失败')
        } else {
          console.log('创建成功')
        }
      })
    }
    
    module.exports = (...args) => {
      return create(...args)
    }
    

    看效果,先来的一个空目录


    空目录.png
    superman-cli create hello
    superman-cli create hello // 再次
    superman-cli create hello -f // 覆盖
    
    create.png 再次create.png 覆盖.png

    4. 小结

    本篇的源码github地址

    并不是说我们手写脚手架就到此结束了,只是要完整实现一个功能不是一两篇文章可以搞定的。不过我相信写到这里,动手能力强的一定也能体验一把手动模仿vue-cli的爽了。至于能写出什么牛C的脚手架,真的就是个人需求和业务代码堆加。当然可以发散思维后续还可以做许多许多事情。强烈建议阅读vue-cli。或者其他脚手架的源码,都在目录:/usr/local/lib/node_modules 下面,看源码真的是学习最直接的方法了,甚至copy人家的代码到自己的cli内执行。其乐无穷

    相关文章

      网友评论

          本文标题:模仿vue-cli,手写一个脚手架

          本文链接:https://www.haomeiwen.com/subject/kmcgyktx.html