国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

webpack-dev-middleware@1.12.2 源碼解讀

yearsj / 3410人閱讀

摘要:如果此時我們不想把文件輸出到內(nèi)存里,可以通過修改的源代碼來實(shí)現(xiàn)。服務(wù)啟動成功。。。根據(jù)請求的,拼接出

? webpack-dev-middleware 是express的一個中間件,它的主要作用是以監(jiān)聽模式啟動webpack,將webpack編譯后的文件輸出到內(nèi)存里,然后將內(nèi)存的文件輸出到epxress服務(wù)器上;下面通過一張圖片來看一下它的工作原理:

了解了它的工作原理以后我們通過一個例子進(jìn)行實(shí)操一下。

demo1:初始化webpack-dev-middleware中間件,啟動webpack監(jiān)聽模式編譯,返回express中間件函數(shù)

// src/app.js
console.log("App.js");
document.write("webpack-dev-middleware");
// demo1/index.js
const path = require("path");
const express = require("express");
const webpack = require("webpack");
const webpackDevMiddleware = require("webpack-dev-middleware");     // webpack開發(fā)中間件
const HtmlWebpackPlugin = require("html-webpack-plugin");           // webpack插件:根據(jù)模版生成html,并且自動注入引用webpack編譯出來的css和js文件

/**
 * 創(chuàng)建webpack編譯器
 */
const comoiler = webpack({  // webpack配置
    entry: path.resolve(__dirname, "src/app.js"),       // 入口文件
    output: {                                           // 輸出配置
        path: path.resolve(__dirname, "dist"),          // 輸出路徑
        filename: "bundle.[hash].js"                    // 輸出文件
    },
    plugins: [                                          // 插件
        new HtmlWebpackPlugin({                         // 根據(jù)模版自動生成html文件插件,并將webpack打包輸出的js文件注入到html文件中
            title: "webpack-dev-middleware"
        })
    ]
});

/**
 * 執(zhí)行webpack-dev-middleware初始化函數(shù),返回express中間件函數(shù)
 * 這個函數(shù)內(nèi)部以監(jiān)聽模式啟動了webpack編譯,相當(dāng)于執(zhí)行cli的: webpack --watch命令
 * 也就是說執(zhí)行到這一步,就已經(jīng)啟動了webpack的監(jiān)聽模式編譯了,代碼執(zhí)行到這里可以看到控制臺已經(jīng)輸出了webpack編譯成功的相關(guān)日志了
 * 由于webpack-dev-middleware中間件內(nèi)部使用memory-fs替換了compiler的outputFileSystem對象,將webpack打包編譯的文件都輸出到內(nèi)存中
 * 所以磁盤上看不到任何webpack編譯輸出的文件
 */
const webpackDevMiddlewareInstance = webpackDevMiddleware(comoiler,{
    reportTime: true,           // webpack狀態(tài)日志輸出帶上時間前綴
    stats: {
        colors: true,           // webpack編譯輸出日志帶上顏色,相當(dāng)于命令行 webpack --colors
        process: true
    }
});

運(yùn)行結(jié)果:

源碼鏈接:https://github.com/Jameswain/...

? 通過上述例子的運(yùn)行結(jié)果,我們可以發(fā)現(xiàn)webpack-dev-middleware實(shí)際上是一個函數(shù),通過執(zhí)行它會返回一個express風(fēng)格的中間件函數(shù),并且會以監(jiān)聽模式啟動webpack編譯。由于webpack-dev-middleware中間件內(nèi)部使用memory-fs替換了compiler的outputFileSystem對象,將webpack打包編譯的文件都輸出到內(nèi)存中,所以雖然我們看到控制臺上有webpack編譯成功的日志,但是并沒有看到任何的輸出文件,就是這個原因,因?yàn)檫@些文件在內(nèi)存里。

? 如果此時我們不想把文件輸出到內(nèi)存里,可以通過修改webpack-dev-middleware的源代碼來實(shí)現(xiàn)。打開node_modules/webpack-dev-middleware/lib/Shared.js文件,將該文件的231行注視掉后,重新運(yùn)行 node demo1/index.js 即可看到文件被輸出到demo1/dist文件夾中。

