国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

走進Vue-cli源碼,自己動手搭建前端腳手架工具

Apollo / 3305人閱讀

摘要:前言前段時間看了一些的源碼,收獲頗深。介紹是一款非常優秀的用于迅速構建基于的應用工具。不影響閱讀源碼,直接忽略掉。引入的包發送請求的工具。自定義工具用于詢問開發者。

前言

前段時間看了一些vue-cli的源碼,收獲頗深。本想找個時間更新一篇文章,但是最近事情比較多,沒有時間去整理這些東西。趁這兩天閑了下來,便整理了一下,然后跟大家分享一下。如果小伙伴們讀完之后,跟我一樣收獲很多的話,還望各位小伙伴們多多點贊收藏支持一下哦。

Vue-cli介紹

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-cli項目結構

整個項目的目錄結構如上圖所示,下面我大概介紹每個文件夾的東西大致都是干嘛的。

bin //這里放的vue的一些命令文件,比如vue init這樣的命令都是從由這里控制的

docs //一些注意事項啥的,不重要的目錄,可以直接忽略

lib //這里存放著一些vue-cli需要的一些自定義方法

node_modules //這里就不用我多說了

test // 單元測試 開會vue-cli工具時會用到,我們讀源碼的時候可以直接忽略掉

一些雜七雜八的東西 //比如eslint配置、.gitignore、LICENSE等等諸如此類這些東西。不影響閱讀源碼,直接忽略掉。

package.json/README.md //這個也不用我多說了,大家都知道的

</>復制代碼

  1. 綜合來說,我們閱讀源碼所要關注的只有bin和lib下面即可,其他的都可忽略。下面開始閱讀之旅吧
Vue-cli源碼閱讀之旅

在開始讀源碼之前,首先我要介紹一個工具(commander),這是用來處理命令行的工具。具體的使用方法可查看github的README.md https://github.com/tj/command... 。小伙伴們再閱讀后面的內容之前,建議先去了解一下commander,方便后續的理解。這里我們對commander就不做詳細介紹了。這里vue-cli采用了commander的git風格的寫法。vue文件處理vue命令,vue-init處理vue init命令以此類推。接著我們一個一個命令看過去。

vue

引入的包:

commander //用于處理命令行

作用: vue這個文件代碼很少,我就直接貼出來了。

</>復制代碼

  1. #!/usr/bin/env node
  2. require("commander")
  3. .version(require("../package").version)
  4. .usage(" [options]")
  5. .command("init", "generate a new project from a template")
  6. .command("list", "list available official templates")
  7. .command("build", "prototype a new project")
  8. .parse(process.argv)

這個文件主要是在用戶輸入“vue”時,終端上顯示參數的使用說明。具體的寫法可參考 https://github.com/tj/command... 上面的說明。

vue build

引入的包:

chalk //用于高亮終端打印出來的信息

作用: vue build命令在vue-cli之中已經刪除了,源碼上做了一定的說明。代碼不多,我就直接貼出來。

</>復制代碼

  1. const chalk = require("chalk")
  2. console.log(chalk.yellow(
  3. "
  4. " +
  5. " We are slimming down vue-cli to optimize the initial installation by " +
  6. "removing the `vue build` command.
  7. " +
  8. " Check out Poi (https://github.com/egoist/poi) which offers the same functionality!" +
  9. "
  10. "
  11. ))
vue list

</>復制代碼

  1. #!/usr/bin/env node
  2. const logger = require("../lib/logger")
  3. const request = require("request")
  4. const chalk = require("chalk")
  5. /**
  6. * Padding.
  7. */
  8. console.log()
  9. process.on("exit", () => {
  10. console.log()
  11. })
  12. /**
  13. * List repos.
  14. */
  15. request({
  16. url: "https://api.github.com/users/vuejs-templates/repos",
  17. headers: {
  18. "User-Agent": "vue-cli"
  19. }
  20. }, (err, res, body) => {
  21. if (err) logger.fatal(err)
  22. const requestBody = JSON.parse(body)
  23. if (Array.isArray(requestBody)) {
  24. console.log(" Available official templates:")
  25. console.log()
  26. requestBody.forEach(repo => {
  27. console.log(
  28. " " + chalk.yellow("★") +
  29. " " + chalk.blue(repo.name) +
  30. " - " + repo.description)
  31. })
  32. } else {
  33. console.error(requestBody.message)
  34. }
  35. })

引入的包:

request //發送http請求的工具。

chalk //用于高亮console.log打印出來的信息。

logger //自定義工具-用于日志打印。

作用:當輸入"vue list"時(我們測試時,可以直接在當前源碼文件目錄下的終端上輸入“bin/vue-list”),vue-cli會請求接口,獲取官方模板的信息,然后做了一定處理,在終端上顯示出來模板名稱和對應的說明。

效果如下:

