欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

詳解webpack-dev-middleware 源碼解讀

 更新時(shí)間:2020年03月23日 08:26:17   作者:政采云前端團(tuán)隊(duì)  
這篇文章主要介紹了webpack-dev-middleware 源碼解讀,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧

前言

Webpack 的使用目前已經(jīng)是前端開發(fā)工程師必備技能之一。若是想在本地環(huán)境啟動(dòng)一個(gè)開發(fā)服務(wù),大家只需在 Webpack 的配置中,增加 devServer 的配置來(lái)啟動(dòng)。devServer 配置的本質(zhì)是 webpack-dev-server 這個(gè)包提供的功能,而 webpack-dev-middleware 則是這個(gè)包的底層依賴。

截至本文發(fā)表前,webpack-dev-middleware 的最新版本為 webpack-dev-middleware@3.7.2,本文的源碼來(lái)自于此版本。本文會(huì)講解 webpack-dev-middleware 的核心模塊實(shí)現(xiàn),相信大家把這篇文章看完,再去閱讀源碼,會(huì)容易理解很多。

webpack-dev-middleware 是什么?

要回答這個(gè)問(wèn)題,我們先來(lái)看看如何使用這個(gè)包:

const wdm = require('webpack-dev-middleware');
const express = require('express');
const webpack = require('webpack');
const webpackConf = require('./webapck.conf.js');
const compiler = webpack(webpackConf);
const app = express();
app.use(wdm(compiler));
app.listen(8080);

通過(guò)啟動(dòng)一個(gè) Express 服務(wù),將 wdm(compiler) 的結(jié)果通過(guò) app.use 方法注冊(cè)為 Express 服務(wù)的中間函數(shù)。從這里,我們不難看出 wdm(compiler) 的執(zhí)行結(jié)果返回的是一個(gè) express 的中間件。它作為一個(gè)容器,將 webpack 編譯后的文件存儲(chǔ)到內(nèi)存中,然后在用戶訪問(wèn) express 服務(wù)時(shí),將內(nèi)存中對(duì)應(yīng)的資源輸出返回。

為什么要使用 webpack-dev-middleware

熟悉 webpack 的同學(xué)都知道,webpack 可以通過(guò)watch mode 方式啟動(dòng),那為何我們不直接使用此方式來(lái)監(jiān)聽資源變化呢?答案就是,webpack 的 watch mode 雖然能監(jiān)聽文件的變更,并且自動(dòng)打包,但是每次打包后的結(jié)果將會(huì)存儲(chǔ)到本地硬盤中,而 IO 操作是非常耗資源時(shí)間的,無(wú)法滿足本地開發(fā)調(diào)試需求。

而 webpack-dev-middleware 擁有以下幾點(diǎn)特性:

  • 以 watch mode 啟動(dòng) webpack,監(jiān)聽的資源一旦發(fā)生變更,便會(huì)自動(dòng)編譯,生產(chǎn)最新的 bundle
  • 在編譯期間,停止提供舊版的 bundle 并且將請(qǐng)求延遲到最新的編譯結(jié)果完成之后
  • webpack 編譯后的資源會(huì)存儲(chǔ)在內(nèi)存中,當(dāng)用戶請(qǐng)求資源時(shí),直接于內(nèi)存中查找對(duì)應(yīng)資源,減少去硬盤中查找的 IO 操作耗時(shí)

本文將主要圍繞這三個(gè)特性和主流程邏輯進(jìn)行分析。

源碼解讀

讓我們先來(lái)看下 webpack-dev-middleware 的源碼目錄:

...
├── lib
│  ├── DevMiddlewareError.js
│  ├── index.js
│  ├── middleware.js
│  └── utils
│    ├── getFilenameFromUrl.js
│    ├── handleRangeHeaders.js
│    ├── index.js
│    ├── ready.js
│    ├── reporter.js
│    ├── setupHooks.js
│    ├── setupLogger.js
│    ├── setupOutputFileSystem.js
│    ├── setupRebuild.js
│    └── setupWriteToDisk.js
├── package.json
...

