2021【前端VUE框架】最新/最全/最细实战课程,真实的模块开发,一步一不到实战脚手架开发
学习目标:
提示:2021【前端VUE框架】最新/最全/最细实战课程,VUE56节进阶学习
重磅来袭:3个月掌握 vue 入门知识到实战学代码。
学习内容:
提示:本节第17节内容:vue-cli知识点
学习本节视频需要掌握的知识点:
1.HTML
2.CSS
3.JavaScript
那么关于上面三个知识点,视频学习地方在:html5+css3+js+jquery课程
学习时间:
提示:2020现在-2021.1月
一。无基础的同学,有充足的事件可以一天学习两节内容,要多写代码,一个案例和知识点最少要敲代码3到4遍:
二。有基础的同学可以自由安排事件哈:
1、 周一至周五晚上 7 点—晚上9点
2、 周六上午 9 点-上午 11 点
3、 周日下午 3 点-下午 6 点
学习产出:
提示:在网站上
例如:
1、 技术笔记 2 遍 ,在你自己的电脑学代码。
2、tofacebook.com, 发布技术文章 1 篇
本节知识点内容如下:
背景
在平时工作中会有遇到许多以相同模板定制的小程序,因此想自己建立一个生成模板的脚手架工具,以模板为基础构建对应的小程序,而平时的小程序都是用mpvue框架来写的,因此首先先参考一下Vue-cli的原理。知道原理之后,再定制自己的模板脚手架肯定是事半功倍的。
在说代码之前我们首先回顾一下Vue-cli的使用,我们通常使用的是webpack模板包,输入的是以下代码。
vue init webpack [project-name]
在执行这段代码之后,系统会自动下载模板包,随后会询问我们一些问题,比如模板名称,作者,是否需要使用eslint,使用npm或者yarn进行构建等等,当所有问题我们回答之后,就开始生成脚手架项目。
我们将源码下载下来,源码仓库点击这里,平时用的脚手架还是2.0版本,要注意,默认的分支是在dev上,dev上是3.0版本。
我们首先看一下package.json,在文件当中有这么一段话
{
"bin": {
"vue": "bin/vue",
"vue-init": "bin/vue-init",
"vue-list": "bin/vue-list"
}
}
由此可见,我们使用的命令 vue init,应该是来自bin/vue-init这个文件,我们接下来看一下这个文件中的内容
bin/vue-init
const download = require('download-git-repo')
const program = require('commander')
const exists = require('fs').existsSync
const path = require('path')
const ora = require('ora')
const home = require('user-home')
const tildify = require('tildify')
const chalk = require('chalk')
const inquirer = require('inquirer')
const rm = require('rimraf').sync
const logger = require('../lib/logger')
const generate = require('../lib/generate')
const checkVersion = require('../lib/check-version')
const warnings = require('../lib/warnings')
const localPath = require('../lib/local-path')
download-git-repo 一个用于下载git仓库的项目的模块
download-git-repo 一个用于下载git仓库的项目的模块
commander 可以将文字输出到终端当中
download-git-repo 一个用于下载git仓库的项目的模块
commander 可以将文字输出到终端当中
fs 是node的文件读写的模块
download-git-repo 一个用于下载git仓库的项目的模块
commander 可以将文字输出到终端当中
fs 是node的文件读写的模块
path 模块提供了一些工具函数,用于处理文件与目录的路径
download-git-repo 一个用于下载git仓库的项目的模块
commander 可以将文字输出到终端当中
fs 是node的文件读写的模块
path 模块提供了一些工具函数,用于处理文件与目录的路径
ora 这个模块用于在终端里有显示载入动画
download-git-repo 一个用于下载git仓库的项目的模块
commander 可以将文字输出到终端当中
fs 是node的文件读写的模块
path 模块提供了一些工具函数,用于处理文件与目录的路径
ora 这个模块用于在终端里有显示载入动画
user-home 获取用户主目录的路径
download-git-repo 一个用于下载git仓库的项目的模块
commander 可以将文字输出到终端当中
fs 是node的文件读写的模块
path 模块提供了一些工具函数,用于处理文件与目录的路径
ora 这个模块用于在终端里有显示载入动画
user-home 获取用户主目录的路径
tildify 将绝对路径转换为波形路径 比如/Users/sindresorhus/dev → ~/dev
download-git-repo 一个用于下载git仓库的项目的模块
commander 可以将文字输出到终端当中
fs 是node的文件读写的模块
path 模块提供了一些工具函数,用于处理文件与目录的路径
ora 这个模块用于在终端里有显示载入动画
user-home 获取用户主目录的路径
tildify 将绝对路径转换为波形路径 比如/Users/sindresorhus/dev → ~/dev
inquirer 是一个命令行的回答的模块,你可以自己设定终端的问题,然后对这些回答给出相应的处理
download-git-repo 一个用于下载git仓库的项目的模块
commander 可以将文字输出到终端当中
fs 是node的文件读写的模块
path 模块提供了一些工具函数,用于处理文件与目录的路径
ora 这个模块用于在终端里有显示载入动画
user-home 获取用户主目录的路径
tildify 将绝对路径转换为波形路径 比如/Users/sindresorhus/dev → ~/dev
inquirer 是一个命令行的回答的模块,你可以自己设定终端的问题,然后对这些回答给出相应的处理
rimraf 是一个可以使用 UNIX 命令 rm -rf的模块
download-git-repo 一个用于下载git仓库的项目的模块
commander 可以将文字输出到终端当中
fs 是node的文件读写的模块
path 模块提供了一些工具函数,用于处理文件与目录的路径
ora 这个模块用于在终端里有显示载入动画
user-home 获取用户主目录的路径
tildify 将绝对路径转换为波形路径 比如/Users/sindresorhus/dev → ~/dev
inquirer 是一个命令行的回答的模块,你可以自己设定终端的问题,然后对这些回答给出相应的处理
rimraf 是一个可以使用 UNIX 命令 rm -rf的模块
剩下的本地路径的模块其实都是一些工具类,等用到的时候我们再来讲
// 是否为本地路径的方法 主要是判断模板路径当中是否存在 `./`
const isLocalPath = localPath.isLocalPath
// 获取模板路径的方法 如果路径参数是绝对路径 则直接返回 如果是相对的 则根据当前路径拼接
const getTemplatePath = localPath.getTemplatePath
/**
* Usage.
*/
program
.usage('<template-name> [project-name]')
.option('-c, --clone', 'use git clone')
.option('--offline', 'use cached template')
/**
* Help.
*/
program.on('--help', () => {
console.log(' Examples:')
console.log()
console.log(chalk.gray(' # create a new project with an official template'))
console.log(' $ vue init webpack my-project')
console.log()
console.log(chalk.gray(' # create a new project straight from a github template'))
console.log(' $ vue init username/repo my-project')
console.log()
})
/**
* Help.
*/
function help () {
program.parse(process.argv)
if (program.args.length < 1) return program.help()
}
help()
这部分代码声明了vue init用法,如果在终端当中 输入 vue init --help或者跟在vue init 后面的参数长度小于1,也会输出下面的描述
Usage: vue-init <template-name> [project-name]
Options:
-c, --clone use git clone
--offline use cached template
-h, --help output usage information
Examples:
# create a new project with an official template
$ vue init webpack my-project
# create a new project straight from a github template
$ vue init username/repo my-project
接下来是一些变量的获取
/**
* Settings.
*/
// 模板路径
let template = program.args[0]
const hasSlash = template.indexOf('/') > -1
// 项目名称
const rawName = program.args[1]
const inPlace = !rawName || rawName === '.'
// 如果不存在项目名称或项目名称输入的'.' 则name取的是 当前文件夹的名称
const name = inPlace ? path.relative('../', process.cwd()) : rawName
// 输出路径
const to = path.resolve(rawName || '.')
// 是否需要用到 git clone
const clone = program.clone || false
// tmp为本地模板路径 如果 是离线状态 那么模板路径取本地的
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
}
接下来主要是根据模板名称,来下载并生产模板,如果是本地的模板路径,就直接生成。
/**
* Check, download and generate the project.
*/
function run () {
// 判断是否是本地模板路径
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) {
// 拼接路径 'vuejs-tempalte'下的都是官方的模板包
const officialTemplate = 'vuejs-templates/' + template
// 如果路径当中存在 '#'则直接下载
if (template.indexOf('#') !== -1) {
downloadAndGenerate(officialTemplate)
} else {
// 如果不存在 -2.0的字符串 则会输出 模板废弃的相关提示
if (template.indexOf('-2.0') !== -1) {
warnings.v2SuffixTemplatesDeprecated(template, inPlace ? '' : name)
return
}
// 下载并生产模板
downloadAndGenerate(officialTemplate)
}
} else {
// 下载并生生成模板
downloadAndGenerate(template)
}
})
}
}
我们来看下 downloadAndGenerate这个方法
/**
* Download a generate from a template repo.
*
* @param {String} template
*/
function downloadAndGenerate (template) {
// 执行加载动画
const spinner = ora('downloading template')
spinner.start()
// Remove if local template exists
// 删除本地存在的模板
if (exists(tmp)) rm(tmp)
// template参数为目标地址 tmp为下载地址 clone参数代表是否需要clone
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)
})
})
}
到这里为止,bin/vue-init就讲完了,该文件做的最主要的一件事情,就是根据模板名称,来下载生成模板,但是具体下载和生成的模板的方法并不在里面。
下载模板
下载模板用的download方法是属于download-git-repo模块的。
最基础的用法为如下用法,这里的参数很好理解,第一个参数为仓库地址,第二个为输出地址,第三个是否需要 git clone,带四个为回调参数
download('flipxfx/download-git-repo-fixture', 'test/tmp',{ clone: true }, function (err) {
console.log(err ? 'Error' : 'Success')
})
在上面的run方法中有提到一个#的字符串实际就是这个模块下载分支模块的用法
download('bitbucket:flipxfx/download-git-repo-fixture#my-branch', 'test/tmp', { clone: true }, function (err) {
console.log(err ? 'Error' : 'Success')
})
生成模板
模板生成generate方法在generate.js当中,我们继续来看一下
generate.js
const chalk = require('chalk')
const Metalsmith = require('metalsmith')
const Handlebars = require('handlebars')
const async = require('async')
const render = require('consolidate').handlebars.render
const path = require('path')
const multimatch = require('multimatch')
const getOptions = require('./options')
const ask = require('./ask')
const filter = require('./filter')
const logger = require('./logger')
chalk 是一个可以让终端输出内容变色的模块
chalk 是一个可以让终端输出内容变色的模块
Metalsmith是一个静态网站(博客,项目)的生成库
chalk 是一个可以让终端输出内容变色的模块
Metalsmith是一个静态网站(博客,项目)的生成库
handlerbars 是一个模板编译器,通过template和json,输出一个html
chalk 是一个可以让终端输出内容变色的模块
Metalsmith是一个静态网站(博客,项目)的生成库
handlerbars 是一个模板编译器,通过template和json,输出一个html
async 异步处理模块,有点类似让方法变成一个线程
chalk 是一个可以让终端输出内容变色的模块
Metalsmith是一个静态网站(博客,项目)的生成库
handlerbars 是一个模板编译器,通过template和json,输出一个html
async 异步处理模块,有点类似让方法变成一个线程
consolidate 模板引擎整合库
chalk 是一个可以让终端输出内容变色的模块
Metalsmith是一个静态网站(博客,项目)的生成库
handlerbars 是一个模板编译器,通过template和json,输出一个html
async 异步处理模块,有点类似让方法变成一个线程
consolidate 模板引擎整合库
multimatch 一个字符串数组匹配的库
chalk 是一个可以让终端输出内容变色的模块
Metalsmith是一个静态网站(博客,项目)的生成库
handlerbars 是一个模板编译器,通过template和json,输出一个html
async 异步处理模块,有点类似让方法变成一个线程
consolidate 模板引擎整合库
multimatch 一个字符串数组匹配的库
options 是一个自己定义的配置项文件
随后注册了2个渲染器,类似于vue中的 vif velse的条件渲染
// register handlebars helper
Handlebars.registerHelper('if_eq', function (a, b, opts) {
return a === b
? opts.fn(this)
: opts.inverse(this)
})
Handlebars.registerHelper('unless_eq', function (a, b, opts) {
return a === b
? opts.inverse(this)
: opts.fn(this)
})
接下来看关键的generate方法
module.exports = function generate (name, src, dest, done) {
// 读取了src目录下的 配置文件信息, 同时将 name auther(当前git用户) 赋值到了 opts 当中
const opts = getOptions(name, src)
// 拼接了目录 src/{template} 要在这个目录下生产静态文件
const metalsmith = Metalsmith(path.join(src, 'template'))
// 将metalsmitch中的meta 与 三个属性合并起来 形成 data
const data = Object.assign(metalsmith.metadata(), {
destDirName: name,
inPlace: dest === process.cwd(),
noEscape: true
})
// 遍历 meta.js元数据中的helpers对象,注册渲染模板数据
// 分别指定了 if_or 和 template_version内容
opts.helpers && Object.keys(opts.helpers).map(key => {
Handlebars.registerHelper(key, opts.helpers[key])
})
const helpers = { chalk, logger }
// 将metalsmith metadata 数据 和 { isNotTest, isTest 合并 }
if (opts.metalsmith && typeof opts.metalsmith.before === 'function') {
opts.metalsmith.before(metalsmith, opts, helpers)
}
// askQuestions是会在终端里询问一些问题
// 名称 描述 作者 是要什么构建 在meta.js 的opts.prompts当中
// 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)
}
// clean方法是设置在写入之前是否删除原先目标目录 默认为true
// source方法是设置原路径
// destination方法就是设置输出的目录
// build方法执行构建
metalsmith.clean(false)
.source('.') // start from template root instead of `./src` which is Metalsmith's default for `source`
.destination(dest)
.build((err, files) => {
done(err)
if (typeof opts.complete === 'function') {
// 当生成完毕之后执行 meta.js当中的 opts.complete方法
const helpers = { chalk, logger, files }
opts.complete(data, helpers)
} else {
logMessage(opts.completeMessage, data)
}
})
return data
}
meta.js
接下来看以下complete方法
complete: function(data, { chalk }) {
const green = chalk.green
// 会将已有的packagejoson 依赖声明重新排序
sortDependencies(data, green)
const cwd = path.join(process.cwd(), data.inPlace ? '' : data.destDirName)
// 是否需要自动安装 这个在之前构建前的询问当中 是我们自己选择的
if (data.autoInstall) {
// 在终端中执行 install 命令
installDependencies(cwd, data.autoInstall, green)
.then(() => {
return runLintFix(cwd, data, green)
})
.then(() => {
printMessage(data, green)
})
.catch(e => {
console.log(chalk.red('Error:'), e)
})
} else {
printMessage(data, chalk)
}
}
构建自定义模板
在看完vue-init命令的原理之后,其实定制自定义的模板是很简单的事情,我们只要做2件事
- 首先我们需要有一个自己模板项目
- 如果需要自定义一些变量,就需要在模板的meta.js当中定制
由于下载模块使用的是download-git-repo模块,它本身是支持在github,gitlab,bitucket上下载的,到时候我们只需要将定制好的模板项目放到git远程仓库上即可。
由于我需要定义的是小程序的开发模板,mpvue本身也有一个quickstart的模板,那么我们就在它的基础上进行定制,首先我们将它fork下来,新建一个custom分支,在这个分支上进行定制。
我们需要定制的地方有用到的依赖库,需要额外用到less以及wxparse
我们需要定制的地方有用到的依赖库,需要额外用到less以及wxparse
因此我们在 template/package.json当中进行添加
{
// ... 部分省略
"dependencies": {
"mpvue": "^1.0.11"{{#vuex}},
"vuex": "^3.0.1"{{/vuex}}
},
"devDependencies": {
// ... 省略
// 这是添加的包
"less": "^3.0.4",
"less-loader": "^4.1.0",
"mpvue-wxparse": "^0.6.5"
}
}
除此之外,我们还需要定制一下eslint规则,由于只用到standard,因此我们在meta.js当中 可以将 airbnb风格的提问删除
"lintConfig": {
"when": "lint",
"type": "list",
"message": "Pick an ESLint preset",
"choices": [
{
"name": "Standard (https://github.com/feross/standard)",
"value": "standard",
"short": "Standard"
},
{
"name": "none (configure it yourself)",
"value": "none",
"short": "none"
}
]
}
.eslinttrc.js
'rules': {
{{#if_eq lintConfig "standard"}}
"camelcase": 0,
// allow paren-less arrow functions
"arrow-parens": 0,
"space-before-function-paren": 0,
// allow async-await
"generator-star-spacing": 0,
{{/if_eq}}
{{#if_eq lintConfig "airbnb"}}
// don't require .vue extension when importing
'import/extensions': ['error', 'always', {
'js': 'never',
'vue': 'never'
}],
// allow optionalDependencies
'import/no-extraneous-dependencies': ['error', {
'optionalDependencies': ['test/unit/index.js']
}],
{{/if_eq}}
// allow debugger during development
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0
}
最后我们在构建时的提问当中,再设置一个小程序名称的提问,而这个名称会设置到导航的标题当中。
最后我们在构建时的提问当中,再设置一个小程序名称的提问,而这个名称会设置到导航的标题当中。
提问是在meta.js当中添加
"prompts": {
"name": {
"type": "string",
"required": true,
"message": "Project name"
},
// 新增提问
"appName": {
"type": "string",
"required": true,
"message": "App name"
}
}
main.json
{
"pages": [
"pages/index/main",
"pages/counter/main",
"pages/logs/main"
],
"window": {
"backgroundTextStyle": "light",
"navigationBarBackgroundColor": "#fff",
// 根据提问设置标题
"navigationBarTitleText": "{{appName}}",
"navigationBarTextStyle": "black"
}
}
最后我们来尝试一下我们自己的模板
vue init Baifann/mpvue-quickstart#custom min-app-project
总结
以上模板的定制是十分简单的,在实际项目上肯定更为复杂,但是按照这个思路应该都是可行的。比如说将一些自行封装的组件也放置到项目当中等等,这里就不再细说。原理解析都是基于vue-cli 2.0的,但实际上 3.0也已经整装待发,如果后续有机会,深入了解之后,再和大家分享,谢谢大家。
背景
在平时工作中会有遇到许多以相同模板定制的小程序,因此想自己建立一个生成模板的脚手架工具,以模板为基础构建对应的小程序,而平时的小程序都是用mpvue框架来写的,因此首先先参考一下Vue-cli的原理。知道原理之后,再定制自己的模板脚手架肯定是事半功倍的。
在说代码之前我们首先回顾一下Vue-cli的使用,我们通常使用的是webpack模板包,输入的是以下代码。
vue init webpack [project-name]
在执行这段代码之后,系统会自动下载模板包,随后会询问我们一些问题,比如模板名称,作者,是否需要使用eslint,使用npm或者yarn进行构建等等,当所有问题我们回答之后,就开始生成脚手架项目。
我们将源码下载下来,源码仓库点击这里,平时用的脚手架还是2.0版本,要注意,默认的分支是在dev上,dev上是3.0版本。
我们首先看一下package.json,在文件当中有这么一段话
{ "bin": { "vue": "bin/vue", "vue-init": "bin/vue-init", "vue-list": "bin/vue-list" } }
由此可见,我们使用的命令 vue init
,应该是来自bin/vue-init
这个文件,我们接下来看一下这个文件中的内容
bin/vue-init
const download = require('download-git-repo') const program = require('commander') const exists = require('fs').existsSync const path = require('path') const ora = require('ora') const home = require('user-home') const tildify = require('tildify') const chalk = require('chalk') const inquirer = require('inquirer') const rm = require('rimraf').sync const logger = require('../lib/logger') const generate = require('../lib/generate') const checkVersion = require('../lib/check-version') const warnings = require('../lib/warnings') const localPath = require('../lib/local-path')
download-git-repo 一个用于下载git仓库的项目的模块
download-git-repo 一个用于下载git仓库的项目的模块 commander 可以将文字输出到终端当中
download-git-repo 一个用于下载git仓库的项目的模块 commander 可以将文字输出到终端当中 fs 是node的文件读写的模块
download-git-repo 一个用于下载git仓库的项目的模块 commander 可以将文字输出到终端当中 fs 是node的文件读写的模块 path 模块提供了一些工具函数,用于处理文件与目录的路径
download-git-repo 一个用于下载git仓库的项目的模块 commander 可以将文字输出到终端当中 fs 是node的文件读写的模块 path 模块提供了一些工具函数,用于处理文件与目录的路径 ora 这个模块用于在终端里有显示载入动画
download-git-repo 一个用于下载git仓库的项目的模块 commander 可以将文字输出到终端当中 fs 是node的文件读写的模块 path 模块提供了一些工具函数,用于处理文件与目录的路径 ora 这个模块用于在终端里有显示载入动画 user-home 获取用户主目录的路径
download-git-repo 一个用于下载git仓库的项目的模块 commander 可以将文字输出到终端当中 fs 是node的文件读写的模块 path 模块提供了一些工具函数,用于处理文件与目录的路径 ora 这个模块用于在终端里有显示载入动画 user-home 获取用户主目录的路径 tildify 将绝对路径转换为波形路径 比如/Users/sindresorhus/dev → ~/dev
download-git-repo 一个用于下载git仓库的项目的模块 commander 可以将文字输出到终端当中 fs 是node的文件读写的模块 path 模块提供了一些工具函数,用于处理文件与目录的路径 ora 这个模块用于在终端里有显示载入动画 user-home 获取用户主目录的路径 tildify 将绝对路径转换为波形路径 比如/Users/sindresorhus/dev → ~/dev
inquirer 是一个命令行的回答的模块,你可以自己设定终端的问题,然后对这些回答给出相应的处理
download-git-repo 一个用于下载git仓库的项目的模块 commander 可以将文字输出到终端当中 fs 是node的文件读写的模块 path 模块提供了一些工具函数,用于处理文件与目录的路径 ora 这个模块用于在终端里有显示载入动画 user-home 获取用户主目录的路径 tildify 将绝对路径转换为波形路径 比如/Users/sindresorhus/dev → ~/dev
inquirer 是一个命令行的回答的模块,你可以自己设定终端的问题,然后对这些回答给出相应的处理 rimraf 是一个可以使用 UNIX 命令 rm -rf
的模块
download-git-repo 一个用于下载git仓库的项目的模块 commander 可以将文字输出到终端当中 fs 是node的文件读写的模块 path 模块提供了一些工具函数,用于处理文件与目录的路径 ora 这个模块用于在终端里有显示载入动画 user-home 获取用户主目录的路径 tildify 将绝对路径转换为波形路径 比如/Users/sindresorhus/dev → ~/dev
inquirer 是一个命令行的回答的模块,你可以自己设定终端的问题,然后对这些回答给出相应的处理 rimraf 是一个可以使用 UNIX 命令 rm -rf
的模块 剩下的本地路径的模块其实都是一些工具类,等用到的时候我们再来讲
// 是否为本地路径的方法 主要是判断模板路径当中是否存在 `./` const isLocalPath = localPath.isLocalPath // 获取模板路径的方法 如果路径参数是绝对路径 则直接返回 如果是相对的 则根据当前路径拼接 const getTemplatePath = localPath.getTemplatePath
/** * Usage. */ program .usage('<template-name> [project-name]') .option('-c, --clone', 'use git clone') .option('--offline', 'use cached template') /** * Help. */
program.on('–help', () => {
console.log(' Examples:')
console.log()
console.log(chalk.gray(' # create a new project with an official template'))
console.log(' $ vue init webpack my-project')
console.log()
console.log(chalk.gray(' # create a new project straight from a github template'))
console.log(' $ vue init username/repo my-project')
console.log()
})
/**
- Help.
*/
function help () {
program.parse(process.argv)
if (program.args.length < 1) return program.help()
}
help()这部分代码声明了
vue init
用法,如果在终端当中 输入vue init --help
或者跟在vue init
后面的参数长度小于1,也会输出下面的描述Usage: vue-init <template-name> [project-name]
Options:
-c, --clone use git clone
–offline use cached template
-h, --help output usage information
Examples:create a new project with an official template
$ vue init webpack my-projectcreate a new project straight from a github template
$ vue init username/repo my-project接下来是一些变量的获取
/**
- Settings.
*/
// 模板路径
let template = program.args[0]
const hasSlash = template.indexOf('/') > -1
// 项目名称
const rawName = program.args[1]
const inPlace = !rawName || rawName === '.'
// 如果不存在项目名称或项目名称输入的'.' 则name取的是 当前文件夹的名称
const name = inPlace ? path.relative('…/', process.cwd()) : rawName
// 输出路径
const to = path.resolve(rawName || '.')
// 是否需要用到 git clone
const clone = program.clone || false
// tmp为本地模板路径 如果 是离线状态 那么模板路径取本地的
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
}
接下来主要是根据模板名称,来下载并生产模板,如果是本地的模板路径,就直接生成。
/**
- Check, download and generate the project.
*/
function run () {
// 判断是否是本地模板路径
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) {
// 拼接路径 'vuejs-tempalte'下的都是官方的模板包
const officialTemplate = 'vuejs-templates/' + template
// 如果路径当中存在 '#'则直接下载
if (template.indexOf('#') !== -1) {
downloadAndGenerate(officialTemplate)
} else {
// 如果不存在 -2.0的字符串 则会输出 模板废弃的相关提示
if (template.indexOf('-2.0') !== -1) {
warnings.v2SuffixTemplatesDeprecated(template, inPlace ? '' : name)
return
}
// 下载并生产模板
downloadAndGenerate(officialTemplate)
}
} else {
// 下载并生生成模板
downloadAndGenerate(template)
}
})
}
}
我们来看下 downloadAndGenerate
这个方法
/**
- Download a generate from a template repo.
- @param {String} template
*/
function downloadAndGenerate (template) {
// 执行加载动画
const spinner = ora(‘downloading template’)
spinner.start()
// Remove if local template exists
// 删除本地存在的模板
if (exists(tmp)) rm(tmp)
// template参数为目标地址 tmp为下载地址 clone参数代表是否需要clone
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)
})
})
}
到这里为止,bin/vue-init
就讲完了,该文件做的最主要的一件事情,就是根据模板名称,来下载生成模板,但是具体下载和生成的模板的方法并不在里面。
下载模板
下载模板用的download方法是属于download-git-repo模块的。
最基础的用法为如下用法,这里的参数很好理解,第一个参数为仓库地址,第二个为输出地址,第三个是否需要 git clone
,带四个为回调参数
download(‘flipxfx/download-git-repo-fixture’, ‘test/tmp’,{ clone: true }, function (err) {
console.log(err ? ‘Error’ : ‘Success’)
})
在上面的run
方法中有提到一个#
的字符串实际就是这个模块下载分支模块的用法
download(‘bitbucket:flipxfx/download-git-repo-fixture#my-branch’, ‘test/tmp’, { clone: true }, function (err) {
console.log(err ? ‘Error’ : ‘Success’)
})
生成模板
模板生成generate
方法在generate.js
当中,我们继续来看一下
generate.js
const chalk = require(‘chalk’)
const Metalsmith = require(‘metalsmith’)
const Handlebars = require(‘handlebars’)
const async = require(‘async’)
const render = require(‘consolidate’).handlebars.render
const path = require(‘path’)
const multimatch = require(‘multimatch’)
const getOptions = require(’./options’)
const ask = require(’./ask’)
const filter = require(’./filter’)
const logger = require(’./logger’)
chalk 是一个可以让终端输出内容变色的模块
chalk 是一个可以让终端输出内容变色的模块
Metalsmith是一个静态网站(博客,项目)的生成库
chalk 是一个可以让终端输出内容变色的模块
Metalsmith是一个静态网站(博客,项目)的生成库
handlerbars 是一个模板编译器,通过template
和json
,输出一个html
chalk 是一个可以让终端输出内容变色的模块
Metalsmith是一个静态网站(博客,项目)的生成库
handlerbars 是一个模板编译器,通过template
和json
,输出一个html
async 异步处理模块,有点类似让方法变成一个线程
chalk 是一个可以让终端输出内容变色的模块
Metalsmith是一个静态网站(博客,项目)的生成库
handlerbars 是一个模板编译器,通过template
和json
,输出一个html
async 异步处理模块,有点类似让方法变成一个线程
consolidate 模板引擎整合库
chalk 是一个可以让终端输出内容变色的模块
Metalsmith是一个静态网站(博客,项目)的生成库
handlerbars 是一个模板编译器,通过template
和json
,输出一个html
async 异步处理模块,有点类似让方法变成一个线程
consolidate 模板引擎整合库
multimatch 一个字符串数组匹配的库
chalk 是一个可以让终端输出内容变色的模块
Metalsmith是一个静态网站(博客,项目)的生成库
handlerbars 是一个模板编译器,通过template
和json
,输出一个html
async 异步处理模块,有点类似让方法变成一个线程
consolidate 模板引擎整合库
multimatch 一个字符串数组匹配的库
options 是一个自己定义的配置项文件
随后注册了2个渲染器,类似于vue中的 vif velse的条件渲染
// register handlebars helper
Handlebars.registerHelper('if_eq', function (a, b, opts) {
return a === b
? opts.fn(this)
: opts.inverse(this)
})
Handlebars.registerHelper('unless_eq', function (a, b, opts) {
return a === b
? opts.inverse(this)
: opts.fn(this)
})
接下来看关键的generate
方法
module.exports = function generate (name, src, dest, done) {
// 读取了src目录下的 配置文件信息, 同时将 name auther(当前git用户) 赋值到了 opts 当中
const opts = getOptions(name, src)
// 拼接了目录 src/{template} 要在这个目录下生产静态文件
const metalsmith = Metalsmith(path.join(src, 'template'))
// 将metalsmitch中的meta 与 三个属性合并起来 形成 data
const data = Object.assign(metalsmith.metadata(), {
destDirName: name,
inPlace: dest === process.cwd(),
noEscape: true
})
// 遍历 meta.js元数据中的helpers对象,注册渲染模板数据
// 分别指定了 if_or 和 template_version内容
opts.helpers && Object.keys(opts.helpers).map(key => {
Handlebars.registerHelper(key, opts.helpers[key])
})
const helpers = { chalk, logger }
// 将metalsmith metadata 数据 和 { isNotTest, isTest 合并 }
if (opts.metalsmith && typeof opts.metalsmith.before === 'function') {
opts.metalsmith.before(metalsmith, opts, helpers)
}
// askQuestions是会在终端里询问一些问题
// 名称 描述 作者 是要什么构建 在meta.js 的opts.prompts当中
// 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)
}
// clean方法是设置在写入之前是否删除原先目标目录 默认为true
// source方法是设置原路径
// destination方法就是设置输出的目录
// build方法执行构建
metalsmith.clean(false)
.source('.') // start from template root instead of ./src
which is Metalsmith's default for source
.destination(dest)
.build((err, files) => {
done(err)
if (typeof opts.complete === 'function') {
// 当生成完毕之后执行 meta.js当中的 opts.complete方法
const helpers = { chalk, logger, files }
opts.complete(data, helpers)
} else {
logMessage(opts.completeMessage, data)
}
})
return data
}
meta.js
接下来看以下complete方法
complete: function(data, { chalk }) {
const green = chalk.green
// 会将已有的packagejoson 依赖声明重新排序
sortDependencies(data, green)
const cwd = path.join(process.cwd(), data.inPlace ? ‘’ : data.destDirName)
// 是否需要自动安装 这个在之前构建前的询问当中 是我们自己选择的
if (data.autoInstall) {
// 在终端中执行 install 命令
installDependencies(cwd, data.autoInstall, green)
.then(() => {
return runLintFix(cwd, data, green)
})
.then(() => {
printMessage(data, green)
})
.catch(e => {
console.log(chalk.red(‘Error:’), e)
})
} else {
printMessage(data, chalk)
}
}
构建自定义模板
在看完vue-init
命令的原理之后,其实定制自定义的模板是很简单的事情,我们只要做2件事
- 首先我们需要有一个自己模板项目
- 如果需要自定义一些变量,就需要在模板的
meta.js
当中定制
由于下载模块使用的是download-git-repo
模块,它本身是支持在github,gitlab,bitucket上下载的,到时候我们只需要将定制好的模板项目放到git远程仓库上即可。
由于我需要定义的是小程序的开发模板,mpvue本身也有一个quickstart的模板,那么我们就在它的基础上进行定制,首先我们将它fork下来,新建一个custom分支,在这个分支上进行定制。
我们需要定制的地方有用到的依赖库,需要额外用到less以及wxparse
我们需要定制的地方有用到的依赖库,需要额外用到less以及wxparse
因此我们在 template/package.json
当中进行添加
{
// … 部分省略
“dependencies”: {
“mpvue”: “^1.0.11”{{#vuex}},
“vuex”: “^3.0.1”{{/vuex}}
},
“devDependencies”: {
// … 省略
// 这是添加的包
“less”: “^3.0.4”,
“less-loader”: “^4.1.0”,
“mpvue-wxparse”: “^0.6.5”
}
}
除此之外,我们还需要定制一下eslint规则,由于只用到standard,因此我们在meta.js
当中 可以将 airbnb风格的提问删除
"lintConfig": {
“when”: “lint”,
“type”: “list”,
“message”: “Pick an ESLint preset”,
“choices”: [
{
“name”: “Standard (https://github.com/feross/standard)”,
“value”: “standard”,
“short”: “Standard”
},{ "name": "none (configure it yourself)", "value": "none", "short": "none" }
]
}
.eslinttrc.js
’rules’: {
{{#if_eq lintConfig “standard”}}"camelcase": 0, // allow paren-less arrow functions "arrow-parens": 0, "space-before-function-paren": 0, // allow async-await
“generator-star-spacing”: 0,
{{/if_eq}}
{{#if_eq lintConfig “airbnb”}}
// don’t require .vue extension when importing
‘import/extensions’: [‘error’, ‘always’, {
‘js’: ‘never’,
‘vue’: ‘never’
}],
// allow optionalDependencies
‘import/no-extraneous-dependencies’: [‘error’, {
‘optionalDependencies’: [‘test/unit/index.js’]
}],
{{/if_eq}}
// allow debugger during development
‘no-debugger’: process.env.NODE_ENV === ‘production’ ? 2 : 0
}
最后我们在构建时的提问当中,再设置一个小程序名称的提问,而这个名称会设置到导航的标题当中。
最后我们在构建时的提问当中,再设置一个小程序名称的提问,而这个名称会设置到导航的标题当中。
提问是在meta.js
当中添加
"prompts": {
“name”: {
“type”: “string”,
“required”: true,
“message”: “Project name”
},
// 新增提问
“appName”: {
“type”: “string”,
“required”: true,
“message”: “App name”
}
}
main.json
{
“pages”: [
“pages/index/main”,
“pages/counter/main”,
“pages/logs/main”
],
“window”: {
“backgroundTextStyle”: “light”,
“navigationBarBackgroundColor”: “#fff”,
// 根据提问设置标题
“navigationBarTitleText”: “{{appName}}”,
“navigationBarTextStyle”: “black”
}
}
最后我们来尝试一下我们自己的模板
vue init Baifann/mpvue-quickstart#custom min-app-project
总结
以上模板的定制是十分简单的,在实际项目上肯定更为复杂,但是按照这个思路应该都是可行的。比如说将一些自行封装的组件也放置到项目当中等等,这里就不再细说。原理解析都是基于vue-cli 2.0的,但实际上 3.0也已经整装待发,如果后续有机会,深入了解之后,再和大家分享,谢谢大家。
加我微信号:欢迎你来撩,谢谢!