</>復制代碼

  1. Available official templates:
  2. ★ browserify - A full-featured Browserify + vueify setup with hot-reload, linting & unit testing.
  3. ★ browserify-simple - A simple Browserify + vueify setup for quick prototyping.
  4. ★ pwa - PWA template for vue-cli based on the webpack template
  5. ★ simple - The simplest possible Vue setup in a single HTML file
  6. ★ webpack - A full-featured Webpack + vue-loader setup with hot reload, linting, testing & css extraction.
  7. ★ webpack-simple - A simple Webpack + vue-loader setup for quick prototyping.
vue init

vue init”是用來構建項目的命令,也是vue-cli的核心文件,上面的三個都是非常簡單的命令,算是我們閱讀源碼的開胃菜,真正的大餐在這里。

工作流程

在講代碼之前,首先我們要講一下整個vue-cli初始項目的流程,然后我們沿著流程一步一步走下去。

整個vue init大致流程如我上圖所示,應該還是比較好理解的。這里我大致闡述一下大致的流程。

vue-cli會先判斷你的模板在遠程github倉庫上還是在你的本地某個文件里面,若是本地文件夾則會立即跳到第3步,反之則走第2步。

第2步會判斷是否為官方模板,官方模板則會從官方github倉庫中下載模板到本地的默認倉庫下,即根目錄下.vue-templates文件夾下。

第3步則讀取模板目錄下meta.js或者meta.json文件,根據里面的內容會詢問開發者,根據開發者的回答,確定一些修改。

根據模板內容以及開發者的回答,渲染出項目結構并生成到指定目錄。

源碼內容

這里vue-init文件的代碼比較多,我這里就拆分幾塊來看。首先我先把整個文件的結構列出來,方便后續的閱讀。

</>復制代碼

  1. /**
  2. * 引入一大堆包
  3. */
  4. const program = require("commander")
  5. ...
  6. /**
  7. * 配置commander的使用方法
  8. */
  9. program
  10. .usage(" [project-name]")
  11. .option("-c, --clone", "use git clone")
  12. .option("--offline", "use cached template")
  13. /**
  14. * 定義commander的help方法
  15. */
  16. program.on("--help", () => {
  17. console.log(" Examples:")
  18. console.log()
  19. console.log(chalk.gray(" # create a new project with an official template"))
  20. console.log(" $ vue init webpack my-project")
  21. console.log()
  22. console.log(chalk.gray(" # create a new project straight from a github template"))
  23. console.log(" $ vue init username/repo my-project")
  24. console.log()
  25. })
  26. function help () {
  27. program.parse(process.argv)
  28. if (program.args.length < 1) return program.help() //如果沒有輸入參數,終端顯示幫助
  29. }
  30. help()
  31. /**
  32. * 定義一大堆變量
  33. */
  34. let template = program.args[0]
  35. ...
  36. /**
  37. * 判斷是否輸入項目名 是 - 直接執行run函數 否- 詢問開發者是否在當前目錄下生成項目,開發者回答“是” 也執行run函數 否則不執行run函數
  38. */
  39. /**
  40. * 定義主函數 run
  41. */
  42. function run (){
  43. ...
  44. }
  45. /**
  46. * 定義下載模板并生產項目的函數 downloadAndGenerate
  47. */
  48. function downloadAndGenerate(){
  49. ...
  50. }

整個文件大致的東西入上面所示,后面我們將一塊一塊內容來看。

引入的一堆包

</>復制代碼

  1. const download = require("download-git-repo") //用于下載遠程倉庫至本地 支持GitHub、GitLab、Bitbucket
  2. const program = require("commander") //命令行處理工具
  3. const exists = require("fs").existsSync //node自帶的fs模塊下的existsSync方法,用于檢測路徑是否存在。(會阻塞)
  4. const path = require("path") //node自帶的path模塊,用于拼接路徑
  5. const ora = require("ora") //用于命令行上的加載效果
  6. const home = require("user-home") //用于獲取用戶的根目錄
  7. const tildify = require("tildify") //將絕對路徑轉換成帶波浪符的路徑
  8. const chalk = require("chalk")// 用于高亮終端打印出的信息
  9. const inquirer = require("inquirer") //用于命令行與開發者交互
  10. const rm = require("rimraf").sync // 相當于UNIX的“rm -rf”命令
  11. const logger = require("../lib/logger") //自定義工具-用于日志打印
  12. const generate = require("../lib/generate") //自定義工具-用于基于模板構建項目
  13. const checkVersion = require("../lib/check-version") //自定義工具-用于檢測vue-cli版本的工具
  14. const warnings = require("../lib/warnings") //自定義工具-用于模板的警告
  15. const localPath = require("../lib/local-path") //自定義工具-用于路徑的處理
  16. const isLocalPath = localPath.isLocalPath //判斷是否是本地路徑
  17. const getTemplatePath = localPath.getTemplatePath //獲取本地模板的絕對路徑
定義的一堆變量