其中 lib 目錄下為源代碼,一眼望去有近 10 多個(gè)文件要解讀。但刨除 utils 工具集合目錄,其核心源碼文件其實(shí)只有兩個(gè) index.js、middleware.js

下面我們就來(lái)分析核心文件 index.js 、middleware.js 的源碼實(shí)現(xiàn)

入口文件 index.js

從上文我們已經(jīng)得知 wdm(compiler) 返回的是一個(gè) express 中間件,所以入口文件 index.js 則為一個(gè)中間件的容器包裝函數(shù)。它接收兩個(gè)參數(shù),一個(gè)為 webpack 的 compiler、另一個(gè)為配置對(duì)象,經(jīng)過(guò)一系列的處理,最后返回一個(gè)中間件函數(shù)。下面我將對(duì) index.js 中的核心代碼進(jìn)行講解:

...
setupHooks(context);
...
// start watching
context.watching = compiler.watch(options.watchOptions, (err) => {
 if (err) {
  context.log.error(err.stack || err);
  if (err.details) {
   context.log.error(err.details);
  }
 }
});
...
setupOutputFileSystem(compiler, context);

index.js 最為核心的是以上 3 個(gè)部分的執(zhí)行,分別完成了我們上文提到的兩點(diǎn)特性:

  • 以監(jiān)控的方式啟動(dòng) webpack
  • 將 webpack 的編譯內(nèi)容,輸出至內(nèi)存中

setupHooks

此函數(shù)的作用是在 compiler 的 invalid、run、done、watchRun 這 4 個(gè)編譯生命周期上,注冊(cè)對(duì)應(yīng)的處理方法

context.compiler.hooks.invalid.tap('WebpackDevMiddleware', invalid);
context.compiler.hooks.run.tap('WebpackDevMiddleware', invalid);
context.compiler.hooks.done.tap('WebpackDevMiddleware', done);
context.compiler.hooks.watchRun.tap(
 'WebpackDevMiddleware',
 (comp, callback) => {
  invalid(callback);
 }
);
  • 在 done 生命周期上注冊(cè) done 方法,該方法主要是 report 編譯的信息以及執(zhí)行 context.callbacks 回調(diào)函數(shù)
  • 在 invalid、run、watchRun 等生命周期上注冊(cè) invalid 方法,該方法主要是 report 編譯的狀態(tài)信息

compiler.watch

此部分的作用是,調(diào)用 compiler 的 watch 方法,之后 webpack 便會(huì)監(jiān)聽文件變更,一旦檢測(cè)到文件變更,就會(huì)重新執(zhí)行編譯。

setupOutputFileSystem

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

fileSystem = new MemoryFileSystem();
// eslint-disable-next-line no-param-reassign
compiler.outputFileSystem = fileSystem;

通過(guò)以上 3 個(gè)部分的執(zhí)行,我們以 watch mode 的方式啟動(dòng)了 webpack,一旦監(jiān)測(cè)的文件變更,便會(huì)重新進(jìn)行編譯打包,同時(shí)我們又將文件的存儲(chǔ)方法改為了內(nèi)存存儲(chǔ),提高了文件的存儲(chǔ)讀取效率。最后,我們只需要返回 express 的中間件就可以了,而中間件則是調(diào)用 middleware(context) 函數(shù)得到的。下面,我們來(lái)看看 middleware 是如何實(shí)現(xiàn)的。

middleware.js

此文件返回的是一個(gè) express 中間件函數(shù)的包裝函數(shù),其核心處理邏輯主要針對(duì) request 請(qǐng)求,根據(jù)各種條件判斷,最終返回對(duì)應(yīng)的文件內(nèi)容:

function goNext() {
 if (!context.options.serverSideRender) {
  return next();
 }
 return new Promise((resolve) => {
  ready(
   context,
   () => {
    // eslint-disable-next-line no-param-reassign
    res.locals.webpackStats = context.webpackStats;
    // eslint-disable-next-line no-param-reassign
    res.locals.fs = context.fs;
    resolve(next());
   },
   req
  );
 });
}

