vue-cli源码分析(试探篇)

引言:近期在研究怎么经过node命令生成本身想要的文件夹结构,由于大佬说vue-cli的原理和这个很像,因此抽时间研究了一下,并分享本身的研究心得。但愿个人初学经验对你有用。html


1.vue init命令是怎么实现的

vue-cli能够经过命令vue init webpack my-project运行vue

拿到一个项目配置,首先看的是package.jsonnode

"bin": {
    "vue": "bin/vue",
    "vue-init": "bin/vue-init",
    "vue-list": "bin/vue-list",
    "vue-build": "bin/vue-build"
}复制代码

bin项用来指定各个内部命令对应的可执行文件的位置。因此,vue能够运行文件bin/vue,让咱们看看bin/vue里面是什么?webpack

// file: ./bin/vue
require('commander')
  //版本号
  .version(require('../package').version)
  .usage('<command> [options]')
  .command('init', 'generate a new project from a template')
  .command('list', 'list available official templates')
  .command('build', 'prototype a new project')
  .parse(process.argv)复制代码

因而,找到了commander,8000多star,github地址请点这里 git

正常来讲,command的用法有三个参数,以下github

program
   .command('aaa')
   .description('do something')
   //aaa命令调用是回调action函数
   .action(function(env) {
     console.log('deploying "%s"', env);
   });复制代码

而vue-cli里面并无显示调用action(fn)时,则将会使用子命令模式。Commander 将尝试在入口脚本的目录中搜索可执行文件,(像./bin/vue)与名称 program-command,像 vue-init,vue-build。web

.command('init')命令则会找到同文件夹的vue-init,因此会调用文件"bin/vue-init"并执行。vue-cli

而后vue init就能够解释了,那么vue init webpack my-project 怎么执行?往下看

2.vue-init文件解析

头部模块
#!/usr/bin/env node

//从仓库下载并提取git存储库(GitHub,GitLab,Bitbucket)。
var download = require('download-git-repo')
//主要用于建立子命令和切割命令行参数并执行
var program = require('commander')
//检查文件是否存在
var exists = require('fs').existsSync
//路径模块提供用于处理文件和目录路径的实用程序。 好比路径分割,文件路径格式化,json格式化等
var path = require('path')
//漂亮的loding
var ora = require('ora')
//获取用户主目录的路径
var home = require('user-home')
//绝对路径转换为相对路径
var tildify = require('tildify')
//美化
var chalk = require('chalk')
//经常使用的交互式命令行用户界面的集合。表现是控制台输出提问
var inquirer = require('inquirer')
var rm = require('rimraf').sync
var logger = require('../lib/logger')
//输出信息
var generate = require('../lib/generate')
var checkVersion = require('../lib/check-version')
var warnings = require('../lib/warnings')
var localPath = require('../lib/local-path')

var isLocalPath = localPath.isLocalPath
var getTemplatePath = localPath.getTemplatePath复制代码
主体部分
//help部分太简单跳过
/*** 用法举例: 1.path.relative('/data/orandea/test/aaa', '/data/orandea/impl/bbb'); Returns: '../../impl/bbb' 2.path.resolve('wwwroot', 'static_files/png/', '../gif/image.gif'); 若是当前路径是/home/myself/node, Returns: '/home/myself/node/wwwroot/static_files/gif/image.gif' 3.path.join('/foo', 'bar', 'baz/asdf', 'quux', '..'); Returns: '/foo/bar/baz/asdf' */

/** * 配置 */
//假设是vue init webpack my-project,第一个参数是webpack
var template = program.args[0]
var hasSlash = template.indexOf('/') > -1
var rawName = program.args[1]
var inPlace = !rawName || rawName === '.'

//path.relative()方法根据当前工做目录返回相对路径。 
//若是从每一个解析到相同的路径(在每一个路径上调用path.resolve()以后),返回零长度的字符串。
var name = inPlace ? path.relative('../', process.cwd()) : rawName

//合并路径
var to = path.resolve(rawName || '.')
var clone = program.clone || false
//path.join()方法使用平台特定的分隔符做为分隔符将全部给定的路径段链接在一块儿, 
//而后对结果路径进行规范化。
//home输出举例 => /Users/admin, tmp => /Users/admin/.vue-templates/webpack
var tmp = path.join(home, '.vue-templates', template.replace(/\//g, '-'))
//若是是线下,则template直接取这个路径,不然须要去线上仓库下载
if (program.offline) {
  console.log(`> Use cached template at ${chalk.yellow(tildify(tmp))}`)
  template = tmp
}


/** * Padding. */

console.log()
process.on('exit', function () {
  console.log()
})

if (exists(to)) {
    //询问选择
  inquirer.prompt([{
    type: 'confirm',
    message: inPlace
      ? 'Generate project in current directory?'
      : 'Target directory exists. Continue?',
    name: 'ok'
  }], function (answers) {
    if (answers.ok) {
      run()
    }
  })
} else {
  run()
}复制代码

讲run函数以前,要先了解下generate命令的调用文件--lib/generate文件,里面用到了metalsmith,github地址请点这里 json

/** * Check, download and generate the project. */

function run () {
  // 检查要下载的模版是不是本地路径
  if (isLocalPath(template)) {
    var templatePath = getTemplatePath(template)
    //信息输出
    if (exists(templatePath)) {
      generate(name, templatePath, to, function (err) {
        if (err) logger.fatal(err)
        console.log()
        logger.success('Generated "%s".', name)
      })
    } else {
      logger.fatal('Local template "%s" not found.', template)
    }
  } else {
      //检查版本并输出信息
    checkVersion(function () {
      if (!hasSlash) {
        // use official templates
        var officialTemplate = 'vuejs-templates/' + template
        if (template.indexOf('#') !== -1) {
          downloadAndGenerate(officialTemplate)
        } else {
          if (template.indexOf('-2.0') !== -1) {
            warnings.v2SuffixTemplatesDeprecated(template, inPlace ? '' : name)
            return
          }

          // warnings.v2BranchIsNowDefault(template, inPlace ? '' : name)
          downloadAndGenerate(officialTemplate)
        }
      } else {
        downloadAndGenerate(template)
      }
    })
  }
}

/** * 从模版仓库下载模版 * * @param {String} template */

function downloadAndGenerate (template) {
  //启动控制台loading
  var spinner = ora('downloading template')
  spinner.start()
  // 若是存在本地模版则移除
  if (exists(tmp)) rm(tmp)
  //下载
  download(template, tmp, { clone: clone }, function (err) {
    spinner.stop()
    //日志
    if (err) logger.fatal('Failed to download repo ' + template + ': ' + err.message.trim())
    //控制台打印出模版信息,好比 Generated "my-project".
    generate(name, tmp, to, function (err) {
      if (err) logger.fatal(err)
      console.log()
      logger.success('Generated "%s".', name)
    })
  })
}复制代码

3.总结

vue init webpack my-project 的执行大体以下:

1.经过program.argv拿到参数[webpack,my-project]

2.根据参数经过拼接、path处理等操做,拿到下载模版路径,而后根据本地和在线的区别,作不一样的操做去下载便可。复制代码

以上有说得不对的地方,还请你们多多指教,共同进步。bash

参考资料:

【1】html-js.site/2017/05/26/…

相关文章
相关标签/搜索