Webpack 設(shè)計(jì)理念

一、前言

Webpack 一直都是有些人的心魔,不清楚原理是什么,不知道怎么去配置,只會(huì)基本的 API 使用。它就像一個(gè)黑盒,讓部分開發(fā)者對(duì)它望而生畏。
大家之所以認(rèn)為 Webpack 復(fù)雜,很大程度上是因?yàn)樗栏街惶嫶蟮纳鷳B(tài)系統(tǒng)。其實(shí) Webpack 的核心流程遠(yuǎn)沒有我們想象中那么復(fù)雜,甚至只需百來行代碼就能完整復(fù)刻出來。
因此在學(xué)習(xí)過程中,我們應(yīng)注重學(xué)習(xí)它本身的設(shè)計(jì)思想,不管是它的 Plugin 系統(tǒng)還是 Loader 系統(tǒng),都是建立于這套核心思想之上。所謂萬變不離其宗,一通百通。

二、基本使用

初始化項(xiàng)目:

npm init  //初始化一個(gè)項(xiàng)目
yarn add webpack //安裝項(xiàng)目依賴

安裝完依賴后,根據(jù)以下目錄結(jié)構(gòu)來添加對(duì)應(yīng)的目錄和文件:

├── node_modules
├── package-lock.json
├── package.json
├── webpack.config.js #配置文件
├── debugger.js #測(cè)試文件
└── src # 源碼目錄
     |── index.js
     |── name.js
     └── age.js

webpack.config.js

const path = require("path");
module.exports = {
  mode: "development", //防止代碼被壓縮
  entry: "./src/index.js", //入口文件
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "[name].js",
  },
  devtool: "source-map", //防止干擾源文件
}; 

src/index.js

const name = require("./name");
const age = require("./age");
console.log("entry文件打印作者信息", name, age);

src/name.js

module.exports = "不要禿頭啊";

src/age.js

module.exports = "99";

文件依賴關(guān)系:
入口文件 src/index.js
依賴文件 src/name.js src/age.js
Webpack 本質(zhì)上是一個(gè)函數(shù),它接受一個(gè)配置信息作為參數(shù),執(zhí)行后返回一個(gè) compiler 對(duì)象,調(diào)用 compiler 對(duì)象中的 run 方法就會(huì)啟動(dòng)編譯。run 方法接受一個(gè)回調(diào),可以用來查看編譯過程中的錯(cuò)誤信息或編譯信息。
debugger.js

// const { webpack } = require("./webpack.js"); //后面自己手寫
const { webpack } = require("webpack");
const webpackOptions = require("./webpack.config.js");
const compiler = webpack(webpackOptions);

//開始編譯
compiler.run((err, stats) => {
  console.log(err);
  console.log(
    stats.toJson({
      assets: true, //打印本次編譯產(chǎn)出的資源
      chunks: true, //打印本次編譯產(chǎn)出的代碼塊
      modules: true, //打印本次編譯產(chǎn)出的模塊
    })
  );
});

執(zhí)行打包命令:

node ./debugger.js

得到產(chǎn)出文件 dist/main.js
運(yùn)行該文件,得到結(jié)果:

entry文件打印作者信息 不要禿頭啊 99

三、核心思想

我們先來分析一下源代碼和構(gòu)建產(chǎn)物之間的關(guān)系:
入口文件(src/index.js)被包裹在最后的立即執(zhí)行函數(shù)中,而它所依賴的模塊(src/name.js、src/age.js)則被放進(jìn)了 modules 對(duì)象中(modules 用于存放入口文件的依賴模塊,key 值為依賴模塊路徑,value 值為依賴模塊源代碼)。
require 函數(shù)是 web 環(huán)境下 加載模塊的方法( require 原本是 node環(huán)境 中內(nèi)置的方法,瀏覽器并不認(rèn)識(shí) require,所以這里需要手動(dòng)實(shí)現(xiàn)一下),它接受模塊的路徑為參數(shù),返回模塊導(dǎo)出的內(nèi)容。
要想弄清楚 Webpack 原理,那么核心問題就變成了:如何將左邊的源代碼轉(zhuǎn)換成 dist/main.js 文件?

核心思想:

  • 第一步:首先,根據(jù)配置信息(webpack.config.js)找到入口文件(src/index.js)
  • 第二步:找到入口文件所依賴的模塊,并收集關(guān)鍵信息:比如路徑、源代碼、它所依賴的模塊等:
