繼 React,Vue,這是第三個著重閱讀源碼的前端項目-Webpack。本文主要以:
誠然 Webpack 這是一個前端工程化工具,理解容易, 使用簡單,似乎沒有深入研究的必要。那為什麼還要費心費力閱讀其源碼?這,把正在寫此篇文章的我也問住了。理提綱時,認為 WHY 最好寫,幾句話就可帶過,但事實證明真要較真這一塊還值得一說。
擅自揣測下會閱讀 Webpack 源碼夥伴可能的動機:作者最先是原因是 4,然後是 1,2。當然,1,2 應該是大多數人看項目源碼的動機。
搭建源碼調試環境要閱讀源碼,首先拿到源碼,然後最後能邊調試邊閱讀。當然,如果智力和推理能力驚人,大可以直接在 Github 上在線閱讀。
有 2 種方法下載源碼。一種是最常見的 git clone,將 Github 上 webpack 項目 clone 到本地,pull 後與 webpack 官方最新代碼保持一致,一勞永逸。不過作者嘗試第一種方法時,總是 clone 不下來,很大可能是由於 webpack 源文件過大且 github 伺服器 clone 一直很慢。
於是退而求其次,使用第二種方法:下載 Webpack 源碼 release 版本。選擇一個打算閱讀的 webpack 源碼版本,直接下載"Source code(zip)"即可。速度非常快,因為不包含 .git。IDE 作者使用 VSCode,調試 node 很方便。拿到源碼後,在目錄新建一個文件夾,寫一個簡單的 webpack 案例,然後使用 VSCode 進行調試。
不過,在實際操作中,直接使用下載源碼中的 webpack.js 調試可能會出現報錯 Cannot find module 'json-parse-better-errors' 或 Cannot find module 'webpack/lib/RequestShortener',只需運行 npm install webpack webpack-cli --save-dev,即可解決報錯,且不影響調試源碼。此附作者在調試時
使用版本參考,下載後使用 VSCode 打開 webpack-4.41.4(modified),安裝依賴,安裝 webpack 和 webpack-cli,按 F5 即可啟動調試。https://github.com/Terry-Su/learn-webpack/archive/0.0.1.zipWebpack 源碼量龐大,把每一行代碼都讀懂確實沒有必要,但是我們至少要知道它的整體運行流程,知道它反覆用到的核心代碼,以及各個模塊的生命周期如何運轉。
找核心功能源碼代碼量大,想要在走整體流程時恰好找核心功能的源碼,困難重重,至少對於 webpack 源碼是這樣,因為其獨特的插件和回調結構。
不過,我們可以根據每一個想要了解的核心功能,單獨去尋找和閱讀相關源碼。比如,如果我們想看 webpack 如何打包生成 bundle.js,可通過 webpack 一定會調用 NodeJS 文件系統輸出文件方法,全局搜索"writeFile"找到相關代碼,或通過 bundle.js 中的關鍵字"// Check if module is in cache"進行搜索。
通過邊調試邊閱讀代碼,了解代碼整體走向以及webpack如何打包生成 bundle.js,作者學到了以下內容:
TapableTapable 在源碼中應用隨處可見,要了解源碼,首先得學習 tapable 機制。其實它並不複雜,並且我們只需要知道它的基本作用和用法即可。
Tapable 可理解為一套鉤子回調函數機制,每一個鉤子可訂閱多個函數,發布鉤子時會運行該鉤子訂閱該的多個函數。
const { SyncHook } = require('tapable')
class Car {
constructor() {
this.hooks = {
// # 添加一個鉤子
start: new SyncHook()
}
}
}
const car = new Car()
// start鉤子訂閱一個函數
car.hooks.start.tap( 'run slowly', () => console.log('start running slowly') )
// start鉤子訂閱另一個函數
car.hooks.start.tap( 'run mediumly', () => console.log('start running mediumly') )
// 發布鉤子
car.hooks.start.call() // 輸出: run slowly run mediumly
簡化版 Webpack 運行流程bundle.js 內容如何生成/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
....
其實 Webpack 對於內容分兩步處理,第一步先通過 loader(默認為 babel-loader)生成組合 JS 代碼。第二步將組合 JS 代碼放入 webpack 默認函數中,從而避免變量洩露。
export const foo = () => 'hello foo!'
import { foo } from './foo.js'
foo()
console.log( 'hello bar!' )
打包第一步,通過 loader(默認為 babel-loader)生成組合 JS 代碼:
...
const foo = () => 'hello foo!'
...
\r\n__WEBPACK_MODULE_REFERENCE__0_666f6f_call__()\r\nconsole.log( 'hello bar!' )
...
打包第二步,組合 JS 代碼放入 webpack 默認函數中。
/******/ (function(modules) { // webpackBootstrap\n
...
const foo = () => 'hello foo!'
...
foo()
console.log( 'hello bar!' )
...
值得注意的是,核心文件為 ConcatenatedModule.js, 通過遍歷 modulesWithInfo 從而生成打包代碼。不管在 webpack 源碼,還是 Vue 源碼和其他地方,runtime 經常出現。runtime 究竟是什麼?
經過反覆查閱資料和推敲,runtime 代碼可以理解為編譯後生成的代碼。比如,對於 React,runtime 代碼就是編譯 JSX 代碼後生成的 JS 代碼。對於 Vue,runtime 代碼則是編譯 template,script,style 後生成的 JS 代碼。
2. 熱更新,Code Splitting, Tree-shaking 等是如何實現?Webpack 內容較多,核心模塊原理也不少,比如 loader 如何運轉,Code Splitting 如何實現,Tree-Shaking 和熱加載又是怎麼做到的。但畢竟時間有限,此次閱讀源碼的目標不是大而全的弄懂所有內容,而是掌握 Webpack 的主要運轉流程以及了解較為感興趣的幾個模塊。所以其他模塊原理以後有機再加入此文。對相應模塊模塊感興趣的夥伴可網上先自行搜索相關內容。
閱讀源碼資源推薦https://raw.githubusercontent.com/sokra/slides/master/data/how-webpack-works.pdfhttps://webpack.wuhaolin.cn/https://github.com/TheLarkInn/artsy-webpack-tourhttps://www.youtube.com/watch?v=Gc9-7PBqOC8點擊左下角閱讀原文,歡迎到
SegmentFault 思否社區 和文章作者展開更多互動和交流。