閱讀 webpack 源碼 第一個問題 第二個問題 新的第二個問題 流程圖待補充 第三個問題 第四個問題 第五個問題 第六個問題 第七個問題 第八個問題 第九個問題 第十個問題

[email protected]
[email protected]
閱讀源碼小技巧:摺疊所有代碼,先不看所有的變量聲明和 require,要是之後用到再看,直接看主要邏輯。

第一個問題

  • webpack-cli 是如何調用 webpack 的
  • 在 demo 目錄運行 webpack-cli,會自動把 src/index.js 打包爲 dist/main.js
  • 顯然會調用 webpack 來打包,那麼請問是如何做到的
  1. 首先當我們運行 webpack-cli 命令的時候,會去執行 bin/cli.js 。
  2. 摺疊文件所有代碼,跳過 require 和所有聲明,第一個 if 不看,之後的 if else 就需要打開來看了,因爲這段話必定要執行,我們先假設 webpack 存在,進入 runCli 這個函數。
  3. 摺疊所有代碼,直接看主分支 try catch 裏面,主要調用了 cli.run 這個方法。
  4. 進入 cli.run,第一個方法 this.runOptionGroups(args) 從名字可以看出和配置項相關,所以跳過,最後發現執行了 this.createCompiler 這個函數。
  5. 進入 createCompiler 並摺疊函數,我們發現他其實就是調用了 webpack 這個函數。
  • 看完源碼之後明白
  • compiler = webpack(options, callback)
  • webpack = require('webpack')
  • webpack-cli 就是這麼調用 webpack 的

第二個問題

  • webpack 是如何分析 index.js 的
  • 通過之前自己做的簡易打包器,打包器需要先分析並收集依賴,然後打包成一個文件
  • 那麼 webpack 肯定也做了這件事
  • 顯然 webpack 也需要分析 AST,不可能用正則來做
  1. 直接看 webpack 的 package.json 的 main 屬性,可以得知入口是 lib/index.js。
  2. 進入 index.js,主要是導出了一個函數,這個函數又是從 './webpack' 裏面的。
  3. 進入 './webpack',也是主要導出了一個webpack 這個函數,第一個函數 validateSchema 是驗證函數(不看),然後是 一個 if else,一般我們用 webpck 的時候,options 一般都不會是一個數組,所以我們看 else,執行了 一個 createCompiler 函數。
  4. 懵逼了,憑我目前的認知,看不懂這套邏輯了,createCompiler 就是 return 了一個 new Compiler,compiler.hooks.xxx.call 幹了什麼,猜測主要邏輯應該在這個裏面(排除法)。
  • 看了源碼之後發現
  • 看了半天,就發現創建了一個compiler 對象,然後什麼都沒做
  • hooks.xxx.call 是什麼鬼?
  • 看源碼不是好的學習方式,性價比低
  • 第一次勸退

hooks.xxx.call 是什麼

  • Tapable
  • 這是 webpack 團隊爲了寫 wepack 而寫的一個事件/鉤子庫(也就是發佈訂閱)
  • 用法
  • 定義一個事件/鉤子
  • this.hooks.eventName = new SyncHook(['arg1', 'arg2']);
  • 監聽一個事件/鉤子
  • this.hooks.eventName.tap('監聽理由', fn)
  • 觸發一個事件/鉤子
  • this.hooks.eventName.call('arg1', 'arg2')

教訓:看源碼時,遇到不懂的,要快速學會

新的第二個問題

  • webpack 的流程是怎麼樣的
  • webpack 把打包分爲了哪幾個階段(事件或鉤子)
  • 看完代碼發現
  • 至少有 env init run beforeCompile compile compilation make finishMake afterCompile emit 這幾個鉤子

流程圖待補充

第三個問題

  • 讀取 index.js 並分析和收集依賴是在哪個階段?
  • 用排除法可以知道,肯定不是 env 和 emit,肯定在 beforeCompile 和 afterCompile 之間
  • 最有可能是在 make - finishMake 階段(爲什麼?)
  • 學過 C 語言就會知道,make 是編譯時必然會用到的工具,可見很重要
  • 驗證想法
  • 我們發現 make - finishMake 之間什麼代碼都沒有

