摘要:前言前段時(shí)間看了一些的源碼,收獲頗深。介紹是一款非常優(yōu)秀的用于迅速構(gòu)建基于的應(yīng)用工具。不影響閱讀源碼,直接忽略掉。引入的包發(fā)送請(qǐng)求的工具。自定義工具用于詢問(wèn)開發(fā)者。
前言
前段時(shí)間看了一些vue-cli的源碼,收獲頗深。本想找個(gè)時(shí)間更新一篇文章,但是最近事情比較多,沒有時(shí)間去整理這些東西。趁這兩天閑了下來(lái),便整理了一下,然后跟大家分享一下。如果小伙伴們讀完之后,跟我一樣收獲很多的話,還望各位小伙伴們多多點(diǎn)贊收藏支持一下哦。
Vue-cli介紹Vue-cli是一款非常優(yōu)秀的用于迅速構(gòu)建基于Vue的Web應(yīng)用工具。他不同于creat-react-app這樣的工具,開發(fā)者只需要關(guān)注項(xiàng)目邏輯的代碼,而不需要關(guān)心webpack打包、啟動(dòng)Node服務(wù)等等諸如此類的這些問(wèn)題。Vue-cli是一款基于模板化的開發(fā)工具,等于就是把別人的項(xiàng)目結(jié)構(gòu)給照搬過(guò)來(lái),所有的配置都是暴露出來(lái)的,你可以根據(jù)實(shí)際情況去做一些配置的修改,更加靈活自由一點(diǎn)。當(dāng)然這對(duì)前端工程師提出更高的要求,考慮的東西也變多了。不過(guò)Vue-cli即將發(fā)布3.0的版本,整個(gè)Vue-cli發(fā)生了翻天覆地的變化,它采用跟creat-react-app這類工具的模式,開發(fā)者只需要關(guān)注項(xiàng)目邏輯的代碼即可。不過(guò)目前3.0還沒有出來(lái),所以這次源碼分析我采用的v2.9.3的源碼,也就是2.0的代碼。后面小伙們?cè)陂喿x的時(shí)候要注意以下。
Vue-cli項(xiàng)目結(jié)構(gòu)整個(gè)項(xiàng)目的目錄結(jié)構(gòu)如上圖所示,下面我大概介紹每個(gè)文件夾的東西大致都是干嘛的。
bin //這里放的vue的一些命令文件,比如vue init這樣的命令都是從由這里控制的
docs //一些注意事項(xiàng)啥的,不重要的目錄,可以直接忽略
lib //這里存放著一些vue-cli需要的一些自定義方法
node_modules //這里就不用我多說(shuō)了
test // 單元測(cè)試 開會(huì)vue-cli工具時(shí)會(huì)用到,我們讀源碼的時(shí)候可以直接忽略掉
一些雜七雜八的東西 //比如eslint配置、.gitignore、LICENSE等等諸如此類這些東西。不影響閱讀源碼,直接忽略掉。
package.json/README.md //這個(gè)也不用我多說(shuō)了,大家都知道的
綜合來(lái)說(shuō),我們閱讀源碼所要關(guān)注的只有bin和lib下面即可,其他的都可忽略。下面開始閱讀之旅吧Vue-cli源碼閱讀之旅
在開始讀源碼之前,首先我要介紹一個(gè)工具(commander),這是用來(lái)處理命令行的工具。具體的使用方法可查看github的README.md https://github.com/tj/command... 。小伙伴們?cè)匍喿x后面的內(nèi)容之前,建議先去了解一下commander,方便后續(xù)的理解。這里我們對(duì)commander就不做詳細(xì)介紹了。這里vue-cli采用了commander的git風(fēng)格的寫法。vue文件處理vue命令,vue-init處理vue init命令以此類推。接著我們一個(gè)一個(gè)命令看過(guò)去。
vue引入的包:
commander //用于處理命令行
作用: vue這個(gè)文件代碼很少,我就直接貼出來(lái)了。
#!/usr/bin/env node require("commander") .version(require("../package").version) .usage("[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)
這個(gè)文件主要是在用戶輸入“vue”時(shí),終端上顯示參數(shù)的使用說(shuō)明。具體的寫法可參考 https://github.com/tj/command... 上面的說(shuō)明。
vue build引入的包:
chalk //用于高亮終端打印出來(lái)的信息
作用: vue build命令在vue-cli之中已經(jīng)刪除了,源碼上做了一定的說(shuō)明。代碼不多,我就直接貼出來(lái)。
const chalk = require("chalk") console.log(chalk.yellow( " " + " We are slimming down vue-cli to optimize the initial installation by " + "removing the `vue build` command. " + " Check out Poi (https://github.com/egoist/poi) which offers the same functionality!" + " " ))vue list
#!/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) } })
引入的包:
request //發(fā)送http請(qǐng)求的工具。
chalk //用于高亮console.log打印出來(lái)的信息。
logger //自定義工具-用于日志打印。
作用:當(dāng)輸入"vue list"時(shí)(我們測(cè)試時(shí),可以直接在當(dāng)前源碼文件目錄下的終端上輸入“bin/vue-list”),vue-cli會(huì)請(qǐng)求接口,獲取官方模板的信息,然后做了一定處理,在終端上顯示出來(lái)模板名稱和對(duì)應(yīng)的說(shuō)明。
效果如下:
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 init”是用來(lái)構(gòu)建項(xiàng)目的命令,也是vue-cli的核心文件,上面的三個(gè)都是非常簡(jiǎn)單的命令,算是我們閱讀源碼的開胃菜,真正的大餐在這里。
工作流程在講代碼之前,首先我們要講一下整個(gè)vue-cli初始項(xiàng)目的流程,然后我們沿著流程一步一步走下去。
整個(gè)vue init大致流程如我上圖所示,應(yīng)該還是比較好理解的。這里我大致闡述一下大致的流程。
vue-cli會(huì)先判斷你的模板在遠(yuǎn)程github倉(cāng)庫(kù)上還是在你的本地某個(gè)文件里面,若是本地文件夾則會(huì)立即跳到第3步,反之則走第2步。
第2步會(huì)判斷是否為官方模板,官方模板則會(huì)從官方github倉(cāng)庫(kù)中下載模板到本地的默認(rèn)倉(cāng)庫(kù)下,即根目錄下.vue-templates文件夾下。
第3步則讀取模板目錄下meta.js或者meta.json文件,根據(jù)里面的內(nèi)容會(huì)詢問(wèn)開發(fā)者,根據(jù)開發(fā)者的回答,確定一些修改。
根據(jù)模板內(nèi)容以及開發(fā)者的回答,渲染出項(xiàng)目結(jié)構(gòu)并生成到指定目錄。
源碼內(nèi)容這里vue-init文件的代碼比較多,我這里就拆分幾塊來(lái)看。首先我先把整個(gè)文件的結(jié)構(gòu)列出來(lái),方便后續(xù)的閱讀。
/** * 引入一大堆包 */ const program = require("commander") ... /** * 配置commander的使用方法 */ program .usage("[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() //如果沒有輸入?yún)?shù),終端顯示幫助 } help() /** * 定義一大堆變量 */ let template = program.args[0] ... /** * 判斷是否輸入項(xiàng)目名 是 - 直接執(zhí)行run函數(shù) 否- 詢問(wèn)開發(fā)者是否在當(dāng)前目錄下生成項(xiàng)目,開發(fā)者回答“是” 也執(zhí)行run函數(shù) 否則不執(zhí)行run函數(shù) */ /** * 定義主函數(shù) run */ function run (){ ... } /** * 定義下載模板并生產(chǎn)項(xiàng)目的函數(shù) downloadAndGenerate */ function downloadAndGenerate(){ ... }
整個(gè)文件大致的東西入上面所示,后面我們將一塊一塊內(nèi)容來(lái)看。
引入的一堆包const download = require("download-git-repo") //用于下載遠(yuǎn)程倉(cāng)庫(kù)至本地 支持GitHub、GitLab、Bitbucket const program = require("commander") //命令行處理工具 const exists = require("fs").existsSync //node自帶的fs模塊下的existsSync方法,用于檢測(cè)路徑是否存在。(會(huì)阻塞) const path = require("path") //node自帶的path模塊,用于拼接路徑 const ora = require("ora") //用于命令行上的加載效果 const home = require("user-home") //用于獲取用戶的根目錄 const tildify = require("tildify") //將絕對(duì)路徑轉(zhuǎn)換成帶波浪符的路徑 const chalk = require("chalk")// 用于高亮終端打印出的信息 const inquirer = require("inquirer") //用于命令行與開發(fā)者交互 const rm = require("rimraf").sync // 相當(dāng)于UNIX的“rm -rf”命令 const logger = require("../lib/logger") //自定義工具-用于日志打印 const generate = require("../lib/generate") //自定義工具-用于基于模板構(gòu)建項(xiàng)目 const checkVersion = require("../lib/check-version") //自定義工具-用于檢測(cè)vue-cli版本的工具 const warnings = require("../lib/warnings") //自定義工具-用于模板的警告 const localPath = require("../lib/local-path") //自定義工具-用于路徑的處理 const isLocalPath = localPath.isLocalPath //判斷是否是本地路徑 const getTemplatePath = localPath.getTemplatePath //獲取本地模板的絕對(duì)路徑定義的一堆變量
let template = program.args[0] //模板名稱 const hasSlash = template.indexOf("/") > -1 //是否有斜杠,后面將會(huì)用來(lái)判定是否為官方模板 const rawName = program.args[1] //項(xiàng)目構(gòu)建目錄名 const inPlace = !rawName || rawName === "." // 沒寫或者“.”,表示當(dāng)前目錄下構(gòu)建項(xiàng)目 const name = inPlace ? path.relative("../", process.cwd()) : rawName //如果在當(dāng)前目錄下構(gòu)建項(xiàng)目,當(dāng)前目錄名為項(xiàng)目構(gòu)建目錄名,否則是當(dāng)前目錄下的子目錄【rawName】為項(xiàng)目構(gòu)建目錄名 const to = path.resolve(rawName || ".") //項(xiàng)目構(gòu)建目錄的絕對(duì)路徑 const clone = program.clone || false //是否采用clone模式,提供給“download-git-repo”的參數(shù) const tmp = path.join(home, ".vue-templates", template.replace(/[/:]/g, "-")) //遠(yuǎn)程模板下載到本地的路徑主邏輯
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() }
對(duì)著上面代碼,vue-cli會(huì)判斷inPlace和exists(to),true則詢問(wèn)開發(fā)者,當(dāng)開發(fā)者回答“yes”的時(shí)候執(zhí)行run函數(shù),否則直接執(zhí)行run函數(shù)。這里詢問(wèn)開發(fā)者的問(wèn)題有如下兩個(gè):
Generate project in current directory? //是否在當(dāng)前目錄下構(gòu)建項(xiàng)目
Target directory exists. Continue? //構(gòu)建目錄已存在,是否繼續(xù)
這兩個(gè)問(wèn)題依靠變量inPlace來(lái)選擇,下面我看一下變量inPlace是怎么得來(lái)的。
const rawName = program.args[1] //rawName為命令行的第二個(gè)參數(shù)(項(xiàng)目構(gòu)建目錄的相對(duì)目錄) const inPlace = !rawName || rawName === "." //rawName存在或者為“.”的時(shí)候,視為在當(dāng)前目錄下構(gòu)建
通過(guò)上面的描述可知,變量inPlace用于判斷是否在當(dāng)前目錄下構(gòu)建,因此變量inPlace為true時(shí),則會(huì)提示Generate project in current directory? ,反之當(dāng)變量inPlace為false時(shí),此時(shí)exists(to)一定為true,便提示Target directory exists. Continue?。
Run函數(shù)邏輯:
源碼:
function run () { // check if template is local if (isLocalPath(template)) { //是否是本地模板 const templatePath = getTemplatePath(template) //獲取絕對(duì)路徑 if (exists(templatePath)) { //判斷模板所在路徑是否存在 //渲染模板 generate(name, templatePath, to, err => { if (err) logger.fatal(err) console.log() logger.success("Generated "%s".", name) }) } else { //打印錯(cuò)誤日志,提示本地模板不存在 logger.fatal("Local template "%s" not found.", template) } } else { checkVersion(() => { //檢查版本號(hào) if (!hasSlash) { //官方模板還是第三方模板 // use official templates // 從這句話以及download-git-repo的用法,我們得知了vue的官方的模板庫(kù)的地址: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" //發(fā)出警告 warnings.v2SuffixTemplatesDeprecated(template, inPlace ? "" : name) return } // warnings.v2BranchIsNowDefault(template, inPlace ? "" : name) downloadAndGenerate(officialTemplate)//下載模板 } } else { downloadAndGenerate(template)//下載模板 } }) } }downloadAndGenerate函數(shù)
function downloadAndGenerate (template) { const spinner = ora("downloading template") spinner.start()//顯示加載狀態(tài) // Remove if local template exists if (exists(tmp)) rm(tmp) //當(dāng)前模板庫(kù)是否存在該模板,存在就刪除 //下載模板 template-模板名 tmp- 模板路徑 clone-是否采用git clone模板 err-錯(cuò)誤短信 download(template, tmp, { clone }, err => { spinner.stop() //隱藏加載狀態(tài) //如果有錯(cuò)誤,打印錯(cuò)誤日志 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 generate.js (★)
lib文件下最重要的js文件,他是我們構(gòu)建項(xiàng)目中最重要的一環(huán),根據(jù)模板渲染成我們需要的項(xiàng)目。這塊內(nèi)容是需要我們重點(diǎn)關(guān)注的。
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 注冊(cè)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對(duì)象 const data = Object.assign(metalsmith.metadata(), { destDirName: name, inPlace: dest === process.cwd(), noEscape: true })//添加一些變量至metalsmith中,并獲取metalsmith中全部變量 //注冊(cè)配置對(duì)象中的helper opts.helpers && Object.keys(opts.helpers).map(key => { Handlebars.registerHelper(key, opts.helpers[key]) }) const helpers = { chalk, logger } //配置對(duì)象是否有before函數(shù),是則執(zhí)行 if (opts.metalsmith && typeof opts.metalsmith.before === "function") { opts.metalsmith.before(metalsmith, opts, helpers) } metalsmith.use(askQuestions(opts.prompts)) //詢問(wèn)問(wèn)題 .use(filterFiles(opts.filters)) //過(guò)濾文件 .use(renderTemplateFiles(opts.skipInterpolation)) //渲染模板文件 //配置對(duì)象是否有after函數(shù),是則執(zhí)行 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") { //配置對(duì)象有complete函數(shù)則執(zhí)行 const helpers = { chalk, logger, files } opts.complete(data, helpers) } else { //配置對(duì)象有completeMessage,執(zhí)行l(wèi)ogMessage函數(shù) 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是一個(gè)數(shù)組 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 // 跳過(guò)符合skipInterpolation的要求的file if (skipInterpolation && multimatch([file], skipInterpolation, { dot: true }).length) { return next() } //獲取文件的文本內(nèi)容 const str = files[file].contents.toString() // do not attempt to render files that do not have mustaches //跳過(guò)不符合handlebars語(yǔ)法的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直接退出函數(shù) render(message, data, (err, res) => { if (err) { console.error(" Error when rendering template complete message: " + err.message.trim()) //渲染錯(cuò)誤打印錯(cuò)誤信息 } else { console.log(" " + res.split(/ ? /g).map(line => " " + line).join(" ")) //渲染成功打印最終渲染的結(jié)果 } }) }
引入的包:
chalk //用于高亮終端打印出來(lái)的信息。
metalsmith //靜態(tài)網(wǎng)站生成器。
handlebars //知名的模板引擎。
async //非常強(qiáng)大的異步處理工具。
consolidate //支持各種模板引擎的渲染。
path //node自帶path模塊,用于路徑的處理。
multimatch // 可以支持多個(gè)條件的匹配。
options //自定義工具-用于獲取模板配置。
ask //自定義工具-用于詢問(wèn)開發(fā)者。
filter //自定義工具-用于文件過(guò)濾。
logger //自定義工具-用于日志打印。
主邏輯:
獲取模板配置 -->初始化Metalsmith -->添加一些變量至Metalsmith -->handlebars模板注冊(cè)helper -->配置對(duì)象中是否有before函數(shù),有則執(zhí)行 -->詢問(wèn)問(wèn)題 -->過(guò)濾文件 -->渲染模板文件 -->配置對(duì)象中是否有after函數(shù),有則執(zhí)行 -->最后構(gòu)建項(xiàng)目?jī)?nèi)容 -->構(gòu)建完成,成功若配置對(duì)象中有complete函數(shù)則執(zhí)行,否則打印配置對(duì)象中的completeMessage信息,如果有錯(cuò)誤,執(zhí)行回調(diào)函數(shù)done(err)
其他函數(shù):
askQuestions: 詢問(wèn)問(wèn)題
filterFiles: 過(guò)濾文件
renderTemplateFiles: 渲染模板文件
logMessage: 用于構(gòu)建成功時(shí),打印信息
Metalsmith插件格式:
functionoptions.js{ 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 } }
引入的包:
path //node自帶path模塊,用于路徑的處理
read-metadata //用于讀取json或者yaml元數(shù)據(jù)文件并返回一個(gè)對(duì)象
fs.existsSync //node自帶fs模塊的existsSync方法,用于檢測(cè)路徑是否存在
git-user //獲取本地的git配置
validate-npm-package-name //用于npm包的名字是否是合法的
作用:
主方法: 第一步:先獲取模板的配置文件信息;第二步:設(shè)置name字段并檢測(cè)name是否合法;第三步:只是author字段。
getMetadata: 獲取meta.js或則meta.json中的配置信息
setDefault: 用于向配置對(duì)象中添加一下默認(rèn)字段
setValidateName: 用于檢測(cè)配置對(duì)象中name字段是否合法
git-user.jsconst 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 || "") }
引入的包:
child_process.execSync //node自帶模塊child_process中的execSync方法用于新開一個(gè)shell并執(zhí)行相應(yīng)的command,并返回相應(yīng)的輸出。
作用: 用于獲取本地的git配置的用戶名和郵件,并返回格式 姓名<郵箱> 的字符串。
eval.jsconst 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)) } }
引入的包:
chalk //用于高亮終端打印出來(lái)的信息。
作用: 在data的作用域執(zhí)行exp表達(dá)式并返回其執(zhí)行得到的值
ask.jsconst 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 交于下一個(gè)metalsmith插件處理 */ module.exports = function ask (prompts, data, done) { //遍歷處理prompts下的每一個(gè)字段 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() } //獲取默認(rèn)值 let promptDefault = prompt.default if (typeof prompt.default === "function") { promptDefault = function () { return prompt.default.bind(this)(data) } } //設(shè)置問(wèn)題,具體使用方法可去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])) { //當(dāng)答案是一個(gè)數(shù)組時(shí) data[key] = {} answers[key].forEach(multiChoiceAnswer => { data[key][multiChoiceAnswer] = true }) } else if (typeof answers[key] === "string") { //當(dāng)答案是一個(gè)字符串時(shí) data[key] = answers[key].replace(/"/g, """) } else { //其他情況 data[key] = answers[key] } done() }).catch(done) }
引入的包:
async //異步處理工具。
inquirer //命令行與用戶之間的交互
eval //返回某作用下表達(dá)式的值
作用: 將meta.js或者meta.json中的prompts字段解析成對(duì)應(yīng)的問(wèn)題詢問(wèn)。
filter.jsconst match = require("minimatch") const evaluate = require("./eval") /** * files 模板內(nèi)的所有文件 * filters meta.js或者meta.json的filters字段 * data metalsmith.metadata() * done 交于下一個(gè)metalsmith插件處理 */ module.exports = (files, filters, data, done) => { if (!filters) { //meta.js或者meta.json沒有filters字段直接跳過(guò)交于下一個(gè)metalsmith插件處理 return done() } //獲取所有文件的名字 const fileNames = Object.keys(files) //遍歷meta.js或者meta.json沒有filters下的所有字段 Object.keys(filters).forEach(glob => { //遍歷所有文件名 fileNames.forEach(file => { //如果有文件名跟filters下的某一個(gè)字段匹配上 if (match(file, glob, { dot: true })) { const condition = filters[glob] if (!evaluate(condition, data)) { //如果metalsmith.metadata()下condition表達(dá)式不成立,刪除該文件 delete files[file] } } }) }) done() }
引入的包:
minimatch //字符匹配工具
eval //返回某作用下表達(dá)式的值
作用: 根據(jù)metalsmith.metadata()刪除一些不需要的模板文件,而metalsmith.metadata()主要在ask.js中改變的,也就是說(shuō)ask.js中獲取到用戶的需求。
logger.jsconst 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) }
引入的包:
chalk //用于高亮終端打印出來(lái)的信息。
format //node自帶的util模塊中的format方法。
作用: logger.js主要提供三個(gè)方法log(常規(guī)日志)、fatal(錯(cuò)誤日志)、success(成功日志)。每個(gè)方法都挺簡(jiǎn)單的,我就不錯(cuò)過(guò)多的解釋了。
local-path.jsconst 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)) } }
引入的包:
path //node自帶的路徑處理工具。
作用:
isLocalPath: UNIX (以“.”或者"/"開頭) WINDOWS(以形如:“C:”的方式開頭)。
getTemplatePath: templatePath是否為絕對(duì)路徑,是則返回templatePath 否則轉(zhuǎn)換成絕對(duì)路徑并規(guī)范化。
check-version.jsconst 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() }) }
引入的包:
request //http請(qǐng)求工具。
semver //版本號(hào)處理工具。
chalk //用于高亮終端打印出來(lái)的信息。
作用:
第一步:檢查本地的node版本號(hào),是否達(dá)到package.json文件中對(duì)node版本的要求,若低于nodepackage.json文件中要求的版本,則直接要求開發(fā)者更新自己的node版本。反之,可開始第二步。
第二步: 通過(guò)請(qǐng)求https://registry.npmjs.org/vu...來(lái)獲取vue-cli的最新版本號(hào),跟package.json中的version字段進(jìn)行比較,若本地的版本號(hào)小于最新的版本號(hào),則提示有最新版本可以更新。這里需要注意的是,這里檢查版本號(hào)并不影響后續(xù)的流程,即便本地的vue-cli版本不是最新的,也不影響構(gòu)建,僅僅提示一下。
warnings.jsconst 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() } }
引入的包:
chalk //用于高亮終端打印出來(lái)的信息。
作用:
v2SuffixTemplatesDeprecated:提示帶“-2.0”的模板已經(jīng)棄用了,官方模板默認(rèn)用2.0了。不需要用“-2.0”來(lái)區(qū)分vue1.0和vue2.0了。
v2BranchIsNowDefault: 這個(gè)方法在vue-init文件中已經(jīng)被注釋掉,不再使用了。在vue1.0向vue2.0過(guò)渡的時(shí)候用到過(guò),現(xiàn)在都是默認(rèn)2.0了,自然也就不用了。
總結(jié)由于代碼比較多,很多代碼我就沒有一一細(xì)講了,一些比較簡(jiǎn)單或者不是很重要的js文件,我就單單說(shuō)明了它的作用了。但是重點(diǎn)的js文件,我還是加了很多注解在上面。其中我個(gè)人認(rèn)為比較重點(diǎn)的文件就是vue-init、generate.js、options.js、ask.js、filter.js,這五個(gè)文件構(gòu)成了vue-cli構(gòu)建項(xiàng)目的主流程,因此需要我們花更多的時(shí)間在上面。另外,我們?cè)谧x源碼的過(guò)程中,一定要理清楚整個(gè)構(gòu)建流程是什么樣子的,心里得有一個(gè)譜。讀完源碼之后,我個(gè)人是建議自己動(dòng)手搭建一個(gè)構(gòu)建工具,這樣的話印象才會(huì)更加深刻,個(gè)人成長(zhǎng)會(huì)更大點(diǎn)。我自己在讀完整個(gè)vue-cli之后,我自己根據(jù)vue-cli的流程也動(dòng)手搞了一個(gè)腳手架工具,僅供大家參考學(xué)習(xí)一下。地址如下:
https://github.com/ruichengpi...
最后祝愿大家可以在前端的道路上越走越好!如果喜歡我的文章,請(qǐng)記得關(guān)注我哦!后續(xù)會(huì)推出更多的優(yōu)質(zhì)的文章哦,敬請(qǐng)期待!
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/93689.html
摘要:作為目前最熱門最具前景的前端框架之一,其提供了一種幫助我們快速構(gòu)建并開發(fā)前端項(xiàng)目的新的思維模式。的新版本,的簡(jiǎn)稱。的包管理工具,用于同一管理我們前端項(xiàng)目中需要用到的包插件工具命令等,便于開發(fā)和維護(hù)。 Vue.js作為目前最熱門最具前景的前端框架之一,其提供了一種幫助我們快速構(gòu)建并開發(fā)前端項(xiàng)目的新的思維模式。本文旨在幫助大家認(rèn)識(shí)Vue.js,了解Vue.js的開發(fā)流程,并進(jìn)一步理解如何通...
摘要:初來(lái)乍到,請(qǐng)多多關(guān)照也許這是第一套基于搭建基于的項(xiàng)目教程。年月初升級(jí)到了新的版本版,跟版本項(xiàng)目結(jié)構(gòu)發(fā)生了很大的改變,目錄結(jié)構(gòu)更簡(jiǎn)潔更科學(xué)。并且可以選配,在此之前配置略麻煩,是的超級(jí),靜態(tài)類型,便捷的注解,使前端代碼優(yōu)雅。 前言 hello~ 大家好。 初來(lái)乍到,請(qǐng)多多關(guān)照 ?(??????)??也許這是第一套基于 vue-cli 3.x 搭建基于 typescript 的vue項(xiàng)目教程...
摘要:前言是現(xiàn)階段很流行的前端框架,很多人通過(guò)官方文檔的學(xué)習(xí),對(duì)的使用都有了一定的了解,但再在項(xiàng)目工程化處理的時(shí)候,卻發(fā)現(xiàn)不知道改怎么更好的管理自己的項(xiàng)目,如何去引入一些框架以及全家桶其他框架的使用,以下將詳細(xì)地介紹本人在處理工程文件構(gòu)建的過(guò)程對(duì) 前言 vue是現(xiàn)階段很流行的前端框架,很多人通過(guò)vue官方文檔的學(xué)習(xí),對(duì)vue的使用都有了一定的了解,但再在項(xiàng)目工程化處理的時(shí)候,卻發(fā)現(xiàn)不知道改怎...
摘要:前言是現(xiàn)階段很流行的前端框架,很多人通過(guò)官方文檔的學(xué)習(xí),對(duì)的使用都有了一定的了解,但再在項(xiàng)目工程化處理的時(shí)候,卻發(fā)現(xiàn)不知道改怎么更好的管理自己的項(xiàng)目,如何去引入一些框架以及全家桶其他框架的使用,以下將詳細(xì)地介紹本人在處理工程文件構(gòu)建的過(guò)程對(duì) 前言 vue是現(xiàn)階段很流行的前端框架,很多人通過(guò)vue官方文檔的學(xué)習(xí),對(duì)vue的使用都有了一定的了解,但再在項(xiàng)目工程化處理的時(shí)候,卻發(fā)現(xiàn)不知道改怎...
閱讀 803·2021-10-09 09:58
閱讀 668·2021-08-27 16:24
閱讀 1753·2019-08-30 14:15
閱讀 2409·2019-08-30 11:04
閱讀 2120·2019-08-29 18:43
閱讀 2188·2019-08-29 15:20
閱讀 2749·2019-08-26 12:20
閱讀 1651·2019-08-26 11:44