? 問:為什么webpack-dev-middleware要將webpack打包后的文件輸出到內(nèi)存中,而不是直接到磁盤上呢?

? 答:速度,因?yàn)镮O操作是非常耗資源時間的,直接在內(nèi)存里操作會比磁盤操作會更加快速和高效。因?yàn)榧词故莣ebpack把文件輸出到磁盤,要將磁盤上到文件通過一個服務(wù)輸出到瀏覽器,也是需要將磁盤的文件讀取到內(nèi)存里,然后在通過流進(jìn)行輸出,然后瀏覽器上才能看到,所以中間件這么做其實(shí)還是省了一步讀取磁盤文件的操作。

? 下面通過一個例子演示一下如何將本地磁盤上的文件通過Express服務(wù)輸出到response,在瀏覽器上進(jìn)行訪問:

//demo3/app.js
const express = require("express");
const path = require("path");
const fs = require("fs");
const app = express();

// 讀取index.html文件
const htmlIndex = fs.readFileSync(path.resolve(__dirname,"index.html"));
// 讀取圖片
const img = fs.readFileSync(path.resolve(__dirname, "node.jpg"));

app.use((req, res, next) => {
    console.log(req.url)
    if (req.url === "/" || req.url === "/index.html") {
        res.setHeader("Content-Type", "text/html;charset=UTF-8");
        res.setHeader("Content-Length", htmlIndex.length);
        res.send(htmlIndex);    // 傳送HTTP響應(yīng)
        // res.end();           // 此方法向服務(wù)器發(fā)出信號,表明已發(fā)送所有響應(yīng)頭和主體,該服務(wù)器應(yīng)該視為此消息已完成。 必須在每個響應(yīng)上調(diào)用此 response.end() 方法。
        // res.sendFile(path.resolve(__dirname, "index.html"));    //傳送指定路徑的文件 -會自動根據(jù)文件extension設(shè)定Content-Type
    } else if (req.url === "/node.jpg") {
        res.end(img);   // 此方法向服務(wù)器發(fā)出信號,表明已發(fā)送所有響應(yīng)頭和主體,該服務(wù)器應(yīng)該視為此消息已完成。 必須在每個響應(yīng)上調(diào)用此 response.end() 方法。
    }
});

app.listen(3000, () => console.log("express 服務(wù)啟動成功。。。"));

//瀏覽器訪問:http://localhost:3000/node.jpg
//瀏覽器訪問:http://localhost:3000/

項(xiàng)目目錄:

運(yùn)行結(jié)果:

通過上述代碼我們可以看出不管是輸出html文件還是圖片文件都是需要先將這些文件讀取到內(nèi)存里,然后才能輸出到response上。

?

middleware.js

? 下面我們就來看看webpack-dev-middleware這個函數(shù)內(nèi)部是如何實(shí)現(xiàn)的,它的運(yùn)行原理是什么?個人感覺讀源碼最主要的就是基礎(chǔ) + 耐心 + 流程

? 首先打開node_modules/webpack-dev-middleware/middleware.js文件,注意版本號,我這份代碼的版本號是webpack-dev-middleware@1.12.2。

? middleware.js文件就是webpack-dev-middleware的入口文件,它主要做以下幾件事情:

? 1、記錄compiler對象和中間件配置

? 2、創(chuàng)建webpack操作對象shared

? 3、創(chuàng)建中間件函數(shù)webpackDevMiddleware

? 4、將webpack的一些常用操作函數(shù)暴露到中間件函數(shù)上,供外部直接調(diào)用

? 5、返回中間件函數(shù)

Shared.js

這個文件對webpack的compiler這個對象進(jìn)行封裝操作,我們大概先來看看這個文件主要做了哪些事情:

首先設(shè)置中間件的一些默認(rèn)選項(xiàng)配置

使用memory-fs對象替換掉compiler的文件系統(tǒng)對象,讓webpack編譯后的文件輸出到內(nèi)存中

監(jiān)聽webpack的鉤子函數(shù)

invalid:監(jiān)聽模式下,文件發(fā)生變化時調(diào)用,同時會傳入2個參數(shù),分別是文件名和時間戳

