【源码】Vue-cli2 源码阅读与改造

前言

最近组里让我写个脚手架,用脚手架来生成模板项目。比如每个公司都会有很多后台管理系统,用于给产品、客服等内部人员使用。对于一个新的管理后台,如果每次都从头开始写,必然浪费太多时间。或者copy以前的项目进行改造又不太优雅。如果写一个像vue-cli这样的脚手架进行命令行创建那再好不过了。

一开始我感觉会有点难,像vue-cli这样的项目,经历了很多次的提交,代码量不少。我之前的想象是,vue-cli中进行许多的目录、文件的读写与创建,用户的交互,各种npm包的下载,脚本的运行,最终才生成了项目。想想就有点乱。

但看过后发现变简单多了。总的来说,vue-cli分两部分,一部分为vue-cli,vue-cli只是一个拉取仓库的作用,一个下载的作用。另一部分就是真正的模板了。这个模板也是github上的项目,按照一定的规范进行编写。

正文

这次看的是 vue-cli2,vue-cli3进行了很多封装,既然只需要其拉取仓库的作用,那么vue-cli2就够了。其目录结构如图:

image.png

网上也有许多vue-cli2的源码解析,这里只是简单说明。
首先从package.json开始,查看一些命令。也许用了vue-cli3,很多人已经忘记vue-cli2的命令了。在package.json中有:

  "bin": {
    "vue": "bin/vue",
    "vue-init": "bin/vue-init",
    "vue-list": "bin/vue-list"
  }

"bin" 字段的作用是能让我们在命令窗口全局输入命令执行。

下面将以 vue init webpack my-project 为例。说明输入命令后发生了什么。

输入命令后,此时将运行bin文件夹下的vue-init脚本:


image.png

vue-init主要看这几个部分:

第一部分

首先第一句是必须的,这样才能在全局下运行命令。

#!/usr/bin/env node
第二部分

首先要知道的是,模板可以从各种地方下载,而不仅仅是官方提供的那几个(虽然许多时候用不到,但其的确提供了)。这一部分主要是获取命令中的参数,从而得出模板、项目名等信息。进而确定如何去获取模板。

let template = program.args[0]  // 获取模板名称: webpack
const hasSlash = template.indexOf('/') > -1   // 模板名称是否有斜杠 (这里为false)
const rawName = program.args[1]  // 项目名称: my-project
const inPlace = !rawName || rawName === '.'
const name = inPlace ? path.relative('../', process.cwd()) : rawName
const to = path.resolve(rawName || '.')
const clone = program.clone || false // 是否采用 clone 模式,默认 http 方式下载模版

const tmp = path.join(home, '.vue-templates', template.replace(/[\/:]/g, '-')) // 将模板下载到该路径,并非直接下载到创建项目的路径(缓存)
if (program.offline) { // 是否使用离线模版(使用上一行代码中缓存下的)
  console.log(`> Use cached template at ${chalk.yellow(tildify(tmp))}`)
  template = tmp
}
第三部分
function run () {
  // check if template is local
  if (isLocalPath(template)) { // 是否使用本地模版 - 判断模版路径是否是本地
    const templatePath = getTemplatePath(template)
    if (exists(templatePath)) {
      generate(name, templatePath, to, err => {
        if (err) logger.fatal(err)
        console.log()
        logger.success('Generated "%s".', name)
      })
    } else {
      logger.fatal('Local template "%s" not found.', template)
    }
  } else {
    checkVersion(() => {
      if (!hasSlash) {
        // use official templates
        const 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)
      }
    })
  }
}

确定好命令中各种参数后,然后就根据参数进行下载了。首先isLocalPath(template) 判断参数是否是一个路径,如 ../../webpack,而我们输入是参数为 webpack。则走到 else 逻辑。else 中首先进行版本检查: checkVersion,这就是如果有新版本的vue-cli了,将在命令行中询问我们是否下载。

由于没有斜杠(hasSlash),且不带版本号,最终走到了 downloadAndGenerate(officialTemplate) 去下载官方模板,其中经过拼接后officialTemplate的值为: vuejs-templates/webpack。至于 downloadAndGenerate(template)是自定义模板,后面再讲。

第四部分
function downloadAndGenerate (template) {
  const spinner = ora('downloading template')
  spinner.start()
  // Remove if local template exists
  if (exists(tmp)) rm(tmp)
  download(template, tmp, { clone }, err => {
    spinner.stop()
    if (err) logger.fatal('Failed to download repo ' + template + ': ' + err.message.trim())
    generate(name, tmp, to, err => {
      if (err) logger.fatal(err)
      console.log()
      logger.success('Generated "%s".', name)
    })
  })
}

downloadAndGenerate 方法没什么内容,主要看其中的 download 方法。download 是通过一个npm包 download-git-repo
引入的 :const download = require('download-git-repo')。download 方法也没有什么内容,主要是根据url去下载(http)或者 clone项目。

那么如何根据传入的 officialTemplate: vuejs-templates/webpack 就能得出URL呢,主要在于 normalize:

image.png

最终得出的URL为:https://github.com/vuejs-templates/webpack。就这样,我们终于找到官方模板的位置了,总的来说vue-cli只起了一个下载的作用,而真正的模板隐藏于此!这也就是命令 vue init webpack my-projectwebpack 参数的作用。在vuejs-templates 这个用户下,我们还可以看到其他几个模板,如当运行 vue init pwa my-project 时将下载pwa模板。

image.png

还记得第三部分中说到的 downloadAndGenerate(template)逻辑吗?那是有斜杆/ 时将去下载自定义模板。
也就是说,如果运行命令 vue init my-templates/webpack my-project,它将到 my-templates 这个用户下寻找自定义的模板进行下载!

这个自定义模板编写是具有一定规范,具体可以看 https://github.com/vuejs/vue-cli/tree/v2#custom-templates

第五部分

在执行完 download 后,这是将模板下载完成而已。这个模板不是直接就用的,还记得我们创建项目时在命令窗口进行的询问与选择吗:

项目的作者?项目的描述?是否选择eslint?eslint的版本?等等。那么如何根据模板来生成自己最终的项目呢?

可以看到后面再调用 generate 进行项目的生成:

generate(name, tmp, to, err => {
      if (err) logger.fatal(err)
      console.log()
      logger.success('Generated "%s".', name)
    })

generate中主要使用了 Metalsmith 这个静态网站生成器的插件,这个插件配合https://github.com/vuejs/vue-cli/tree/v2#custom-templates 规范进行生成项目。具体怎么使用看其文档即可了。

限于篇幅,vue-cli的源码阅读就到这了,网上也有很多分析。下面说明如何进行简单的改造就可以使vue-cli为自己所用。也就是如何从公司gitlab进行拉取模板。

vue-cli已经提供了不同平台下载模板的方式:github、gitlab、bitbucket。默认是从github下载模板,如从gitlab下载则运行:

vue init gitlab:username/repo my-project

而我们是要从公司的gitlab上拉取,公司gitlab的域名是这样组成的gitlab.xxxx.com。需要我们进行改造。在第三部分的run方法中,将 officialTemplate 的拼接改为:

const officialTemplate = `gitlab:gitlab.xxxx.com:username/${template}`

那么,当你运行vue init webpack my-project时,将默认从你公司的gitlab上拉取模板。那接下来就是如何编写模板的事了,按照https://github.com/vuejs/vue-cli/tree/v2#custom-templates 去做吧。

结束语

9月30日:文章是之前写的,之前不给发。祝大家国庆快乐吧!

你可能感兴趣的:(【源码】Vue-cli2 源码阅读与改造)