webpack-dev-middleware解讀

  1. 簡單介紹
    webpack-dev-middleware,作用就是,生成一個與webpack的compiler綁定的中間件,然后在express啟動的服務app中調用這個中間件。
    這個中間件的作用呢,簡單總結為以下三點:通過watch mode,監聽資源的變更,然后自動打包(如何實現,見下文詳解);快速編譯,走內存;返回中間件,支持express的use格式。特別注明:webpack明明可以用watch mode,可以實現一樣的效果,但是為什么還需要這個中間件呢?
    答案就是,第二點所提到的,采用了內存方式。如果,只依賴webpack的watch mode來監聽文件變更,自動打包,每次變更,都將新文件打包到本地,就會很慢。

  2. 實踐出真知
    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的熱加載效果了,效果展示。

    效果展示
  3. 源碼分析。

    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!

  4. 延伸擴展;

    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過程。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,362評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,013評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,346評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,421評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,146評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,534評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,585評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,767評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,318評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,074評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,258評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,828評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,486評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,916評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,156評論 1 290
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,993評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,234評論 2 375

推薦閱讀更多精彩內容