watch-run:監(jiān)聽模式下,一個新的編譯觸發(fā)之后,完成編譯之前調(diào)用

done:編譯完成時調(diào)用,并傳入webpack編譯日志對象stats

run:在開始讀取記錄之前調(diào)用,只有調(diào)用compiler.run()函數(shù)時才會觸發(fā)該鉤子函數(shù)

以觀察者模式啟動webpack編譯

返回share對象,該對象封裝了很多關(guān)于compiler的操作函數(shù)

通過上面的截圖我們大概知道了Shared.js文件的運(yùn)行流程,下面我們再來看看它一些比較重要的細(xì)節(jié)。

share.setOptions 設(shè)置中間件的默認(rèn)配置

share.setFs(context.compiler) 設(shè)置compiler的文件操作對象

share.startWatch() 以觀察模式啟動webpack

compiler.watch(watchOptions, callback) 這個函數(shù)表示以監(jiān)聽模式啟動webpack并返回一個watching對象,這里特別需要注意的是當(dāng)調(diào)用compiler.watch函數(shù)時會立即執(zhí)行watch-run這個鉤子回調(diào)函數(shù),直到這個鉤子回調(diào)函數(shù)執(zhí)行完畢后,才會返回watching對象。

share.compilerDone(stats) webpack編譯完成回調(diào)處理函數(shù)

當(dāng)webpack的一個編譯完成時會進(jìn)入done鉤子回調(diào)函數(shù),然后調(diào)用compilerDone函數(shù),這個函數(shù)內(nèi)部首先將context.state設(shè)置為true表示webpack編譯完成,并記錄webpack的統(tǒng)計信息對象stats,然后將webpack日志輸出操作和回調(diào)函數(shù)執(zhí)行都放到process.nextTick()任務(wù)隊(duì)列執(zhí)行,就是等主邏輯所有的代碼執(zhí)行完畢后才進(jìn)行webpack的日志輸出和中間件回調(diào)函數(shù)的執(zhí)行。

context.options.reporter (share.defaultReporter) webpack默認(rèn)日志輸出函數(shù)

context.options.reporter 和 share.defaultReporter 指向的都是同一個函數(shù)

? 通過代碼我們可以看出這個函數(shù)內(nèi)部首先是要判斷一下state這個狀態(tài),false表示webpack處于編譯中,則直接輸出 webpack: Compiling...。true:則表示webpack編譯完成,則需要判斷webpack-dev-middleware這個中間件都兩個配置,noInfo和quiet,noInfo如果是為true則只輸出錯誤和警告,quiet為true則不輸出任何內(nèi)容,默認(rèn)這倆選項(xiàng)都是false,這時候會判斷webpack編譯成功后返回的stats對象里有沒有錯誤和警告,有錯誤或警告就輸出錯誤和警告,沒有則輸出webpack的編譯日志,并且使用webpack-dev-middleware的options.stats配置項(xiàng)作為webpack日志輸出配置,更多webpack日志輸出配置選項(xiàng)見:https://www.webpackjs.com/con...

handleCompilerCallback() - watch回調(diào)函數(shù)

這個是watch回調(diào)函數(shù),它是在compiler.plugin("done")鉤子函數(shù)執(zhí)行完畢之后執(zhí)行,它有兩個參數(shù),一個是錯誤信息,一個是webpack編譯成功的統(tǒng)計信息對象stats,可以看到這個回調(diào)函數(shù)內(nèi)部只做錯誤信息的輸出。

webpack watch模式鉤子函數(shù)執(zhí)行流程圖

使用webpack-dev-middleware中間件

? 之前我介紹的都是webpack-dev-middleware中間件初始化階段主要做了什么事情,而且我的第一個代碼例子里也只是調(diào)用了webpack-dev-middleware中間件的初始化函數(shù)而已,并沒有和express結(jié)合使用,當(dāng)時這么做的主要是為了說明這個中間件的初始化階段的運(yùn)行機(jī)制,下面我們通過一個完整一點(diǎn)的例子說明webpack-dev-middleware中間件如何和express進(jìn)行結(jié)合使用以及它的運(yùn)行流程和原理。