</>復制代碼

  1. let template = program.args[0] //模板名稱
  2. const hasSlash = template.indexOf("/") > -1 //是否有斜杠,后面將會用來判定是否為官方模板
  3. const rawName = program.args[1] //項目構建目錄名
  4. const inPlace = !rawName || rawName === "." // 沒寫或者“.”,表示當前目錄下構建項目
  5. const name = inPlace ? path.relative("../", process.cwd()) : rawName //如果在當前目錄下構建項目,當前目錄名為項目構建目錄名,否則是當前目錄下的子目錄【rawName】為項目構建目錄名
  6. const to = path.resolve(rawName || ".") //項目構建目錄的絕對路徑
  7. const clone = program.clone || false //是否采用clone模式,提供給“download-git-repo”的參數
  8. const tmp = path.join(home, ".vue-templates", template.replace(/[/:]/g, "-")) //遠程模板下載到本地的路徑
主邏輯

</>復制代碼

  1. if (inPlace || exists(to)) {
  2. inquirer.prompt([{
  3. type: "confirm",
  4. message: inPlace
  5. ? "Generate project in current directory?"
  6. : "Target directory exists. Continue?",
  7. name: "ok"
  8. }]).then(answers => {
  9. if (answers.ok) {
  10. run()
  11. }
  12. }).catch(logger.fatal)
  13. } else {
  14. run()
  15. }

對著上面代碼,vue-cli會判斷inPlace和exists(to),true則詢問開發者,當開發者回答“yes”的時候執行run函數,否則直接執行run函數。這里詢問開發者的問題有如下兩個:

Generate project in current directory? //是否在當前目錄下構建項目

Target directory exists. Continue? //構建目錄已存在,是否繼續

這兩個問題依靠變量inPlace來選擇,下面我看一下變量inPlace是怎么得來的。

</>復制代碼

  1. const rawName = program.args[1] //rawName為命令行的第二個參數(項目構建目錄的相對目錄)
  2. const inPlace = !rawName || rawName === "." //rawName存在或者為“.”的時候,視為在當前目錄下構建

通過上面的描述可知,變量inPlace用于判斷是否在當前目錄下構建,因此變量inPlace為true時,則會提示Generate project in current directory? ,反之當變量inPlace為false時,此時exists(to)一定為true,便提示Target directory exists. Continue?

Run函數

邏輯:

源碼:

</>復制代碼

  1. function run () {
  2. // check if template is local
  3. if (isLocalPath(template)) { //是否是本地模板
  4. const templatePath = getTemplatePath(template) //獲取絕對路徑
  5. if (exists(templatePath)) { //判斷模板所在路徑是否存在
  6. //渲染模板
  7. generate(name, templatePath, to, err => {
  8. if (err) logger.fatal(err)
  9. console.log()
  10. logger.success("Generated "%s".", name)
  11. })
  12. } else {
  13. //打印錯誤日志,提示本地模板不存在
  14. logger.fatal("Local template "%s" not found.", template)
  15. }
  16. } else {
  17. checkVersion(() => { //檢查版本號
  18. if (!hasSlash) { //官方模板還是第三方模板
  19. // use official templates
  20. // 從這句話以及download-git-repo的用法,我們得知了vue的官方的模板庫的地址:https://github.com/vuejs-templates
  21. const officialTemplate = "vuejs-templates/" + template
  22. if (template.indexOf("#") !== -1) { //模板名是否帶"#"
  23. downloadAndGenerate(officialTemplate) //下載模板
  24. } else {
  25. if (template.indexOf("-2.0") !== -1) { //是都帶"-2.0"
  26. //發出警告
  27. warnings.v2SuffixTemplatesDeprecated(template, inPlace ? "" : name)
  28. return
  29. }
  30. // warnings.v2BranchIsNowDefault(template, inPlace ? "" : name)
  31. downloadAndGenerate(officialTemplate)//下載模板
  32. }
  33. } else {
  34. downloadAndGenerate(template)//下載模板
  35. }
  36. })
  37. }
  38. }
downloadAndGenerate函數

</>復制代碼

  1. function downloadAndGenerate (template) {
  2. const spinner = ora("downloading template")
  3. spinner.start()//顯示加載狀態
  4. // Remove if local template exists
  5. if (exists(tmp)) rm(tmp) //當前模板庫是否存在該模板,存在就刪除
  6. //下載模板 template-模板名 tmp- 模板路徑 clone-是否采用git clone模板 err-錯誤短信
  7. download(template, tmp, { clone }, err => {
  8. spinner.stop() //隱藏加載狀態
  9. //如果有錯誤,打印錯誤日志
  10. if (err) logger.fatal("Failed to download repo " + template + ": " + err.message.trim())
  11. //渲染模板
  12. generate(name, tmp, to, err => {
  13. if (err) logger.fatal(err)
  14. console.log()
  15. logger.success("Generated "%s".", name)
  16. })
  17. })
  18. }
lib generate.js (★)

lib文件下最重要的js文件,他是我們構建項目中最重要的一環,根據模板渲染成我們需要的項目。這塊內容是需要我們重點關注的。

