vue-cli是Vue.js官方脚手架命令行工具,咱们能够用它快速搭建Vue.js项目,vue-cli最主要的功能就是初始化项目,既可使用官方模板,也可使用自定义模板生成项目,并且从2.8.0版本开始,vue-cli新增了build
命令,能让你零配置启动一个Vue.js应用。接下来,咱们一块儿探究一下vue-cli是如何工做的。前端
首先,vue-cli是一个node包,且能够在终端直接经过vue
命令调用,因此vue-cli须要全局安装,当npm全局安装一个包时,主要作了两件事:vue
看一下vue-cli的package.json,能够发现以下代码:node
{
"bin": {
"vue": "bin/vue",
"vue-init": "bin/vue-init",
"vue-list": "bin/vue-list",
"vue-build": "bin/vue-build"
}
}
复制代码
这样在全局安装vue-cli后,npm会帮你注册vue
, vue-init
, vue-list
, vue-build
这几个命令。webpack
vue-cli项目自己也不大,项目结构以下:git
.
├── bin
├── docs
├── lib
└── test
└── e2e
复制代码
bin
目录下是可执行文件,docs
下是新特性vue build
的文档,lib
是拆分出来的类库,test
下是测试文件,咱们着重看bin
目录下的文件便可。es6
首先看bin/vue
,内容很简短,只有以下代码:github
#!/usr/bin/env node
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)
复制代码
vue-cli是基于commander.js写的,支持Git-style sub-commands,因此执行vue init
能够达到和vue-init
一样的效果。web
接下来看bin/vue-init
,vue-init
的主要做用是根据指定模板生成项目原型。文件首先是引入一些依赖模块和lib中的辅助函数,由于init命令须要接收至少一个参数,因此vue-init
第一个被执行到的就是检验入参的help
函数,若是没有传入参数,则打印提示,传入参数则继续运行。vue-cli
再向下是解析参数的过程:npm
var template = program.args[0]
var hasSlash = template.indexOf('/') > -1
var rawName = program.args[1]
var inPlace = !rawName || rawName === '.'
var name = inPlace ? path.relative('../', process.cwd()) : rawName
var to = path.resolve(rawName || '.')
var clone = program.clone || false
var tmp = path.join(home, '.vue-templates', template.replace(/\//g, '-'))
if (program.offline) {
console.log(`> Use cached template at ${chalk.yellow(tildify(tmp))}`)
template = tmp
}
复制代码
template
是模板名,第二个参数(program.args[1]
)rawName
为项目名,若是不存在或为.
则视为在当前目录下初始化(inPlace = true
),默认项目名称name
也为当前文件夹名。to
是项目的输出路径,后面会用到。clone
参数判断是否使用git clone的方式下载模板,当模板在私有仓库时用得上。offline
参数决定是否使用离线模式,若是使用离线模式,vue-cli会尝试去~/.vue-templates
下获取对应的模板,能够省去漫长的downloading template
的等待时间,可是模板是否是最新的版本就没法肯定了。
前面在处理参数时会获得一个变量to
,表示即将生成的项目路径,若是已存在,则会输出警告,让用户确认是否继续,确认后执行run
函数:
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
函数。
generate函数是生成项目的核心,主要代码:
module.exports = function generate (name, src, dest, done) {
var opts = getOptions(name, src)
// Metalsmith读取template下全部资源
var metalsmith = Metalsmith(path.join(src, 'template'))
var data = Object.assign(metalsmith.metadata(), {
destDirName: name,
inPlace: dest === process.cwd(),
noEscape: true
})
opts.helpers && Object.keys(opts.helpers).map(function (key) {
Handlebars.registerHelper(key, opts.helpers[key])
})
var helpers = {chalk, logger}
if (opts.metalsmith && typeof opts.metalsmith.before === 'function') {
opts.metalsmith.before(metalsmith, opts, helpers)
}
// 一次使用askQuestions, filterFiles, renderTemplateFiles处理读取的内容
metalsmith.use(askQuestions(opts.prompts))
.use(filterFiles(opts.filters))
.use(renderTemplateFiles(opts.skipInterpolation))
if (typeof opts.metalsmith === 'function') {
opts.metalsmith(metalsmith, opts, helpers)
} else if (opts.metalsmith && typeof opts.metalsmith.after === 'function') {
opts.metalsmith.after(metalsmith, opts, helpers)
}
// 将处理后的文件输出
metalsmith.clean(false)
.source('.') // start from template root instead of `./src` which is Metalsmith's default for `source` .destination(dest) .build(function (err, files) { done(err) if (typeof opts.complete === 'function') { var helpers = {chalk, logger, files} opts.complete(data, helpers) } else { logMessage(opts.completeMessage, data) } }) return data } 复制代码
首先经过getOptions
获取了一些项目的基础配置信息,如项目名,git用户信息等。而后经过metalsmith
结合askQuestions
,filterFiles
,renderTemplateFiles
这几个中间件完成了项目模板的生成过程。**metalsmith**是一个插件化的静态网站生成器,它的一切都是经过插件运做的,这样能够很方便地为其扩展。 经过generate函数的代码,很容易看出来生成项目的过程主要是如下几个阶段。
每一个过程主要用了如下库:
meta.json
或meta.js
,meta.json
是必须的文件,为cli提供多种信息,例如自定义的helper,自定义选项,文件过滤规则等等。该如何写一个自定义模板,能够参考这里/* eslint-disable no-new */
new Vue({
el: '#app',
{{#router}}
router,
{{/router}}
{{#if_eq build "runtime"}}
render: h => h(App){{#if_eq lintConfig "airbnb"}},{{/if_eq}}
{{/if_eq}}
{{#if_eq build "standalone"}}
template: '<App/>',
components: { App }{{#if_eq lintConfig "airbnb"}},{{/if_eq}}
{{/if_eq}}
}){{#if_eq lintConfig "airbnb"}};{{/if_eq}}
复制代码
vue-init
的整个工做流程大体就是这样,vue-cli
做为一个便捷的命令行工具,其代码写的也简洁易懂,并且经过分析源码,能够发现其中用到的不少有意思的模块。
vue-list功能很简单,拉取vuejs-templates的模板信息并输出。
vue-build则是经过一份webpack配置将项目跑起来,若是是入口仅是一个.vue
组件,就使用默认的default-entry.es6
加载组件并渲染。
在看vue-cli源码时,发现了user-home这个模块,这个模块的内容以下:
'use strict';
module.exports = require('os-homedir')();
复制代码
os-homedir
这个包是一个os.homedir
的polyfill,在Why not just use the os-home module?下,我看到了Modules are cheap in Node.js这个blog。事实上sindresorhus写了不少的One-line node modules,他也很喜欢One-line node moduels,由于模块越小,就意味着灵活性和重用性更高。固然对于One-line modules,每一个人的见解不同,毕竟也不是第一次听到**“就这一个函数也tm能写个包”**的话了。我认为这个要因人而异,sindresorhus何许人也,不少著名开源项目的做者,发布的npm包1000+,大多数他用到的模块,都是他本身写的,因此对他来讲,使用各类“积木”去组建“高楼”驾轻就熟。不过对于其余人来讲,若是习惯于这种方式,可能会对这些东西依赖性变强,就像如今不少前端开发依赖框架而不重基础同样,因此我认为这种“拼积木”开发方式挺好,但最好仍是要知其因此然。可是我感受One-line modules的做用却不大,就像user-home这个模块,若是没有它,const home = require('os-homedir')();
也能够达到目的,可能处于强迫症的缘由,user-home才诞生了吧,并且像negative-zero这样的One-line modules,使用场景少是其一,并且也没带来什么方便,尤为是2.0版本,这个包直接使用Object.is去判断了:
'use strict';
module.exports = x => Object.is(x, -0);
复制代码
不知道你们对One-line modules是什么见解?