// demo2/index.js
const path = require("path");
const express = require("express");
const webpack = require("webpack");
const webpackDevMiddleware = require("webpack-dev-middleware");
const HtmlWebpackPlugin = require("html-webpack-plugin");

// 創(chuàng)建webpack編譯器
const compiler = webpack({
    entry: path.resolve(__dirname, "src/app.js"),
    output: {
        path: path.resolve(__dirname, "dist"),
        filename: "bundle.[hash].js"
    },
    plugins: [
        new HtmlWebpackPlugin({
            title: "webpack-dev-middleware"
        })
    ]
});

// webpack開發(fā)中間件:其實(shí)這個中間件函數(shù)執(zhí)行完成時,中間件內(nèi)部就會執(zhí)行webpack的watch函數(shù),啟動webpack的監(jiān)聽模式,相當(dāng)于執(zhí)行cli的: webpack --watch命令
const webpackDevMiddlewareInstance = webpackDevMiddleware(compiler, {
    reportTime: true,           // webpack狀態(tài)日志輸出帶上時間前綴
    stats: {
        colors: true,           // webpack編譯輸出日志帶上顏色,相當(dāng)于命令行 webpack --colors
        process: true
    },
    // noInfo: true,            // 不輸出任何webpack編譯日志(只有警告和錯誤)
    // quiet: true,             // 不向控制臺顯示任何內(nèi)容
    // reporter: function (context) {   // 提供自定義報告器以更改webpack日志的輸出方式。
    //     console.log(context.stats.toString(context.options.stats))
    // },
});

/**
 * webpack第一次編譯完成并且輸出編譯日志后調(diào)用
 * 之后監(jiān)聽到文件變化重新編譯不會再執(zhí)行此函數(shù)
 */
webpackDevMiddlewareInstance.waitUntilValid(stats => {
    console.log("webpack第一次編譯成功回調(diào)函數(shù)");
});

// 創(chuàng)建express對象
const app = express();
app.use(webpackDevMiddlewareInstance);      // 使用webpack-dev-middleware中間件,每一個web請求都會進(jìn)入該中間件函數(shù)
app.listen(3000, () => console.log("啟動express服務(wù)..."));  // 啟動express服務(wù)器在3000端口上

// for (let i = 0; i < 10000022220; i++) {}    // 會阻塞webpack的編譯操作

? 源碼地址:https://github.com/Jameswain/...

? 通過app.use進(jìn)行使用中間件,然后我們通過在瀏覽器訪問localhost:3000,然后就可以看到效果了,此時任何一個web請求都會執(zhí)行webpack-dev-middleware的中間件函數(shù),下面我們來看看這個中間件函數(shù)內(nèi)部是如何實(shí)現(xiàn)的,到底做了哪些事情。

? 1、我們先通過一個流程圖看一下上面這段代碼首次執(zhí)行webpack-dev-middleware的內(nèi)部運(yùn)行流程

?

? 2、middleware.js文件中的webpackDevMiddleware函數(shù)代碼解析