第四個問題

  • make - finishMake 之間,做了什麼
  • 搜索 make.tap,發現很多監聽了 make 事件
  • 還是根據排除法,應該是 EntryPlugin(由此發現 webpack 爲什麼什麼都不做呢,因爲 webpack 只是把流程安排好,其他事情都交給插件去做,webpack 架構就是事件模型,你要做什麼事情,你自己去找相對應的鉤子去插入就好了
  • EntryPlugin 的 addEntry 函數就是 make 階段最重要的事情之一
  • 死衚衕
  • 跟代碼跟到 factorizeQueue 就發現沒有後續代碼了,怎麼辦?
  • 第三次勸退
  • 需要補充任務隊列知識,任務隊列發現有任務會自動執行

第五個問題

  • factor.create 是什麼東西
  • 這個 factor 是哪裏來的?
  • 是從 factorizeModule(options 的 options.factory 來的)
  • 這個 options.factory 是哪裏來的?
  • 是從 moduleFactory 來的
  • moduleFactory 哪裏來的?
  • 是用 this.dependencyFactories.get(Dep) 得到的
  • this.dependencyFactories.get(Dep) 是個啥?
  • 你搜 compilation.tap 就知道,他是 normalModuleFactory,簡稱 nmf
  • 結論:factor 就是 nmf,所以 factory.create 就是 nmf.create

第六個問題

  • nmf.create 做了什麼
  • 來到 NormalModuleFactory.js,可以看到 create 的代碼
  • 只發現一具有用的代碼:beforeResolve.call 和 factorize.call
  • 搜索兩者對應的 tap,發現 factorize.tap 裏面有重要代碼
  • 他觸發了 resolve,而 resolve 主要是在收集 loaders
  • 然後他觸發了 createModule,得到了 createdModule
  • 也就是說,nmf.create 得到了一個 module 對象
  • 等價於 factor.create 得到了一個 module 對象
  • 回想一下,我們怎麼找到 factory.create 的?
  • 你可以使用 back 功能回來之前的停頓點
  • 我們是從 factorizeModule 來到 factory.create 的
  • 回來 factorizeModule,發現後續操作是 addModule 和 buildModule

第七個問題

  • addModule 做了什麼
  • 把module 添加到 compilation.modules 裏
  • 而且還通過檢查 id 防止重複添加

第八個問題

  • buildModule 做了什麼
  • 看名字就知道是重要操作,它調用了 module.build()
  • 來到 NormalModule.js(猜的,跟 nmf 差不多) 看 build 源碼,發現了 runLoaders
  • 然後來到 processResult(),發現了 _source = ... 和 _ast = null
  • 這是要做什麼?顯然是要把 _source 變成 _ast 了!
  • 這就是我們第二個問題(webpack 如何分析 index.js) 的答案!
  • 來到 doBuild 的回調,發現了 this.parser.parse()!
  • 終於,着整個過程就是最開始我們的簡易打包器的過程(AST、Babel、依賴)
  • parse 就是把 code 變成 ast
  • 問題來了,parser 是什麼,parse() 的源碼在哪兒?
  • 繼續跟代碼會發現 parser (javascriptParser.js)來自於acorn 庫,需要編譯原理知識(涉及到盲區了)

第九個問題

  • webpack 如何知道 index.js 依賴了那些文件的
  • 目前我們知道 webpack 會對 index.js 進行 parse 得到 ast
  • 那麼接下來 webpack 應該會 traverse 這個 ast,尋找 import 語句
  • 那麼相關代碼在哪兒?
  • 閱讀源碼發現
  • JavascriptParser.js 的 3231 行得到 ast,3260~3264 行 traverse 了 ast
  • 其中 blockPreWalkStatement() 對 ImportDeclaration 進行了檢查
  • 一旦發現 import 'xxx',就會觸發 import 鉤子,對應的監聽函數會處理依賴
  • 其中 walkStatements() 對 importExpression 進行了檢查
  • 一旦發現 import('xxx'),就會觸發 importCall 鉤子,對應的監聽函數也會

第十個問題

  • 怎麼把 Modules 合併成一個文件的?
  • 看 compilation.seal()(猜的,只剩這個階段了),該函數會創建 chunks、 爲每個 chunk 進行 codeGeneration,然後爲每個 chunk 創建 asset(搜索 write,發現writeOut)
  • seal() 之後,emitAssets()、emitFiles() 會創建文件
  • 最終得到 dist/main.js 和其他 chunk 文件

小結

  • webpack 怎麼分析依賴和打包的
  • 使用 JavascriptParser 對 index.js 進行 parse 得到 ast,然後遍歷 ast
  • 發現依賴聲明就將其添加到 module 的 dependencies 或 blocks 中
  • seal 階段,webpack 將 module 轉爲 chunk,可能會把多個 module 通過 codeGeneration 合併爲一個 chunk
  • seal 之後,爲每個 chunk 創建文件,並寫到硬盤上
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章