簡單介紹
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中間件函數。
dev-middleware中間件函數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過程。
webpack-dev-middleware解讀
最后編輯于 :
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
- 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
- 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
- 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
推薦閱讀更多精彩內容
- wepack-hot-middleware 深入解讀 webpack-hot-middleware 做什么的?we...
- 原文首發于:Webpack 3,從入門到放棄 Update (2017.8.27) : 關于 output.pub...