前言
創(chuàng)新互聯(lián)公司是一家專注于成都網(wǎng)站設(shè)計、成都網(wǎng)站制作和鄭州服務(wù)器托管的網(wǎng)絡(luò)公司,有著豐富的建站經(jīng)驗和案例。
Webpack 的使用目前已經(jīng)是前端開發(fā)工程師必備技能之一。若是想在本地環(huán)境啟動一個開發(fā)服務(wù),大家只需在 Webpack 的配置中,增加 devServer 的配置來啟動。devServer 配置的本質(zhì)是 webpack-dev-server 這個包提供的功能,而 webpack-dev-middleware 則是這個包的底層依賴。
截至本文發(fā)表前,webpack-dev-middleware 的最新版本為 webpack-dev-middleware@3.7.2
,本文的源碼來自于此版本。本文會講解 webpack-dev-middleware 的核心模塊實現(xiàn),相信大家把這篇文章看完,再去閱讀源碼,會容易理解很多。
webpack-dev-middleware 是什么?
要回答這個問題,我們先來看看如何使用這個包:
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);
通過啟動一個 Express 服務(wù),將 wdm(compiler) 的結(jié)果通過 app.use 方法注冊為 Express 服務(wù)的中間函數(shù)。從這里,我們不難看出 wdm(compiler) 的執(zhí)行結(jié)果返回的是一個 express 的中間件。它作為一個容器,將 webpack 編譯后的文件存儲到內(nèi)存中,然后在用戶訪問 express 服務(wù)時,將內(nèi)存中對應(yīng)的資源輸出返回。
為什么要使用 webpack-dev-middleware
熟悉 webpack 的同學(xué)都知道,webpack 可以通過watch mode 方式啟動,那為何我們不直接使用此方式來監(jiān)聽資源變化呢?答案就是,webpack 的 watch mode 雖然能監(jiān)聽文件的變更,并且自動打包,但是每次打包后的結(jié)果將會存儲到本地硬盤中,而 IO 操作是非常耗資源時間的,無法滿足本地開發(fā)調(diào)試需求。
而 webpack-dev-middleware 擁有以下幾點特性:
本文將主要圍繞這三個特性和主流程邏輯進(jìn)行分析。
源碼解讀
讓我們先來看下 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 多個文件要解讀。但刨除 utils 工具集合目錄,其核心源碼文件其實只有兩個 index.js、middleware.js
下面我們就來分析核心文件 index.js 、middleware.js 的源碼實現(xiàn)
入口文件 index.js
從上文我們已經(jīng)得知 wdm(compiler) 返回的是一個 express 中間件,所以入口文件 index.js 則為一個中間件的容器包裝函數(shù)。它接收兩個參數(shù),一個為 webpack 的 compiler、另一個為配置對象,經(jīng)過一系列的處理,最后返回一個中間件函數(shù)。下面我將對 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 個部分的執(zhí)行,分別完成了我們上文提到的兩點特性:
setupHooks
此函數(shù)的作用是在 compiler 的 invalid、run、done、watchRun 這 4 個編譯生命周期上,注冊對應(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); } );
compiler.watch
此部分的作用是,調(diào)用 compiler 的 watch 方法,之后 webpack 便會監(jiān)聽文件變更,一旦檢測到文件變更,就會重新執(zhí)行編譯。
setupOutputFileSystem
其作用是使用 memory-fs 對象替換掉 compiler 的文件系統(tǒng)對象,讓 webpack 編譯后的文件輸出到內(nèi)存中。
fileSystem = new MemoryFileSystem(); // eslint-disable-next-line no-param-reassign compiler.outputFileSystem = fileSystem;
通過以上 3 個部分的執(zhí)行,我們以 watch mode 的方式啟動了 webpack,一旦監(jiān)測的文件變更,便會重新進(jìn)行編譯打包,同時我們又將文件的存儲方法改為了內(nèi)存存儲,提高了文件的存儲讀取效率。最后,我們只需要返回 express 的中間件就可以了,而中間件則是調(diào)用 middleware(context) 函數(shù)得到的。下面,我們來看看 middleware 是如何實現(xiàn)的。
middleware.js
此文件返回的是一個 express 中間件函數(shù)的包裝函數(shù),其核心處理邏輯主要針對 request 請求,根據(jù)各種條件判斷,最終返回對應(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 中定義了一個 goNext() 方法,該方法判斷是否是服務(wù)端渲染。如果是,則調(diào)用 ready() 方法(此方法即為 ready.js 文件,作用為根據(jù) context.state 狀態(tài)判斷直接執(zhí)行回調(diào)還是將回調(diào)存儲 callbacks 隊列中)。如果不是,則直接調(diào)用 next() 方法,流轉(zhuǎn)至下一個 express 中間件。
const acceptedMethods = context.options.methods || ['GET', 'HEAD']; if (acceptedMethods.indexOf(req.method) === -1) { return goNext(); }
接著,判斷 HTTP 協(xié)議的請求的類型,若請求不包含于配置中(默認(rèn) GET、HEAD 請求),則直接調(diào)用 goNext() 方法處理請求:
let filename = getFilenameFromUrl( context.options.publicPath, context.compiler, req.url ); if (filename === false) { return goNext(); }
然后,根據(jù)請求的 req.url 地址,在 compiler 的內(nèi)存文件系統(tǒng)中查找對應(yīng)的文件,若查找不到,則直接調(diào)用 goNext() 方法處理請求:
return new Promise((resolve) => { // eslint-disable-next-line consistent-return function processRequest() { ... } ... ready(context, processRequest, req); });
最后,中間件返回一個 Promise 實例,而在實例中,先是定義一個 processRequest 方法,此方法的作用是根據(jù)上文中找到的 filename 路徑獲取到對應(yīng)的文件內(nèi)容,并構(gòu)造 response 對象返回,隨后調(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);
非常簡單的方法,判斷 context.state 的狀態(tài),將直接執(zhí)行回調(diào)函數(shù) fn,或在 context.callbacks 中添加回調(diào)函數(shù) fn。這也解釋了上文提到的另一個特性 “在編譯期間,停止提供舊版的 bundle 并且將請求延遲到最新的編譯結(jié)果完成之后”。若 webpack 還處于編譯狀態(tài),context.state 會被設(shè)置為 false,所以當(dāng)用戶發(fā)起請求時,并不會直接返回對應(yīng)的文件內(nèi)容,而是會將回調(diào)函數(shù) processRequest 添加至 context.callbacks 中,而上文中我們說到在 compile.hooks.done 上注冊了回調(diào)函數(shù) done,等編譯完成之后,將會執(zhí)行這個函數(shù),并循環(huán)調(diào)用 context.callbacks。
總結(jié)
源碼的閱讀是一個非??菰锏倪^程,但是它的收益也是巨大的。上文的源碼解讀主要分析的是 webpack-dev-middleware 它是如何實現(xiàn)它所擁有的特性、如何處理用戶的請求等主要功能點,未包括其他分支邏輯處理、容錯。還需讀者在這篇文章基礎(chǔ)之上,再去閱讀詳細(xì)的源碼,望這篇文章能對你的閱讀過程起到一定的幫助作用。
到此這篇關(guān)于webpack-dev-middleware 源碼解讀的文章就介紹到這了,更多相關(guān)webpack-dev-middleware 源碼解讀內(nèi)容請搜索創(chuàng)新互聯(lián)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持創(chuàng)新互聯(lián)!
當(dāng)前題目:詳解webpack-dev-middleware源碼解讀
URL地址:http://jinyejixie.com/article20/psgcjo.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供定制網(wǎng)站、App開發(fā)、網(wǎng)站收錄、動態(tài)網(wǎng)站、網(wǎng)站制作、外貿(mào)網(wǎng)站建設(shè)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)