[email protected]
[email protected]
閱讀源碼小技巧:摺疊所有代碼,先不看所有的變量聲明和 require,要是之後用到再看,直接看主要邏輯。
第一個問題
- webpack-cli 是如何調用 webpack 的
- 在 demo 目錄運行 webpack-cli,會自動把 src/index.js 打包爲 dist/main.js
- 顯然會調用 webpack 來打包,那麼請問是如何做到的
- 首先當我們運行 webpack-cli 命令的時候,會去執行 bin/cli.js 。
- 摺疊文件所有代碼,跳過 require 和所有聲明,第一個 if 不看,之後的 if else 就需要打開來看了,因爲這段話必定要執行,我們先假設 webpack 存在,進入 runCli 這個函數。
- 摺疊所有代碼,直接看主分支 try catch 裏面,主要調用了 cli.run 這個方法。
- 進入 cli.run,第一個方法
this.runOptionGroups(args)
從名字可以看出和配置項相關,所以跳過,最後發現執行了this.createCompiler
這個函數。 - 進入 createCompiler 並摺疊函數,我們發現他其實就是調用了 webpack 這個函數。
- 看完源碼之後明白
- compiler = webpack(options, callback)
- webpack = require('webpack')
- webpack-cli 就是這麼調用 webpack 的
第二個問題
- webpack 是如何分析 index.js 的
- 通過之前自己做的簡易打包器,打包器需要先分析並收集依賴,然後打包成一個文件
- 那麼 webpack 肯定也做了這件事
- 顯然 webpack 也需要分析 AST,不可能用正則來做
- 直接看 webpack 的 package.json 的 main 屬性,可以得知入口是 lib/index.js。
- 進入 index.js,主要是導出了一個函數,這個函數又是從 './webpack' 裏面的。
- 進入 './webpack',也是主要導出了一個webpack 這個函數,第一個函數
validateSchema
是驗證函數(不看),然後是 一個 if else,一般我們用 webpck 的時候,options 一般都不會是一個數組,所以我們看 else,執行了 一個 createCompiler 函數。 - 懵逼了,憑我目前的認知,看不懂這套邏輯了,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 創建文件,並寫到硬盤上