首先,middleware 中定義了一個(gè) goNext() 方法,該方法判斷是否是服務(wù)端渲染。如果是,則調(diào)用 ready() 方法(此方法即為 ready.js 文件,作用為根據(jù) context.state 狀態(tài)判斷直接執(zhí)行回調(diào)還是將回調(diào)存儲(chǔ) callbacks 隊(duì)列中)。如果不是,則直接調(diào)用 next() 方法,流轉(zhuǎn)至下一個(gè) express 中間件。

const acceptedMethods = context.options.methods || ['GET', 'HEAD'];
if (acceptedMethods.indexOf(req.method) === -1) {
 return goNext();
}

接著,判斷 HTTP 協(xié)議的請(qǐng)求的類型,若請(qǐng)求不包含于配置中(默認(rèn) GET、HEAD 請(qǐng)求),則直接調(diào)用 goNext() 方法處理請(qǐng)求:

let filename = getFilenameFromUrl(
 context.options.publicPath,
 context.compiler,
 req.url
);
if (filename === false) {
 return goNext();
}

然后,根據(jù)請(qǐng)求的 req.url 地址,在 compiler 的內(nèi)存文件系統(tǒng)中查找對(duì)應(yīng)的文件,若查找不到,則直接調(diào)用 goNext() 方法處理請(qǐng)求:

return new Promise((resolve) => {
 // eslint-disable-next-line consistent-return
 function processRequest() {
  ...
 }
 ...
 ready(context, processRequest, req);
});

最后,中間件返回一個(gè) Promise 實(shí)例,而在實(shí)例中,先是定義一個(gè) processRequest 方法,此方法的作用是根據(jù)上文中找到的 filename 路徑獲取到對(duì)應(yīng)的文件內(nèi)容,并構(gòu)造 response 對(duì)象返回,隨后調(diào)用 ready(context, processRequest, req) 函數(shù),去執(zhí)行 processRequest 方法。這里我們著重看下 ready 方法的內(nèi)容:

if (context.state) {
 return fn(context.webpackStats);
}
context.log.info(`wait until bundle finished: ${req.url || fn.name}`);
context.callbacks.push(fn);

非常簡(jiǎn)單的方法,判斷 context.state 的狀態(tài),將直接執(zhí)行回調(diào)函數(shù) fn,或在 context.callbacks 中添加回調(diào)函數(shù) fn。這也解釋了上文提到的另一個(gè)特性 “在編譯期間,停止提供舊版的 bundle 并且將請(qǐng)求延遲到最新的編譯結(jié)果完成之后”。若 webpack 還處于編譯狀態(tài),context.state 會(huì)被設(shè)置為 false,所以當(dāng)用戶發(fā)起請(qǐng)求時(shí),并不會(huì)直接返回對(duì)應(yīng)的文件內(nèi)容,而是會(huì)將回調(diào)函數(shù) processRequest 添加至 context.callbacks 中,而上文中我們說(shuō)到在 compile.hooks.done 上注冊(cè)了回調(diào)函數(shù) done,等編譯完成之后,將會(huì)執(zhí)行這個(gè)函數(shù),并循環(huán)調(diào)用 context.callbacks。

總結(jié)

源碼的閱讀是一個(gè)非常枯燥的過(guò)程,但是它的收益也是巨大的。上文的源碼解讀主要分析的是 webpack-dev-middleware 它是如何實(shí)現(xiàn)它所擁有的特性、如何處理用戶的請(qǐng)求等主要功能點(diǎn),未包括其他分支邏輯處理、容錯(cuò)。還需讀者在這篇文章基礎(chǔ)之上,再去閱讀詳細(xì)的源碼,望這篇文章能對(duì)你的閱讀過(guò)程起到一定的幫助作用。

到此這篇關(guān)于webpack-dev-middleware 源碼解讀的文章就介紹到這了,更多相關(guān)webpack-dev-middleware 源碼解讀內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論