// webpack-dev-middleware 中間件函數(shù),每一個http請求都會進(jìn)入次函數(shù)
    function webpackDevMiddleware(req, res, next) {
        /**
         * 執(zhí)行下一個中間件
         */
        function goNext() {
            // 如果不是服務(wù)器端渲染,則直接執(zhí)行下一個中間件函數(shù)
            if(!context.options.serverSideRender) return next();
            return new Promise(function(resolve) {
                shared.ready(function() {
                    res.locals.webpackStats = context.webpackStats;
                    resolve(next());
                }, req);
            });
        }

        // 如果不是GET請求,則直接調(diào)用下一個中間件并返回退出函數(shù)
        if(req.method !== "GET") {
            return goNext();
        }

        // 根據(jù)請求的URL獲取webpack編譯輸出文件的絕對路徑;例如:req.url="/bundle.492db0756b0d8df3e6dd.js" 獲取到的filename就是"/Users/jameswain/WORK/blog/demo2/dist/bundle.492db0756b0d8df3e6dd.js"
        // 可以看到其實(shí)就是webpack編譯輸出文件的絕對路徑和名稱
        var filename = getFilenameFromUrl(context.options.publicPath, context.compiler, req.url);
        if(filename === false) return goNext();

        return new Promise(function(resolve) {
            shared.handleRequest(filename, processRequest, req);
            function processRequest(stats) {
                try {
                    var stat = context.fs.statSync(filename);
                    // 處理當(dāng)前請求是 / 的情況
                    if(!stat.isFile()) {
                        if(stat.isDirectory()) {
                            // 如果請求的URL是/,則將它的文件設(shè)置為中間件配置的index選項(xiàng)
                            var index = context.options.index;
                            // 如果中間件沒有設(shè)置index選項(xiàng),則默認(rèn)設(shè)置為index.html
                            if(index === undefined || index === true) {
                                index = "index.html";
                            } else if(!index) {
                                throw "next";
                            }
                            // 將webpack的輸出目錄outputPath和index.html拼接起來
                            filename = pathJoin(filename, index);
                            stat = context.fs.statSync(filename);
                            if(!stat.isFile()) throw "next";
                        } else {
                            throw "next";
                        }
                    }
                } catch(e) {
                    return resolve(goNext());
                }

                // server content  服務(wù)器內(nèi)容
                // 讀取文件內(nèi)容
                var content = context.fs.readFileSync(filename);
                // console.log(content.toString())  //輸出文件內(nèi)容
                // 處理可接受數(shù)據(jù)范圍的請求頭
                content = shared.handleRangeHeaders(content, req, res);
                // 獲取文件的mime類型
                var contentType = mime.lookup(filename);
                // do not add charset to WebAssembly files, otherwise compileStreaming will fail in the client
                // 不要將charset添加到WebAssembly文件中,否則編譯流將在客戶端失敗
                if(!/.wasm$/.test(filename)) {
                    contentType += "; charset=UTF-8";
                }
                res.setHeader("Content-Type", contentType);
                res.setHeader("Content-Length", content.length);
                // 中間件自定義請求頭配置,如果中間件有配置,則循環(huán)設(shè)置這些請求頭
                if(context.options.headers) {
                    for(var name in context.options.headers) {
                        res.setHeader(name, context.options.headers[name]);
                    }
                }
                // Express automatically sets the statusCode to 200, but not all servers do (Koa).
                // Express自動將statusCode設(shè)置為200,但不是所有服務(wù)器都這樣做(Koa)。
                res.statusCode = res.statusCode || 200;
                // 將請求的文件或數(shù)據(jù)內(nèi)容輸出到客戶端(瀏覽器)
                if(res.send) res.send(content);
                else res.end(content);
                resolve();
            }
        });
    }

? 這是webpack-dev-middleware中間件的源代碼,我加了一些注釋和個人見解說明這個中間件內(nèi)部的具體操作,這里我簡單總結(jié)一下這個中間件函數(shù)主要做了哪些事情:

首先判斷如果不是GET請求,則調(diào)用下一個中間件函數(shù),并退出當(dāng)前中間件函數(shù)。

根據(jù)請求的URL,拼接出該資源在webpack輸出目錄的絕對路徑。例如:請求的URL為“/bundle.js”,那么在我電腦拼接出的絕對路徑就為"/Users/jameswain/WORK/blog/demo2/dist/bundle.js",如果請求的URL為/,設(shè)置文件為index.html

讀取請求文件的內(nèi)容,是一個Buffer類型,可以立即為流

判斷客戶端是否設(shè)置了range請求頭,如果設(shè)置了,則需要對內(nèi)容進(jìn)行截取限制在指定范圍之內(nèi)。

獲取請求文件的mime類型

設(shè)置請求頭Content-Type和Content-Length,循環(huán)設(shè)置中間件配置的自定義請求頭

設(shè)置狀態(tài)碼為200

將文件內(nèi)容輸出到客戶端

? 下面通過一個流程圖看一下這個中間件函數(shù)的執(zhí)行流程:

總結(jié)

? webpack-dev-middleware這個中間件內(nèi)部其實(shí)主就是做了兩件事,第一就是在中間件函數(shù)初始化時,修改webpack的文件操作對象,讓webpack編譯后的文件輸出到內(nèi)存里,以監(jiān)聽模式啟動webpack。第二就是當(dāng)有http get請求過來時,中間件函數(shù)內(nèi)部讀取webpack輸出到內(nèi)存里的文件,然后輸出到response上,這時候?yàn)g覽器拿到的就是webpack編譯后的資源文件了。