var modules = [
{
  id: "./src/name.js",//路徑
  dependencies: [], //所依賴的模塊
  source: 'module.exports = "不要禿頭啊";', //源代碼
},
{
  id: "./src/age.js",
  dependencies: [], 
  source: 'module.exports = "99";',
},
{
  id: "./src/index.js",
  dependencies: ["./src/name.js", "./src/age.js"], 
  source:
    'const name = require("./src/name.js");\n' +
    'const age = require("./src/age.js");\n' +
    'console.log("entry文件打印作者信息", name, age);',
},
];
  • 第三步:根據(jù)上一步得到的信息,生成最終輸出到硬盤中的文件(dist): 包括 modules 對(duì)象、require 模版代碼、入口執(zhí)行文件等

在這過程中,由于瀏覽器并不認(rèn)識(shí)除 html、js、css 以外的文件格式,所以我們還需要對(duì)源文件進(jìn)行轉(zhuǎn)換 —— Loader 系統(tǒng)。
Loader 系統(tǒng) 本質(zhì)上就是接收資源文件,并對(duì)其進(jìn)行轉(zhuǎn)換,最終輸出轉(zhuǎn)換后的文件:
除此之外,打包過程中也有一些特定的時(shí)機(jī)需要處理,比如:

  • 在打包前需要校驗(yàn)用戶傳過來的參數(shù),判斷格式是否符合要求
  • 在打包過程中,需要知道哪些模塊可以忽略編譯,直接引用 cdn 鏈接
  • 在編譯完成后,需要將輸出的內(nèi)容插入到 html 文件中
  • 在輸出到硬盤前,需要先清空 dist 文件夾

這個(gè)時(shí)候需要一個(gè)可插拔的設(shè)計(jì),方便給社區(qū)提供可擴(kuò)展的接口 —— Plugin 系統(tǒng)。
Plugin 系統(tǒng) 本質(zhì)上就是一種事件流的機(jī)制,到了固定的時(shí)間節(jié)點(diǎn)就廣播特定的事件,用戶可以在事件內(nèi)執(zhí)行特定的邏輯,類似于生命周期:
打包前的生命周期 => 打包過程中的生命周期 => 打包成功的生命周期 => 打包失敗的生命周期
這些設(shè)計(jì)也都是根據(jù)使用場(chǎng)景來的,只有理清需求后我們才能更好的理解它的設(shè)計(jì)思想。

四、架構(gòu)設(shè)計(jì)

在理清楚核心思想后,剩下的就是對(duì)其進(jìn)行一步步拆解。
上面提到,我們需要建立一套事件流的機(jī)制來管控整個(gè)打包過程,大致可以分為三個(gè)階段:

  • 打包開始前的準(zhǔn)備工作
  • 打包過程中(也就是編譯階段)
  • 打包結(jié)束后(包含打包成功和打包失?。?/li>

這其中又以編譯階段最為復(fù)雜,另外還考慮到一個(gè)場(chǎng)景:watch mode(當(dāng)文件變化時(shí),將重新進(jìn)行編譯),因此這里最好將編譯階段(也就是下文中的compilation)單獨(dú)解耦出來。

在 Webpack 源碼中,compiler 就像是一個(gè)大管家,它就代表上面說的三個(gè)階段,在它上面掛載著各種生命周期函數(shù),而 compilation 就像專管伙食的廚師,專門負(fù)責(zé)編譯相關(guān)的工作,也就是打包過程中這個(gè)階段。畫個(gè)圖幫助大家理解:

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 前言 Google Play應(yīng)用市場(chǎng)對(duì)于應(yīng)用的targetSdkVersion有了更為嚴(yán)格的要求。從 2018 年...
    申國駿閱讀 65,723評(píng)論 15 98
  • """1.個(gè)性化消息: 將用戶的姓名存到一個(gè)變量中,并向該用戶顯示一條消息。顯示的消息應(yīng)非常簡(jiǎn)單,如“Hello ...
    她即我命閱讀 4,825評(píng)論 0 6
  • 我們都是軟弱的人,所以才會(huì)說謊。我們都是膽小的人,所以才要武裝。我們都是一群笨蛋,所以才會(huì)互相傷害。
    所羅門的偽證_dc0a閱讀 3,481評(píng)論 1 3
  • 為了讓我有一個(gè)更快速、更精彩、更輝煌的成長,我將開始這段刻骨銘心的自我蛻變之旅!從今天開始,我將每天堅(jiān)持閱...
    李薇帆閱讀 2,224評(píng)論 1 4
  • 似乎最近一直都在路上,每次出來走的時(shí)候感受都會(huì)很不一樣。 1、感恩一直遇到好心人,很幸運(yùn)。在路上總是...
    時(shí)間里的花Lily閱讀 1,703評(píng)論 1 3

友情鏈接更多精彩內(nèi)容