webpack-dev-middleware解讀
簡單介紹
webpack-dev-middleware,作用就是,生成一個與webpack的compiler繫結的中介軟體,然後在express啟動的服務app中呼叫這個中介軟體。
這個中介軟體的作用呢,簡單總結為以下三點:通過watch mode,監聽資源的變更,然後自動打包(如何實現,見下文詳解);快速編譯,走記憶體;返回中介軟體,支援express的use格式。特別註明:webpack明明可以用watch mode,可以實現一樣的效果,但是為什麼還需要這個中介軟體呢?
答案就是,第二點所提到的,採用了記憶體方式。如果,只依賴webpack的watch mode來監聽檔案變更,自動打包,每次變更,都將新檔案打包到本地,就會很慢。-
實踐出真知
webpack-dev-middleware 使用配置很簡單,只需幾步,就可以。專案程式碼,參考原始碼;step1: 配置publicPath.
publicPath,熟悉webpack的同學都知道,這是生成的新檔案所指向的路徑,可以模擬CDN資源引用。那麼跟此處的主角webpack-dev-middleware什麼關係呢,關係就是,此處採用記憶體的方式,記憶體中採用的檔案儲存write path就是此處的publicPath,因此,這裡的配置publicPath需要使用相對路徑。
let path = require('path'); module.exports = { entry: './app.js', output: { publicPath: "/assets/", filename: 'bundle.js', //path: '/' //只使用 dev-middleware 可以忽略本屬性 }, };
step2: express server中引入中介軟體。
const path = require('path'); const express = require("express"); var ejs = require('ejs'); const app = express(); const webpack = require('webpack'); const webpackMiddleware = require("webpack-dev-middleware"); let webpackConf = require('./webpack.config.js'); app.engine('html', ejs.renderFile); app.set('views', path.join(__dirname, 'src/html')); app.set("view engine", "html"); var compiler = webpack(webpackConf); app.use(webpackMiddleware(compiler, { publicPath: webpackConf.output.publicPath, })); app.get("/", function(req, res) { res.render("index"); }); app.listen(3333);
通過step1以及step2,就能看到webpack的熱載入效果了,效果展示。
-
原始碼分析。
step1:首先看webpack-dev-middleware包,專案目錄結構為:
step2:逐一破解:
middleware.js分析:
line 6, var require("./lib/GetFilenameFromUrl");引入通過url得到fileName的方法;
line 11,方法入口,引入compiler以及option配置,可以看到這是常規的結構方法,引入option,然後定義預設值(line 13),處理預設邏輯(line 22).
line 22, 初始化的處理,我們進入shared.js檔案,深入分析一下。shared.js分析:
share結構:line 223,share.setOptions(context.options);,此時的options是我們配置中的
{ publicPath: webpackConf.output.publicPath, }
line 9~36,定義了setOptions方法,簡單一撇,重新定義了options的reporter方法,watchOptions.aggregateTimeout,options的stats(統計資訊物件),mimeTypes定義。(配置資訊不熟悉的可以參考官方github倉庫中的example
line 224, share.setFs(context.compiler);
可以看到,setFs方法做了兩件事,檢查compiler.outputPath是否為絕對路徑(預設為process.cwd()),如果為相對路徑,丟擲錯誤;定義compiler.outputFileSystem = new MemoryFileSystem();這就是webpack-dev-middleware的精髓所在了,使用記憶體檔案系統,而不是硬碟中的檔案,這樣能夠提升編譯的速度(稍後詳細分析這個玩意兒)。
line 226, context.compiler.plugin('done', share.compilerDone);
定義了一個done事件鉤子函式,該函式內主要是reporter編譯的資訊以及執行context.callbacks回撥函式。
line 227,228,229,原始碼:
context.compiler.plugin("invalid", share.compilerInvalid); context.compiler.plugin("watch-run", share.compilerInvalid); context.compiler.plugin("run", share.compilerInvalid);
定義了一個invalid事件(監控的編譯變無效後),watch-run(watch後開始編譯之前),run(讀取記錄之前)的回撥,都是share.compilerInvalid方法,該方法主要還是根據state狀態,report編譯的狀態資訊。
line 231,share.startWatch(),開始監控.可以看到主要邏輯在compiler.watch();納尼?繞了一圈還是呼叫了compiler的原型方法watch。瞅一瞅,webpack/lib/compiler.js檔案的line 216,
Compiler.prototype.watch = function(watchOptions, handler) { this.fileTimestamps = {}; this.contextTimestamps = {}; var watching = new Watching(this, watchOptions, handler); return watching; };
同理,看到 webpack/lib/webpack.js的42行,可以看到,當webpack命令時,若有--watch,實際同樣是呼叫的compiler.watch方法。
至此,回到middleware.js的line22. 也就是重點了,webpackDevMiddleware中介軟體函式。
line 26~35定義了goNext()方法,該方法首先判斷是否伺服器端渲染,如果不是,直接next()處理,否則,呼叫了shared的ready()方法(根據state狀態,處理邏輯)。
line 36~38,非get請求,直接goNext()。
line 40~41,找不到請求的檔案,直接goNext()。
line 43~78,處理邏輯,可以看到精簡後結構。
也就是呼叫shared.handleRequest方法處理,深入該方法,也即是shared.js的line 189~201,主要邏輯為:判斷是否lazy模式而且沒有定義filename,如果是的話,rebuild(),也就是重新編譯,這就是lazy模式只有在瀏覽器重新重新整理請求的時候才會編譯的原因;如果不是lazy模式,如果所尋找的filename存在(注意此處是通過記憶體fs查詢),那麼呼叫processRequest()處理。
line 45~76,是processRequest()的邏輯,主要是express()的res處理邏輯了,簡單明瞭。
line 85,可以看到return webpackDevMiddleware,最終返回了express的中介軟體。
至此,game over!
-
延伸擴充套件;
lazy模式下什麼表現呢???深入shared.js會發現,當lazy為true(shared.js檔案line 169~175)時,npm run test並不會執行編譯,而是當瀏覽器發出請求req時,在shared.js的handleRequest方法(line 191)的194行執行了rebuild()方法,在rebuild方法的180行執行了context.compiler.run()進行了編譯。在修改後,webpack不會立即執行編譯,而是等到req再次請求時編譯。也就是在lazy模式下,每次只有在瀏覽器請求時,才執行一次compile,watch並沒有什麼卵用啊。
正常模式呢?表現是怎麼樣?正常模式,npm run test時,程式碼執行到startWatch(),也就是執行到compiler的watch()方法,深入compiler原始碼可以看到,Compiler.js檔案的114行,執行到invalidate()方法,判斷是否已經running,如果為false,進入_go()方法,執行了compile()邏輯。也就是說,在沒有瀏覽器請求時,就已經執行了編譯。然後在修改了entry相關的檔案後,watch會執行編譯,同時會觸發compiler的invalid事件(在Compiler.js的watch方法的116行可以看到)也就是會執行到Shared.js的229行,執行compilerInvalid方法,列印compiling資訊。
總結就是,lazy模式只有在瀏覽器請求時,才會執行compile編譯,而正常模式下,則是改變後,立即執行compile過程。
相關文章
- Redux解讀Redux
- CausalTAD解讀
- 精讀《利用 GPT 解讀 PDF》GPT
- Docker瞭解(官方解讀)Docker
- Laravel Pipeline解讀Laravel
- 解讀Linux程式Linux
- 讀《圖解HTTP》圖解HTTP
- 深入解讀Dictionary
- 解讀HTTP/3HTTP
- 一文詳解髒讀、不可重複讀、幻讀
- Laravel 原始碼解讀Laravel原始碼
- reselect原始碼解讀原始碼
- Swoft 原始碼解讀原始碼
- Laravel核心解讀 — RequestLaravel
- Seajs原始碼解讀JS原始碼
- ReentrantLock原始碼解讀ReentrantLock原始碼
- MJExtension原始碼解讀原始碼
- Axios 原始碼解讀iOS原始碼
- SDWebImage原始碼解讀Web原始碼
- MJRefresh原始碼解讀原始碼
- ThreadLocal之深度解讀thread
- Handler原始碼解讀原始碼
- LifeCycle原始碼解讀原始碼
- LinkedHashMap原始碼解讀HashMap原始碼
- ConcurrentHashMap原始碼解讀HashMap原始碼
- Redux原始碼解讀Redux原始碼
- ThreadLocal原始碼解讀thread原始碼
- 精讀《圖解HTTP》圖解HTTP
- WeakHashMap,原始碼解讀HashMap原始碼
- Flutter之Navigator解讀Flutter
- Java列舉解讀Java
- 效能測試解讀
- Webpack解讀之loaderWeb
- DeepSort論文解讀
- HDFS短路讀詳解
- ThreadLocal 原始碼解讀thread原始碼
- /proc/{pid}/maps解讀
- Masonry原始碼解讀原始碼
- ZooKeeper原始碼解讀原始碼