</>復制代碼

  1. const chalk = require("chalk")
  2. const Metalsmith = require("metalsmith")
  3. const Handlebars = require("handlebars")
  4. const async = require("async")
  5. const render = require("consolidate").handlebars.render
  6. const path = require("path")
  7. const multimatch = require("multimatch")
  8. const getOptions = require("./options")
  9. const ask = require("./ask")
  10. const filter = require("./filter")
  11. const logger = require("./logger")
  12. // register handlebars helper 注冊handlebars的helper
  13. Handlebars.registerHelper("if_eq", function (a, b, opts) {
  14. return a === b
  15. ? opts.fn(this)
  16. : opts.inverse(this)
  17. })
  18. Handlebars.registerHelper("unless_eq", function (a, b, opts) {
  19. return a === b
  20. ? opts.inverse(this)
  21. : opts.fn(this)
  22. })
  23. /**
  24. * Generate a template given a `src` and `dest`.
  25. *
  26. * @param {String} name
  27. * @param {String} src
  28. * @param {String} dest
  29. * @param {Function} done
  30. */
  31. module.exports = function generate (name, src, dest, done) {
  32. const opts = getOptions(name, src) //獲取配置
  33. const metalsmith = Metalsmith(path.join(src, "template")) //初始化Metalsmith對象
  34. const data = Object.assign(metalsmith.metadata(), {
  35. destDirName: name,
  36. inPlace: dest === process.cwd(),
  37. noEscape: true
  38. })//添加一些變量至metalsmith中,并獲取metalsmith中全部變量
  39. //注冊配置對象中的helper
  40. opts.helpers && Object.keys(opts.helpers).map(key => {
  41. Handlebars.registerHelper(key, opts.helpers[key])
  42. })
  43. const helpers = { chalk, logger }
  44. //配置對象是否有before函數,是則執行
  45. if (opts.metalsmith && typeof opts.metalsmith.before === "function") {
  46. opts.metalsmith.before(metalsmith, opts, helpers)
  47. }
  48. metalsmith.use(askQuestions(opts.prompts)) //詢問問題
  49. .use(filterFiles(opts.filters)) //過濾文件
  50. .use(renderTemplateFiles(opts.skipInterpolation)) //渲染模板文件
  51. //配置對象是否有after函數,是則執行
  52. if (typeof opts.metalsmith === "function") {
  53. opts.metalsmith(metalsmith, opts, helpers)
  54. } else if (opts.metalsmith && typeof opts.metalsmith.after === "function") {
  55. opts.metalsmith.after(metalsmith, opts, helpers)
  56. }
  57. metalsmith.clean(false)
  58. .source(".") // start from template root instead of `./src` which is Metalsmith"s default for `source`
  59. .destination(dest)
  60. .build((err, files) => {
  61. done(err)
  62. if (typeof opts.complete === "function") {
  63. //配置對象有complete函數則執行
  64. const helpers = { chalk, logger, files }
  65. opts.complete(data, helpers)
  66. } else {
  67. //配置對象有completeMessage,執行logMessage函數
  68. logMessage(opts.completeMessage, data)
  69. }
  70. })
  71. return data
  72. }
  73. /**
  74. * Create a middleware for asking questions.
  75. *
  76. * @param {Object} prompts
  77. * @return {Function}
  78. */
  79. function askQuestions (prompts) {
  80. return (files, metalsmith, done) => {
  81. ask(prompts, metalsmith.metadata(), done)
  82. }
  83. }
  84. /**
  85. * Create a middleware for filtering files.
  86. *
  87. * @param {Object} filters
  88. * @return {Function}
  89. */
  90. function filterFiles (filters) {
  91. return (files, metalsmith, done) => {
  92. filter(files, filters, metalsmith.metadata(), done)
  93. }
  94. }
  95. /**
  96. * Template in place plugin.
  97. *
  98. * @param {Object} files
  99. * @param {Metalsmith} metalsmith
  100. * @param {Function} done
  101. */
  102. function renderTemplateFiles (skipInterpolation) {
  103. skipInterpolation = typeof skipInterpolation === "string"
  104. ? [skipInterpolation]
  105. : skipInterpolation //保證skipInterpolation是一個數組
  106. return (files, metalsmith, done) => {
  107. const keys = Object.keys(files) //獲取files的所有key
  108. const metalsmithMetadata = metalsmith.metadata() //獲取metalsmith的所有變量
  109. async.each(keys, (file, next) => { //異步處理所有files
  110. // skipping files with skipInterpolation option
  111. // 跳過符合skipInterpolation的要求的file
  112. if (skipInterpolation && multimatch([file], skipInterpolation, { dot: true }).length) {
  113. return next()
  114. }
  115. //獲取文件的文本內容
  116. const str = files[file].contents.toString()
  117. // do not attempt to render files that do not have mustaches
  118. //跳過不符合handlebars語法的file
  119. if (!/{{([^{}]+)}}/g.test(str)) {
  120. return next()
  121. }
  122. //渲染文件
  123. render(str, metalsmithMetadata, (err, res) => {
  124. if (err) {
  125. err.message = `[${file}] ${err.message}`
  126. return next(err)
  127. }
  128. files[file].contents = new Buffer(res)
  129. next()
  130. })
  131. }, done)
  132. }
  133. }
  134. /**
  135. * Display template complete message.
  136. *
  137. * @param {String} message
  138. * @param {Object} data
  139. */
  140. function logMessage (message, data) {
  141. if (!message) return //沒有message直接退出函數
  142. render(message, data, (err, res) => {
  143. if (err) {
  144. console.error("
  145. Error when rendering template complete message: " + err.message.trim()) //渲染錯誤打印錯誤信息
  146. } else {
  147. console.log("
  148. " + res.split(/
  149. ?
  150. /g).map(line => " " + line).join("
  151. "))
  152. //渲染成功打印最終渲染的結果
  153. }
  154. })
  155. }

引入的包:

chalk //用于高亮終端打印出來的信息。

metalsmith //靜態網站生成器。

handlebars //知名的模板引擎。

async //非常強大的異步處理工具。

consolidate //支持各種模板引擎的渲染。

path //node自帶path模塊,用于路徑的處理。

multimatch // 可以支持多個條件的匹配。

options //自定義工具-用于獲取模板配置。

ask //自定義工具-用于詢問開發者。

filter //自定義工具-用于文件過濾。

logger //自定義工具-用于日志打印。

主邏輯:

獲取模板配置 -->初始化Metalsmith -->添加一些變量至Metalsmith -->handlebars模板注冊helper -->配置對象中是否有before函數,有則執行 -->詢問問題 -->過濾文件 -->渲染模板文件 -->配置對象中是否有after函數,有則執行 -->最后構建項目內容 -->構建完成,成功若配置對象中有complete函數則執行,否則打印配置對象中的completeMessage信息,如果有錯誤,執行回調函數done(err)

其他函數:

askQuestions: 詢問問題

filterFiles: 過濾文件

renderTemplateFiles: 渲染模板文件

logMessage: 用于構建成功時,打印信息

Metalsmith插件格式:

</>復制代碼

  1. function {
  2. return (files,metalsmith,done)=>{
  3. //邏輯代碼
  4. ...
  5. }
  6. }
options.js

</>復制代碼

  1. const path = require("path")
  2. const metadata = require("read-metadata")
  3. const exists = require("fs").existsSync
  4. const getGitUser = require("./git-user")
  5. const validateName = require("validate-npm-package-name")
  6. /**
  7. * Read prompts metadata.
  8. *
  9. * @param {String} dir
  10. * @return {Object}
  11. */
  12. module.exports = function options (name, dir) {
  13. const opts = getMetadata(dir)
  14. setDefault(opts, "name", name)
  15. setValidateName(opts)
  16. const author = getGitUser()
  17. if (author) {
  18. setDefault(opts, "author", author)
  19. }
  20. return opts
  21. }
  22. /**
  23. * Gets the metadata from either a meta.json or meta.js file.
  24. *
  25. * @param {String} dir
  26. * @return {Object}
  27. */
  28. function getMetadata (dir) {
  29. const json = path.join(dir, "meta.json")
  30. const js = path.join(dir, "meta.js")
  31. let opts = {}
  32. if (exists(json)) {
  33. opts = metadata.sync(json)
  34. } else if (exists(js)) {
  35. const req = require(path.resolve(js))
  36. if (req !== Object(req)) {
  37. throw new Error("meta.js needs to expose an object")
  38. }
  39. opts = req
  40. }
  41. return opts
  42. }
  43. /**
  44. * Set the default value for a prompt question
  45. *
  46. * @param {Object} opts
  47. * @param {String} key
  48. * @param {String} val
  49. */
  50. function setDefault (opts, key, val) {
  51. if (opts.schema) {
  52. opts.prompts = opts.schema
  53. delete opts.schema
  54. }
  55. const prompts = opts.prompts || (opts.prompts = {})
  56. if (!prompts[key] || typeof prompts[key] !== "object") {
  57. prompts[key] = {
  58. "type": "string",
  59. "default": val
  60. }
  61. } else {
  62. prompts[key]["default"] = val
  63. }
  64. }
  65. function setValidateName (opts) {
  66. const name = opts.prompts.name
  67. const customValidate = name.validate
  68. name.validate = name => {
  69. const its = validateName(name)
  70. if (!its.validForNewPackages) {
  71. const errors = (its.errors || []).concat(its.warnings || [])
  72. return "Sorry, " + errors.join(" and ") + "."
  73. }
  74. if (typeof customValidate === "function") return customValidate(name)
  75. return true
  76. }
  77. }

引入的包:

path //node自帶path模塊,用于路徑的處理

read-metadata //用于讀取json或者yaml元數據文件并返回一個對象

fs.existsSync //node自帶fs模塊的existsSync方法,用于檢測路徑是否存在

git-user //獲取本地的git配置

validate-npm-package-name //用于npm包的名字是否是合法的

作用:

主方法: 第一步:先獲取模板的配置文件信息;第二步:設置name字段并檢測name是否合法;第三步:只是author字段。

getMetadata: 獲取meta.js或則meta.json中的配置信息

setDefault: 用于向配置對象中添加一下默認字段

setValidateName: 用于檢測配置對象中name字段是否合法

git-user.js

</>復制代碼

  1. const exec = require("child_process").execSync
  2. module.exports = () => {
  3. let name
  4. let email
  5. try {
  6. name = exec("git config --get user.name")
  7. email = exec("git config --get user.email")
  8. } catch (e) {}
  9. name = name && JSON.stringify(name.toString().trim()).slice(1, -1)
  10. email = email && (" <" + email.toString().trim() + ">")
  11. return (name || "") + (email || "")
  12. }

引入的包:

child_process.execSync //node自帶模塊child_process中的execSync方法用于新開一個shell并執行相應的command,并返回相應的輸出。

作用: 用于獲取本地的git配置的用戶名和郵件,并返回格式 姓名<郵箱> 的字符串。

eval.js

</>復制代碼

  1. const chalk = require("chalk")
  2. /**
  3. * Evaluate an expression in meta.json in the context of
  4. * prompt answers data.
  5. */
  6. module.exports = function evaluate (exp, data) {
  7. /* eslint-disable no-new-func */
  8. const fn = new Function("data", "with (data) { return " + exp + "}")
  9. try {
  10. return fn(data)
  11. } catch (e) {
  12. console.error(chalk.red("Error when evaluating filter condition: " + exp))
  13. }
  14. }

引入的包:

chalk //用于高亮終端打印出來的信息。

作用: 在data的作用域執行exp表達式并返回其執行得到的值

ask.js

</>復制代碼

  1. const async = require("async")
  2. const inquirer = require("inquirer")
  3. const evaluate = require("./eval")
  4. // Support types from prompt-for which was used before
  5. const promptMapping = {
  6. string: "input",
  7. boolean: "confirm"
  8. }
  9. /**
  10. * Ask questions, return results.
  11. *
  12. * @param {Object} prompts
  13. * @param {Object} data
  14. * @param {Function} done
  15. */
  16. /**
  17. * prompts meta.js或者meta.json中的prompts字段
  18. * data metalsmith.metadata()
  19. * done 交于下一個metalsmith插件處理
  20. */
  21. module.exports = function ask (prompts, data, done) {
  22. //遍歷處理prompts下的每一個字段
  23. async.eachSeries(Object.keys(prompts), (key, next) => {
  24. prompt(data, key, prompts[key], next)
  25. }, done)
  26. }
  27. /**
  28. * Inquirer prompt wrapper.
  29. *
  30. * @param {Object} data
  31. * @param {String} key
  32. * @param {Object} prompt
  33. * @param {Function} done
  34. */
  35. function prompt (data, key, prompt, done) {
  36. // skip prompts whose when condition is not met
  37. if (prompt.when && !evaluate(prompt.when, data)) {
  38. return done()
  39. }
  40. //獲取默認值
  41. let promptDefault = prompt.default
  42. if (typeof prompt.default === "function") {
  43. promptDefault = function () {
  44. return prompt.default.bind(this)(data)
  45. }
  46. }
  47. //設置問題,具體使用方法可去https://github.com/SBoudrias/Inquirer.js上面查看
  48. inquirer.prompt([{
  49. type: promptMapping[prompt.type] || prompt.type,
  50. name: key,
  51. message: prompt.message || prompt.label || key,
  52. default: promptDefault,
  53. choices: prompt.choices || [],
  54. validate: prompt.validate || (() => true)
  55. }]).then(answers => {
  56. if (Array.isArray(answers[key])) {
  57. //當答案是一個數組時
  58. data[key] = {}
  59. answers[key].forEach(multiChoiceAnswer => {
  60. data[key][multiChoiceAnswer] = true
  61. })
  62. } else if (typeof answers[key] === "string") {
  63. //當答案是一個字符串時
  64. data[key] = answers[key].replace(/"/g, """)
  65. } else {
  66. //其他情況
  67. data[key] = answers[key]
  68. }
  69. done()
  70. }).catch(done)
  71. }

引入的包:

async //異步處理工具。

inquirer //命令行與用戶之間的交互

eval //返回某作用下表達式的值

作用: 將meta.js或者meta.json中的prompts字段解析成對應的問題詢問。

filter.js

</>復制代碼

  1. const match = require("minimatch")
  2. const evaluate = require("./eval")
  3. /**
  4. * files 模板內的所有文件
  5. * filters meta.js或者meta.jsonfilters字段
  6. * data metalsmith.metadata()
  7. * done 交于下一個metalsmith插件處理
  8. */
  9. module.exports = (files, filters, data, done) => {
  10. if (!filters) {
  11. //meta.js或者meta.json沒有filters字段直接跳過交于下一個metalsmith插件處理
  12. return done()
  13. }
  14. //獲取所有文件的名字
  15. const fileNames = Object.keys(files)
  16. //遍歷meta.js或者meta.json沒有filters下的所有字段
  17. Object.keys(filters).forEach(glob => {
  18. //遍歷所有文件名
  19. fileNames.forEach(file => {
  20. //如果有文件名跟filters下的某一個字段匹配上
  21. if (match(file, glob, { dot: true })) {
  22. const condition = filters[glob]
  23. if (!evaluate(condition, data)) {
  24. //如果metalsmith.metadata()下condition表達式不成立,刪除該文件
  25. delete files[file]
  26. }
  27. }
  28. })
  29. })
  30. done()
  31. }

引入的包:

minimatch //字符匹配工具

eval //返回某作用下表達式的值

作用: 根據metalsmith.metadata()刪除一些不需要的模板文件,而metalsmith.metadata()主要在ask.js中改變的,也就是說ask.js中獲取到用戶的需求。

logger.js

</>復制代碼

  1. const chalk = require("chalk")
  2. const format = require("util").format
  3. /**
  4. * Prefix.
  5. */
  6. const prefix = " vue-cli"
  7. const sep = chalk.gray("·")
  8. /**
  9. * Log a `message` to the console.
  10. *
  11. * @param {String} message
  12. */
  13. exports.log = function (...args) {
  14. const msg = format.apply(format, args)
  15. console.log(chalk.white(prefix), sep, msg)
  16. }
  17. /**
  18. * Log an error `message` to the console and exit.
  19. *
  20. * @param {String} message
  21. */
  22. exports.fatal = function (...args) {
  23. if (args[0] instanceof Error) args[0] = args[0].message.trim()
  24. const msg = format.apply(format, args)
  25. console.error(chalk.red(prefix), sep, msg)
  26. process.exit(1)
  27. }
  28. /**
  29. * Log a success `message` to the console and exit.
  30. *
  31. * @param {String} message
  32. */
  33. exports.success = function (...args) {
  34. const msg = format.apply(format, args)
  35. console.log(chalk.white(prefix), sep, msg)
  36. }

引入的包:

chalk //用于高亮終端打印出來的信息。

format //node自帶的util模塊中的format方法。

作用: logger.js主要提供三個方法log(常規日志)、fatal(錯誤日志)、success(成功日志)。每個方法都挺簡單的,我就不錯過多的解釋了。

local-path.js

</>復制代碼

  1. const path = require("path")
  2. module.exports = {
  3. isLocalPath (templatePath) {
  4. return /^[./]|(^[a-zA-Z]:)/.test(templatePath)
  5. },
  6. getTemplatePath (templatePath) {
  7. return path.isAbsolute(templatePath)
  8. ? templatePath
  9. : path.normalize(path.join(process.cwd(), templatePath))
  10. }
  11. }

引入的包:

path //node自帶的路徑處理工具。

作用:

isLocalPath: UNIX (以“.”或者"/"開頭) WINDOWS(以形如:“C:”的方式開頭)。

getTemplatePath: templatePath是否為絕對路徑,是則返回templatePath 否則轉換成絕對路徑并規范化。

check-version.js

</>復制代碼

  1. const request = require("request")
  2. const semver = require("semver")
  3. const chalk = require("chalk")
  4. const packageConfig = require("../package.json")
  5. module.exports = done => {
  6. // Ensure minimum supported node version is used
  7. if (!semver.satisfies(process.version, packageConfig.engines.node)) {
  8. return console.log(chalk.red(
  9. " You must upgrade node to >=" + packageConfig.engines.node + ".x to use vue-cli"
  10. ))
  11. }
  12. request({
  13. url: "https://registry.npmjs.org/vue-cli",
  14. timeout: 1000
  15. }, (err, res, body) => {
  16. if (!err && res.statusCode === 200) {
  17. const latestVersion = JSON.parse(body)["dist-tags"].latest
  18. const localVersion = packageConfig.version
  19. if (semver.lt(localVersion, latestVersion)) {
  20. console.log(chalk.yellow(" A newer version of vue-cli is available."))
  21. console.log()
  22. console.log(" latest: " + chalk.green(latestVersion))
  23. console.log(" installed: " + chalk.red(localVersion))
  24. console.log()
  25. }
  26. }
  27. done()
  28. })
  29. }

引入的包:

request //http請求工具。

semver //版本號處理工具。

chalk //用于高亮終端打印出來的信息。

作用:

第一步:檢查本地的node版本號,是否達到package.json文件中對node版本的要求,若低于nodepackage.json文件中要求的版本,則直接要求開發者更新自己的node版本。反之,可開始第二步。

第二步: 通過請求https://registry.npmjs.org/vu...來獲取vue-cli的最新版本號,跟package.json中的version字段進行比較,若本地的版本號小于最新的版本號,則提示有最新版本可以更新。這里需要注意的是,這里檢查版本號并不影響后續的流程,即便本地的vue-cli版本不是最新的,也不影響構建,僅僅提示一下。

warnings.js

</>復制代碼

  1. const chalk = require("chalk")
  2. module.exports = {
  3. v2SuffixTemplatesDeprecated (template, name) {
  4. const initCommand = "vue init " + template.replace("-2.0", "") + " " + name
  5. console.log(chalk.red(" This template is deprecated, as the original template now uses Vue 2.0 by default."))
  6. console.log()
  7. console.log(chalk.yellow(" Please use this command instead: ") + chalk.green(initCommand))
  8. console.log()
  9. },
  10. v2BranchIsNowDefault (template, name) {
  11. const vue1InitCommand = "vue init " + template + "#1.0" + " " + name
  12. console.log(chalk.green(" This will install Vue 2.x version of the template."))
  13. console.log()
  14. console.log(chalk.yellow(" For Vue 1.x use: ") + chalk.green(vue1InitCommand))
  15. console.log()
  16. }
  17. }

引入的包:

chalk //用于高亮終端打印出來的信息。

作用:

v2SuffixTemplatesDeprecated:提示帶“-2.0”的模板已經棄用了,官方模板默認用2.0了。不需要用“-2.0”來區分vue1.0和vue2.0了。

v2BranchIsNowDefault: 這個方法在vue-init文件中已經被注釋掉,不再使用了。在vue1.0向vue2.0過渡的時候用到過,現在都是默認2.0了,自然也就不用了。

總結

由于代碼比較多,很多代碼我就沒有一一細講了,一些比較簡單或者不是很重要的js文件,我就單單說明了它的作用了。但是重點的js文件,我還是加了很多注解在上面。其中我個人認為比較重點的文件就是vue-initgenerate.jsoptions.jsask.jsfilter.js,這五個文件構成了vue-cli構建項目的主流程,因此需要我們花更多的時間在上面。另外,我們在讀源碼的過程中,一定要理清楚整個構建流程是什么樣子的,心里得有一個譜。讀完源碼之后,我個人是建議自己動手搭建一個構建工具,這樣的話印象才會更加深刻,個人成長會更大點。我自己在讀完整個vue-cli之后,我自己根據vue-cli的流程也動手搞了一個腳手架工具,僅供大家參考學習一下。地址如下:

</>復制代碼

  1. https://github.com/ruichengpi...

最后祝愿大家可以在前端的道路上越走越好!如果喜歡我的文章,請記得關注我哦!后續會推出更多的優質的文章哦,敬請期待!

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/93689.html

相關文章

  • 前端開發之走進Vue.js

    摘要:作為目前最熱門最具前景的前端框架之一,其提供了一種幫助我們快速構建并開發前端項目的新的思維模式。的新版本,的簡稱。的包管理工具,用于同一管理我們前端項目中需要用到的包插件工具命令等,便于開發和維護。 Vue.js作為目前最熱門最具前景的前端框架之一,其提供了一種幫助我們快速構建并開發前端項目的新的思維模式。本文旨在幫助大家認識Vue.js,了解Vue.js的開發流程,并進一步理解如何通...

    zxhaaa 評論0 收藏0
  • almost最好的Vue + Typescript系列01 環境搭建

    摘要:初來乍到,請多多關照也許這是第一套基于搭建基于的項目教程。年月初升級到了新的版本版,跟版本項目結構發生了很大的改變,目錄結構更簡潔更科學。并且可以選配,在此之前配置略麻煩,是的超級,靜態類型,便捷的注解,使前端代碼優雅。 前言 hello~ 大家好。 初來乍到,請多多關照 ?(??????)??也許這是第一套基于 vue-cli 3.x 搭建基于 typescript 的vue項目教程...

    isLishude 評論0 收藏0
  • vue項目搭建以及全家桶的使用詳細教程

    摘要:前言是現階段很流行的前端框架,很多人通過官方文檔的學習,對的使用都有了一定的了解,但再在項目工程化處理的時候,卻發現不知道改怎么更好的管理自己的項目,如何去引入一些框架以及全家桶其他框架的使用,以下將詳細地介紹本人在處理工程文件構建的過程對 前言 vue是現階段很流行的前端框架,很多人通過vue官方文檔的學習,對vue的使用都有了一定的了解,但再在項目工程化處理的時候,卻發現不知道改怎...

    simon_chen 評論0 收藏0
  • vue項目搭建以及全家桶的使用詳細教程

    摘要:前言是現階段很流行的前端框架,很多人通過官方文檔的學習,對的使用都有了一定的了解,但再在項目工程化處理的時候,卻發現不知道改怎么更好的管理自己的項目,如何去引入一些框架以及全家桶其他框架的使用,以下將詳細地介紹本人在處理工程文件構建的過程對 前言 vue是現階段很流行的前端框架,很多人通過vue官方文檔的學習,對vue的使用都有了一定的了解,但再在項目工程化處理的時候,卻發現不知道改怎...

    curlyCheng 評論0 收藏0

發表評論

0條評論

最新活動
閱讀需要支付1元查看
<