? 最后給出本文所有相關(guān)源代碼的地址:https://github.com/Jameswain/...

? 聲明:本文純屬個人閱讀webpack-dev-middleware@1.12.2源碼的一些個人理解和感悟,由于本人技術(shù)水平有限,如有錯誤還望各位大神批評指正。

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/102835.html

相關(guān)文章

  • React 源碼深度解讀(三):首次 DOM 元素渲染 - Part 3

    摘要:在學(xué)習(xí)源碼的過程中,給我?guī)椭畲蟮木褪沁@個系列文章,于是決定基于這個系列文章談一下自己的理解。到此為止,首次渲染就完成啦總結(jié)從啟動到元素渲染到頁面,并不像看起來這么簡單,中間經(jīng)歷了復(fù)雜的層級調(diào)用。 前言 React 是一個十分龐大的庫,由于要同時考慮 ReactDom 和 ReactNative ,還有服務(wù)器渲染等,導(dǎo)致其代碼抽象化程度很高,嵌套層級非常深,閱讀其源碼是一個非常艱辛的過...

    U2FsdGVkX1x 評論0 收藏0
  • React 源碼深度解讀(六):依賴注入

    摘要:依賴注入和控制反轉(zhuǎn),這兩個詞經(jīng)常一起出現(xiàn)。一句話表述他們之間的關(guān)系依賴注入是控制反轉(zhuǎn)的一種實(shí)現(xiàn)方式。而兩者有大量的代碼都是可以共享的,這就是依賴注入的使用場景了。下一步就是創(chuàng)建具體的依賴內(nèi)容,然后注入到需要的地方這里的等于這個對象。 前言 React 是一個十分龐大的庫,由于要同時考慮 ReactDom 和 ReactNative ,還有服務(wù)器渲染等,導(dǎo)致其代碼抽象化程度很高,嵌套層級...

    glumes 評論0 收藏0
  • 來一打前端博客壓壓驚

    前言 本文所有內(nèi)容全部發(fā)布再個人博客主頁 https://github.com/muwoo/blogs歡迎訂閱。不過最近因?yàn)槭虑楸容^多,有一段時間沒有更新了,后面打算繼續(xù)不斷學(xué)習(xí)更新,歡迎小伙伴一起溝通交流~ 最近更新 前端單測的那些事 基于virtual dom 的canvas渲染 js Event loop 機(jī)制簡介 axios 核心源碼實(shí)現(xiàn)原理 JS 數(shù)據(jù)類型、賦值、深拷貝和淺拷貝 j...

    wangbinke 評論0 收藏0
  • 來一打前端博客壓壓驚

    前言 本文所有內(nèi)容全部發(fā)布再個人博客主頁 https://github.com/muwoo/blogs歡迎訂閱。不過最近因?yàn)槭虑楸容^多,有一段時間沒有更新了,后面打算繼續(xù)不斷學(xué)習(xí)更新,歡迎小伙伴一起溝通交流~ 最近更新 前端單測的那些事 基于virtual dom 的canvas渲染 js Event loop 機(jī)制簡介 axios 核心源碼實(shí)現(xiàn)原理 JS 數(shù)據(jù)類型、賦值、深拷貝和淺拷貝 j...

    villainhr 評論0 收藏0
  • 來一打前端博客壓壓驚

    前言 本文所有內(nèi)容全部發(fā)布再個人博客主頁 https://github.com/muwoo/blogs歡迎訂閱。不過最近因?yàn)槭虑楸容^多,有一段時間沒有更新了,后面打算繼續(xù)不斷學(xué)習(xí)更新,歡迎小伙伴一起溝通交流~ 最近更新 前端單測的那些事 基于virtual dom 的canvas渲染 js Event loop 機(jī)制簡介 axios 核心源碼實(shí)現(xiàn)原理 JS 數(shù)據(jù)類型、賦值、深拷貝和淺拷貝 j...

    xiaoqibTn 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<