前段時間看了一些vue-cli的源碼,收穫頗深。本想找個時間更新一篇文章,可是最近事情比較多,沒有時間去整理這些東西。趁這兩天閒了下來,便整理了一下,而後跟你們分享一下。若是小夥伴們讀完以後,跟我同樣收穫不少的話,還望各位小夥伴們多多點贊收藏支持一下哦。css
Vue-cli是一款很是優秀的用於迅速構建基於Vue的Web應用工具。他不一樣於creat-react-app這樣的工具,開發者只須要關注項目邏輯的代碼,而不須要關心webpack打包、啓動Node服務等等諸如此類的這些問題。Vue-cli是一款基於模板化的開發工具,等於就是把別人的項目結構給照搬過來,全部的配置都是暴露出來的,你能夠根據實際狀況去作一些配置的修改,更加靈活自由一點。固然這對前端工程師提出更高的要求,考慮的東西也變多了。不過Vue-cli即將發佈3.0的版本,整個Vue-cli發生了翻天覆地的變化,它採用跟creat-react-app這類工具的模式,開發者只須要關注項目邏輯的代碼便可。不過目前3.0尚未出來,因此此次源碼分析我採用的v2.9.3的源碼,也就是2.0的代碼。後面小夥們在閱讀的時候要注意如下。前端
整個項目的目錄結構如上圖所示,下面我大概介紹每一個文件夾的東西大體都是幹嗎的。vue
bin (這裏放的vue的一些命令文件,好比vue init這樣的命令都是從由這裏控制的。)node
docs (一些注意事項啥的,不重要的目錄,能夠直接忽略。)react
lib (這裏存放着一些vue-cli須要的一些自定義方法。)webpack
node_modules (這裏應該就不用我多說了,相信你們都知道了,不知道的話能夠去面壁去了!●-● )git
test (單元測試 開發vue-cli工具時會用到,咱們讀源碼的時候能夠直接忽略掉。)github
一些雜七雜八的東西 (好比eslint配置、.gitignore、LICENSE等等諸如此類這些東西,不影響咱們閱讀源碼,能夠直接忽略掉。)web
package.json/README.md (這個不知道也能夠去面壁了!●-●)vue-cli
綜合來講,咱們閱讀源碼所要關注的只有bin和lib下面便可,其餘的均可忽略。下面開始閱讀之旅吧
在開始讀源碼以前,首先我要介紹一個工具(commander),這是用來處理命令行的工具。具體的使用方法可查看github的README.md https://github.com/tj/commander.js 。小夥伴們再閱讀後面的內容以前,建議先去了解一下commander,方便後續的理解。這裏咱們對commander就不作詳細介紹了。這裏vue-cli採用了commander的git風格的寫法。vue文件處理vue命令,vue-init處理vue init命令以此類推。接着咱們一個一個命令看過去。
引入的包:
做用: vue這個文件代碼不多,我就直接貼出來了。
#!/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」時,終端上顯示參數的使用說明。具體的寫法可參考 https://github.com/tj/commander.js 上面的說明。
引入的包:
做用: vue build命令在vue-cli之中已經刪除了,源碼上作了必定的說明。代碼很少,我就直接貼出來。
const chalk = require('chalk')
console.log(chalk.yellow(
'\n' +
' We are slimming down vue-cli to optimize the initial installation by ' +
'removing the `vue build` command.\n' +
' Check out Poi (https://github.com/egoist/poi) which offers the same functionality!' +
'\n'
))
複製代碼
#!/usr/bin/env node
const logger = require('../lib/logger')
const request = require('request')
const chalk = require('chalk')
/**
* Padding.
*/
console.log()
process.on('exit', () => {
console.log()
})
/**
* List repos.
*/
request({
url: 'https://api.github.com/users/vuejs-templates/repos',
headers: {
'User-Agent': 'vue-cli'
}
}, (err, res, body) => {
if (err) logger.fatal(err)
const requestBody = JSON.parse(body)
if (Array.isArray(requestBody)) {
console.log(' Available official templates:')
console.log()
requestBody.forEach(repo => {
console.log(
' ' + chalk.yellow('★') +
' ' + chalk.blue(repo.name) +
' - ' + repo.description)
})
} else {
console.error(requestBody.message)
}
})
複製代碼
引入的包:
做用: 當輸入"vue list"時(咱們測試時,能夠直接在當前源碼文件目錄下的終端上輸入「bin/vue-list」),vue-cli會請求接口,獲取官方模板的信息,而後作了必定處理,在終端上顯示出來模板名稱和對應的說明。
效果以下:
Available official templates:
★ browserify - A full-featured Browserify + vueify setup with hot-reload, linting & unit testing.
★ browserify-simple - A simple Browserify + vueify setup for quick prototyping.
★ pwa - PWA template for vue-cli based on the webpack template
★ simple - The simplest possible Vue setup in a single HTML file
★ webpack - A full-featured Webpack + vue-loader setup with hot reload, linting, testing & css extraction.
★ webpack-simple - A simple Webpack + vue-loader setup for quick prototyping.
複製代碼
「vue init」是用來構建項目的命令,也是vue-cli的核心文件,上面的三個都是很是簡單的命令,算是咱們閱讀源碼的開胃菜,真正的大餐在這裏。
在講代碼以前,首先咱們要講一下整個vue-cli初始項目的流程,而後咱們沿着流程一步一步走下去。
整個vue init大體流程如我上圖所示,應該仍是比較好理解的。這裏我大體闡述一下大體的流程。
vue-cli會先判斷你的模板在遠程github倉庫上仍是在你的本地某個文件裏面,如果本地文件夾則會當即跳到第3步,反之則走第2步。
第2步會判斷是否爲官方模板,官方模板則會從官方github倉庫中下載模板到本地的默認倉庫下,即根目錄下.vue-templates文件夾下。
第3步則讀取模板目錄下meta.js或者meta.json文件,根據裏面的內容會詢問開發者,根據開發者的回答,肯定一些修改。
根據模板內容以及開發者的回答,渲染出項目結構並生成到指定目錄。
這裏vue-init文件的代碼比較多,我這裏就拆分幾塊來看。首先我先把整個文件的結構列出來,方便後續的閱讀。
/**
* 引入一大堆包
*/
const program = require('commander')
...
/**
* 配置commander的使用方法
*/
program
.usage('<template-name> [project-name]')
.option('-c, --clone', 'use git clone')
.option('--offline', 'use cached template')
/**
* 定義commander的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()
})
function help () {
program.parse(process.argv)
if (program.args.length < 1) return program.help() //若是沒有輸入參數,終端顯示幫助
}
help()
/**
* 定義一大堆變量
*/
let template = program.args[0]
...
/**
* 判斷是否輸入項目名 是 - 直接執行run函數 否- 詢問開發者是否在當前目錄下生成項目,開發者回答「是」 也執行run函數 不然不執行run函數
*/
/**
* 定義主函數 run
*/
function run (){
...
}
/**
* 定義下載模板並生產項目的函數 downloadAndGenerate
*/
function downloadAndGenerate(){
...
}
複製代碼
整個文件大體的東西入上面所示,後面咱們將一塊一塊內容來看。
const download = require('download-git-repo') //用於下載遠程倉庫至本地 支持GitHub、GitLab、Bitbucket
const program = require('commander') //命令行處理工具
const exists = require('fs').existsSync //node自帶的fs模塊下的existsSync方法,用於檢測路徑是否存在。(會阻塞)
const path = require('path') //node自帶的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 // 至關於UNIX的「rm -rf」命令
const logger = require('../lib/logger') //自定義工具-用於日誌打印
const generate = require('../lib/generate') //自定義工具-用於基於模板構建項目
const checkVersion = require('../lib/check-version') //自定義工具-用於檢測vue-cli版本的工具
const warnings = require('../lib/warnings') //自定義工具-用於模板的警告
const localPath = require('../lib/local-path') //自定義工具-用於路徑的處理
const isLocalPath = localPath.isLocalPath //判斷是不是本地路徑
const getTemplatePath = localPath.getTemplatePath //獲取本地模板的絕對路徑
複製代碼
let template = program.args[0] //模板名稱
const hasSlash = template.indexOf('/') > -1 //是否有斜槓,後面將會用來斷定是否爲官方模板
const rawName = program.args[1] //項目構建目錄名
const inPlace = !rawName || rawName === '.' // 沒寫或者「.」,表示當前目錄下構建項目
const name = inPlace ? path.relative('../', process.cwd()) : rawName //若是在當前目錄下構建項目,當前目錄名爲項目構建目錄名,不然是當前目錄下的子目錄【rawName】爲項目構建目錄名
const to = path.resolve(rawName || '.') //項目構建目錄的絕對路徑
const clone = program.clone || false //是否採用clone模式,提供給「download-git-repo」的參數
const tmp = path.join(home, '.vue-templates', template.replace(/[\/:]/g, '-')) //遠程模板下載到本地的路徑
複製代碼
if (inPlace || exists(to)) {
inquirer.prompt([{
type: 'confirm',
message: inPlace
? 'Generate project in current directory?'
: 'Target directory exists. Continue?',
name: 'ok'
}]).then(answers => {
if (answers.ok) {
run()
}
}).catch(logger.fatal)
} else {
run()
}
複製代碼
對着上面代碼,vue-cli會判斷inPlace和exists(to),true則詢問開發者,當開發者回答「yes」的時候執行run函數,不然直接執行run函數。這裏詢問開發者的問題有以下兩個:
Generate project in current directory? //是否在當前目錄下構建項目
Target directory exists. Continue? //構建目錄已存在,是否繼續
這兩個問題依靠變量inPlace來選擇,下面我看一下變量inPlace是怎麼得來的。
const rawName = program.args[1] //rawName爲命令行的第二個參數(項目構建目錄的相對目錄)
const inPlace = !rawName || rawName === '.' //rawName存在或者爲「.」的時候,視爲在當前目錄下構建
複製代碼
經過上面的描述可知,變量inPlace用於判斷是否在當前目錄下構建,所以變量inPlace爲true時,則會提示Generate project in current directory? ,反之當變量inPlace爲false時,此時exists(to)必定爲true,便提示Target directory exists. Continue?。
邏輯:
源碼:
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
// 從這句話以及download-git-repo的用法,咱們得知了vue的官方的模板庫的地址:https://github.com/vuejs-templates
const officialTemplate = 'vuejs-templates/' + template
if (template.indexOf('#') !== -1) { //模板名是否帶"#"
downloadAndGenerate(officialTemplate) //下載模板
} else {
if (template.indexOf('-2.0') !== -1) { //是都帶"-2.0"
//發出警告
warnings.v2SuffixTemplatesDeprecated(template, inPlace ? '' : name)
return
}
// warnings.v2BranchIsNowDefault(template, inPlace ? '' : name)
downloadAndGenerate(officialTemplate)//下載模板
}
} else {
downloadAndGenerate(template)//下載模板
}
})
}
}
複製代碼
function downloadAndGenerate (template) {
const spinner = ora('downloading template')
spinner.start()//顯示加載狀態
// Remove if local template exists
if (exists(tmp)) rm(tmp) //當前模板庫是否存在該模板,存在就刪除
//下載模板 template-模板名 tmp- 模板路徑 clone-是否採用git clone模板 err-錯誤短信
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)
})
})
}
複製代碼
lib文件下最重要的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')
// register handlebars helper 註冊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 a template given a `src` and `dest`.
*
* @param {String} name
* @param {String} src
* @param {String} dest
* @param {Function} done
*/
module.exports = function generate (name, src, dest, done) {
const opts = getOptions(name, src) //獲取配置
const metalsmith = Metalsmith(path.join(src, 'template')) //初始化Metalsmith對象
const data = Object.assign(metalsmith.metadata(), {
destDirName: name,
inPlace: dest === process.cwd(),
noEscape: true
})//添加一些變量至metalsmith中,並獲取metalsmith中所有變量
//註冊配置對象中的helper
opts.helpers && Object.keys(opts.helpers).map(key => {
Handlebars.registerHelper(key, opts.helpers[key])
})
const helpers = { chalk, logger }
//配置對象是否有before函數,是則執行
if (opts.metalsmith && typeof opts.metalsmith.before === 'function') {
opts.metalsmith.before(metalsmith, opts, helpers)
}
metalsmith.use(askQuestions(opts.prompts)) //詢問問題
.use(filterFiles(opts.filters)) //過濾文件
.use(renderTemplateFiles(opts.skipInterpolation)) //渲染模板文件
//配置對象是否有after函數,是則執行
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((err, files) => { done(err) if (typeof opts.complete === 'function') { //配置對象有complete函數則執行 const helpers = { chalk, logger, files } opts.complete(data, helpers) } else { //配置對象有completeMessage,執行logMessage函數 logMessage(opts.completeMessage, data) } }) return data } /** * Create a middleware for asking questions. * * @param {Object} prompts * @return {Function} */ function askQuestions (prompts) { return (files, metalsmith, done) => { ask(prompts, metalsmith.metadata(), done) } } /** * Create a middleware for filtering files. * * @param {Object} filters * @return {Function} */ function filterFiles (filters) { return (files, metalsmith, done) => { filter(files, filters, metalsmith.metadata(), done) } } /** * Template in place plugin. * * @param {Object} files * @param {Metalsmith} metalsmith * @param {Function} done */ function renderTemplateFiles (skipInterpolation) { skipInterpolation = typeof skipInterpolation === 'string' ? [skipInterpolation] : skipInterpolation //保證skipInterpolation是一個數組 return (files, metalsmith, done) => { const keys = Object.keys(files) //獲取files的全部key const metalsmithMetadata = metalsmith.metadata() //獲取metalsmith的全部變量 async.each(keys, (file, next) => { //異步處理全部files // skipping files with skipInterpolation option // 跳過符合skipInterpolation的要求的file if (skipInterpolation && multimatch([file], skipInterpolation, { dot: true }).length) { return next() } //獲取文件的文本內容 const str = files[file].contents.toString() // do not attempt to render files that do not have mustaches //跳過不符合handlebars語法的file if (!/{{([^{}]+)}}/g.test(str)) { return next() } //渲染文件 render(str, metalsmithMetadata, (err, res) => { if (err) { err.message = `[${file}] ${err.message}` return next(err) } files[file].contents = new Buffer(res) next() }) }, done) } } /** * Display template complete message. * * @param {String} message * @param {Object} data */ function logMessage (message, data) { if (!message) return //沒有message直接退出函數 render(message, data, (err, res) => { if (err) { console.error('\n Error when rendering template complete message: ' + err.message.trim()) //渲染錯誤打印錯誤信息 } else { console.log('\n' + res.split(/\r?\n/g).map(line => ' ' + line).join('\n')) //渲染成功打印最終渲染的結果 } }) } 複製代碼
引入的包:
主邏輯:
獲取模板配置 -->初始化Metalsmith -->添加一些變量至Metalsmith -->handlebars模板註冊helper -->配置對象中是否有before函數,有則執行 -->詢問問題 -->過濾文件 -->渲染模板文件 -->配置對象中是否有after函數,有則執行 -->最後構建項目內容 -->構建完成,成功若配置對象中有complete函數則執行,不然打印配置對象中的completeMessage信息,若是有錯誤,執行回調函數done(err)
其餘函數:
Metalsmith插件格式:
function <function name> {
return (files,metalsmith,done)=>{
//邏輯代碼
...
}
}
複製代碼
const path = require('path')
const metadata = require('read-metadata')
const exists = require('fs').existsSync
const getGitUser = require('./git-user')
const validateName = require('validate-npm-package-name')
/**
* Read prompts metadata.
*
* @param {String} dir
* @return {Object}
*/
module.exports = function options (name, dir) {
const opts = getMetadata(dir)
setDefault(opts, 'name', name)
setValidateName(opts)
const author = getGitUser()
if (author) {
setDefault(opts, 'author', author)
}
return opts
}
/**
* Gets the metadata from either a meta.json or meta.js file.
*
* @param {String} dir
* @return {Object}
*/
function getMetadata (dir) {
const json = path.join(dir, 'meta.json')
const js = path.join(dir, 'meta.js')
let opts = {}
if (exists(json)) {
opts = metadata.sync(json)
} else if (exists(js)) {
const req = require(path.resolve(js))
if (req !== Object(req)) {
throw new Error('meta.js needs to expose an object')
}
opts = req
}
return opts
}
/**
* Set the default value for a prompt question
*
* @param {Object} opts
* @param {String} key
* @param {String} val
*/
function setDefault (opts, key, val) {
if (opts.schema) {
opts.prompts = opts.schema
delete opts.schema
}
const prompts = opts.prompts || (opts.prompts = {})
if (!prompts[key] || typeof prompts[key] !== 'object') {
prompts[key] = {
'type': 'string',
'default': val
}
} else {
prompts[key]['default'] = val
}
}
function setValidateName (opts) {
const name = opts.prompts.name
const customValidate = name.validate
name.validate = name => {
const its = validateName(name)
if (!its.validForNewPackages) {
const errors = (its.errors || []).concat(its.warnings || [])
return 'Sorry, ' + errors.join(' and ') + '.'
}
if (typeof customValidate === 'function') return customValidate(name)
return true
}
}
複製代碼
引入的包:
做用:
const exec = require('child_process').execSync
module.exports = () => {
let name
let email
try {
name = exec('git config --get user.name')
email = exec('git config --get user.email')
} catch (e) {}
name = name && JSON.stringify(name.toString().trim()).slice(1, -1)
email = email && (' <' + email.toString().trim() + '>')
return (name || '') + (email || '')
}
複製代碼
引入的包:
做用: 用於獲取本地的git配置的用戶名和郵件,並返回格式 姓名<郵箱> 的字符串。
const chalk = require('chalk')
/**
* Evaluate an expression in meta.json in the context of
* prompt answers data.
*/
module.exports = function evaluate (exp, data) {
/* eslint-disable no-new-func */
const fn = new Function('data', 'with (data) { return ' + exp + '}')
try {
return fn(data)
} catch (e) {
console.error(chalk.red('Error when evaluating filter condition: ' + exp))
}
}
複製代碼
引入的包:
做用: 在data的做用域執行exp表達式並返回其執行獲得的值
const async = require('async')
const inquirer = require('inquirer')
const evaluate = require('./eval')
// Support types from prompt-for which was used before
const promptMapping = {
string: 'input',
boolean: 'confirm'
}
/**
* Ask questions, return results.
*
* @param {Object} prompts
* @param {Object} data
* @param {Function} done
*/
/**
* prompts meta.js或者meta.json中的prompts字段
* data metalsmith.metadata()
* done 交於下一個metalsmith插件處理
*/
module.exports = function ask (prompts, data, done) {
//遍歷處理prompts下的每個字段
async.eachSeries(Object.keys(prompts), (key, next) => {
prompt(data, key, prompts[key], next)
}, done)
}
/**
* Inquirer prompt wrapper.
*
* @param {Object} data
* @param {String} key
* @param {Object} prompt
* @param {Function} done
*/
function prompt (data, key, prompt, done) {
// skip prompts whose when condition is not met
if (prompt.when && !evaluate(prompt.when, data)) {
return done()
}
//獲取默認值
let promptDefault = prompt.default
if (typeof prompt.default === 'function') {
promptDefault = function () {
return prompt.default.bind(this)(data)
}
}
//設置問題,具體使用方法可去https://github.com/SBoudrias/Inquirer.js上面查看
inquirer.prompt([{
type: promptMapping[prompt.type] || prompt.type,
name: key,
message: prompt.message || prompt.label || key,
default: promptDefault,
choices: prompt.choices || [],
validate: prompt.validate || (() => true)
}]).then(answers => {
if (Array.isArray(answers[key])) {
//當答案是一個數組時
data[key] = {}
answers[key].forEach(multiChoiceAnswer => {
data[key][multiChoiceAnswer] = true
})
} else if (typeof answers[key] === 'string') {
//當答案是一個字符串時
data[key] = answers[key].replace(/"/g, '\\"') } else { //其餘狀況 data[key] = answers[key] } done() }).catch(done) } 複製代碼
引入的包:
做用: 將meta.js或者meta.json中的prompts字段解析成對應的問題詢問。
const match = require('minimatch')
const evaluate = require('./eval')
/**
* files 模板內的全部文件
* filters meta.js或者meta.json的filters字段
* data metalsmith.metadata()
* done 交於下一個metalsmith插件處理
*/
module.exports = (files, filters, data, done) => {
if (!filters) {
//meta.js或者meta.json沒有filters字段直接跳過交於下一個metalsmith插件處理
return done()
}
//獲取全部文件的名字
const fileNames = Object.keys(files)
//遍歷meta.js或者meta.json沒有filters下的全部字段
Object.keys(filters).forEach(glob => {
//遍歷全部文件名
fileNames.forEach(file => {
//若是有文件名跟filters下的某一個字段匹配上
if (match(file, glob, { dot: true })) {
const condition = filters[glob]
if (!evaluate(condition, data)) {
//若是metalsmith.metadata()下condition表達式不成立,刪除該文件
delete files[file]
}
}
})
})
done()
}
複製代碼
引入的包:
做用: 根據metalsmith.metadata()刪除一些不須要的模板文件,而metalsmith.metadata()主要在ask.js中改變的,也就是說ask.js中獲取到用戶的需求。
const chalk = require('chalk')
const format = require('util').format
/**
* Prefix.
*/
const prefix = ' vue-cli'
const sep = chalk.gray('·')
/**
* Log a `message` to the console.
*
* @param {String} message
*/
exports.log = function (...args) {
const msg = format.apply(format, args)
console.log(chalk.white(prefix), sep, msg)
}
/**
* Log an error `message` to the console and exit.
*
* @param {String} message
*/
exports.fatal = function (...args) {
if (args[0] instanceof Error) args[0] = args[0].message.trim()
const msg = format.apply(format, args)
console.error(chalk.red(prefix), sep, msg)
process.exit(1)
}
/**
* Log a success `message` to the console and exit.
*
* @param {String} message
*/
exports.success = function (...args) {
const msg = format.apply(format, args)
console.log(chalk.white(prefix), sep, msg)
}
複製代碼
引入的包:
做用: logger.js主要提供三個方法log(常規日誌)、fatal(錯誤日誌)、success(成功日誌)。每一個方法都挺簡單的,我就不錯過多的解釋了。
const path = require('path')
module.exports = {
isLocalPath (templatePath) {
return /^[./]|(^[a-zA-Z]:)/.test(templatePath)
},
getTemplatePath (templatePath) {
return path.isAbsolute(templatePath)
? templatePath
: path.normalize(path.join(process.cwd(), templatePath))
}
}
複製代碼
引入的包:
做用:
const request = require('request')
const semver = require('semver')
const chalk = require('chalk')
const packageConfig = require('../package.json')
module.exports = done => {
// Ensure minimum supported node version is used
if (!semver.satisfies(process.version, packageConfig.engines.node)) {
return console.log(chalk.red(
' You must upgrade node to >=' + packageConfig.engines.node + '.x to use vue-cli'
))
}
request({
url: 'https://registry.npmjs.org/vue-cli',
timeout: 1000
}, (err, res, body) => {
if (!err && res.statusCode === 200) {
const latestVersion = JSON.parse(body)['dist-tags'].latest
const localVersion = packageConfig.version
if (semver.lt(localVersion, latestVersion)) {
console.log(chalk.yellow(' A newer version of vue-cli is available.'))
console.log()
console.log(' latest: ' + chalk.green(latestVersion))
console.log(' installed: ' + chalk.red(localVersion))
console.log()
}
}
done()
})
}
複製代碼
引入的包:
做用:
const chalk = require('chalk')
module.exports = {
v2SuffixTemplatesDeprecated (template, name) {
const initCommand = 'vue init ' + template.replace('-2.0', '') + ' ' + name
console.log(chalk.red(' This template is deprecated, as the original template now uses Vue 2.0 by default.'))
console.log()
console.log(chalk.yellow(' Please use this command instead: ') + chalk.green(initCommand))
console.log()
},
v2BranchIsNowDefault (template, name) {
const vue1InitCommand = 'vue init ' + template + '#1.0' + ' ' + name
console.log(chalk.green(' This will install Vue 2.x version of the template.'))
console.log()
console.log(chalk.yellow(' For Vue 1.x use: ') + chalk.green(vue1InitCommand))
console.log()
}
}
複製代碼
引入的包:
做用:
因爲代碼比較多,不少代碼我就沒有一一細講了,一些比較簡單或者不是很重要的js文件,我就單單說明了它的做用了。可是重點的js文件,我仍是加了不少註解在上面。其中我我的認爲比較重點的文件就是vue-init、generate.js、options.js、ask.js、filter.js,這五個文件構成了vue-cli構建項目的主流程,所以須要咱們花更多的時間在上面。另外,咱們在讀源碼的過程當中,必定要理清楚整個構建流程是什麼樣子的,內心得有一個譜。我本身在讀完整個vue-cli以後,我本身根據vue-cli的流程也動手搞了一個腳手架工具,僅供你們參考學習一下。地址以下:
https://github.com/ruichengping/asuna-cli
最後祝願你們能夠在前端的道路上越走越好!若是喜歡個人文章,請記得關注我哦!後續會推出更多的優質的文章哦,敬請期待!