一文搞懂 webpack 懶加載機制 —— webpack 系列

webpack 是一個現代 JavaScript 應用程序的靜態模塊打包器 (module bundler)。當 webpack 處理應用程序時,它會遞歸地構建一個依賴關係圖 (dependency graph),其中包含應用程序須要的每一個模塊,而後將全部這些模塊打包成一個或多個 bundlehtml

前端工程化演進到今天,webpack 作了很大的貢獻。項目工程化帶來了不少便捷,咱們再也不須要手動處理依賴之間的關係,也能夠更方便的使用更多好用的框架,咱們能夠更關注業務自己,集中精力打造咱們的產品。前端

在 webpack 中,使用懶加載或者按需加載,是一種很好的優化網頁或應用的方式。這種方式其實是先把你的代碼在一些邏輯斷點處分離開,而後在一些代碼塊中完成某些操做後,當即引用或即將引用另一些新的代碼塊。這樣加快了應用的初始加載速度,減輕了它的整體體積,由於某些代碼塊可能永遠不會被加載。webpack

那麼,接下來讓咱們來探究一下 webpack 對懶加載的模塊作了哪些工做吧~git

實現背景

咱們先假設咱們在完成一個真實的項目,這個項目中有上傳下載功能。github

下載功能通常是打開一個連接,因此咱們直接實如今主包中。而上傳功能可能會使用到第三方 sdk,咱們使用懶加載進行加載。只有在用戶點擊上傳時,咱們纔會加載這個具有上傳功能的包,來進行上傳。web

上傳下載功能可能會使用到一些第三方 sdk,而這些第三方 sdk 的體積每每很是大,而且這個功能因此這個功能作成懶加載實現是合理的。

爲了演示差異,咱們這裏將「下載」和「上傳」兩個功能作區分。npm

項目基礎配置

咱們先搭建一個基礎的 webpack 配置,讓其支持懶加載配置,而後咱們直接經過打包後的代碼來看看懶加載實現的效果。咱們須要有個基礎目錄配置,項目 Demo 目錄結構以下:json

image

文件/目錄 說明
src 入口文件、下載模塊、上傳模塊
index.html html 模板文件
webpack.config.js webpack 配置文件
package.json 項目說明文件

功能代碼實現

咱們先來看看咱們的功能代碼實現吧,分別是 download.jsupload.jsindex.js前端工程化

// ./src/download.js
const download = () => {
  console.log("download start");

  console.log("schedule download sdk");
  
  console.log("download");
}

export default download;
// ./src/upload.js
const upload = () => {
  console.log("upload start");

  console.log("schedule upload sdk");
  
  console.log("upload");
}

export default upload;
// ./src/index.js
import download from "./download";

console.log("initial page");

async function handlerUploadClick() {
  // 動態加載 upload 模塊,該模塊的 default 屬性就是 upload 方法
  const { default: upload } = await import("./upload");
  // 調用 upload 方法
  upload();
}

async function handlerDownloadClick() {
  download();
}

// 點擊 upload 按鈕時,調用上傳方法
document.querySelector("#upload").addEventListener("click", handlerUploadClick, false)

// 點擊 download 按鈕時,調用下載方法
document.querySelector("#download").addEventListener("click", handlerDownloadClick, false)
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Webpack LazyLoad</title>
</head>

<body>
  <section>
    <h1>Home</h1>
    <button id="upload">Upload</button>
    <button id="download">Download</button>
  </section>
</body>

</html>

在咱們的功能代碼實現中,咱們實現了一個 html 網頁,其中有兩個按鈕,一個是上傳按鈕,一個是下載按鈕。數組

配置實現

功能實現後,咱們須要配置 webpack,而後打包生成可以直接運行的項目。

咱們新建文件 webpack.config.js 進行配置,代碼實現以下:

// webpack.config.js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const WebpackChain = require("webpack-chain");

// 使用 webpack chain 組裝配置
const chain = new WebpackChain();

// 設置入口文件爲 src/index.js
chain.entry("main").add("./src/index.js").end();

// 將構建生成的文件輸出到 dist 目錄
chain.output.path(path.resolve(__dirname, "./dist")).end();

// 添加 html-webpack-plugin 插件,設置 HTML 模板文件
chain.plugin("html-webpack-plugin").use(HtmlWebpackPlugin, [{
  template: path.resolve(__dirname, "./index.html")
}]);

// 設置 source map 生成規則
chain.devtool("cheap-source-map").end();

// 將配置轉成 webpack 可識別的配置對象
const config = chain.toConfig();

module.exports = config;

從代碼實現中能夠看出,咱們的 webpack 配置只是簡單配置了入口出口和 html 模板文件。

那麼接下來,咱們須要配置 package.json,在 scripts 中添加啓動命令,代碼實現以下:

"scripts": {
  "build": "NODE_ENV=development webpack --config webpack.config.js && cd dist && anywhere"
}
anywhere 是一個快速啓動 http 服務的插件,可使用 npm i anywhere -g 進行全局安裝。

在配置完了之後,咱們須要運行下面的命令安裝一些依賴

npm i webpack webpack-cli webpack-chain html-webpack-plugin anywhere -D

安裝依賴後,咱們就能夠準備啓動項目啦!

運行項目

咱們運行 npm build 啓動項目編譯,命令行輸出將會是下面這樣(以下圖)

image

咱們的項目在被 webpack 打包後,輸出到了 dist 目錄,而且由 anywhere 運行了一個服務,在 8000 端口。

咱們打開瀏覽器,能夠看到下面這個頁面。(以下圖)

image

咱們打開控制檯,會看到咱們設置的對應輸出(以下圖)

image

頁面操做

咱們來進行一些頁面操做,咱們先點擊 Download 按鈕,會發現控制檯的輸出變成了下面這樣(以下圖)

image

從上圖能夠看出 download 方法被調用了,此時執行了一個下載操做(mock 操做)。

那咱們此時再點擊一下 Upload 按鈕,再觀察控制檯輸出(以下圖)

image

從控制檯能夠看到咱們的上傳操做被調用了,可是彷佛和下載操做沒什麼區別。

此時,咱們須要切換到 network 控制面板,查看網絡請求。(以下圖)

image

咱們從上圖能夠發現,在調用 upload 方法時,纔會加載 upload 方法對應的文件,從而實現懶加載。

這樣作的好處在於,能夠根據需求有效減少主包的體積,加快首屏渲染速度,減小服務器帶寬壓力。同時也減小了 JS 解析時間,提高頁面渲染速度。

懶加載的實現對前端來講,是性能優化專項必修課。能夠說,項目越複雜,那麼懶加載帶來的好處就越大。

實現分析

下面咱們能夠來看看 webpack 編譯後的代碼,看看 webpack 是如何實現懶加載的。

首先,咱們查看主包文件,也就是 dist/main.js。在這個文件裏找到咱們在 src/index.js 中實現的初始化頁面操做。(以下圖)

image

從上圖能夠看出,該操做直接被打包進了構建生成的 bundle 文件中。

Download 解析

那咱們再來看看 src/download.js 中實現的下載方法調用(以下圖)

image

從上圖能夠看出,handlerDownloadClick 最終調用了 _download__WEBPACK_IMPORTED_MODULE_0__.default 方法。

而這個 _download__WEBPACK_IMPORTED_MODULE_0__.default 是什麼呢?

在構建後的代碼中,找到了對這個對象的賦值操做(以下圖)

image

__webpack_require__ 函數,其實就是加載 __webpack_modules__ 中的對應模塊,這裏加載的對應模塊就是 "./src/download.js" 模塊。

最終,咱們在 dist/main.js 中,找到了對該模塊的定義。(以下圖)

image

從上圖能夠看出,對該模塊的定義,其實就是 src/download.js 的實現,被打包進了 dist/main.js 中,成爲了 __webpack_modules__ 對象的一部分。

Upload 解析

看完了 download 方法的打包實現,咱們接下來看看懶加載的 upload 是如何實現的?

咱們先找到 upload 對應的函數調用(以下圖)

image

從上圖能夠看出,當點擊上傳按鈕時,先使用 __webpack_require__.e 方法進行了一個加載操做,咱們來看看這個方法所作的事情。

該方法先拼接了這個模塊對應的絕對路徑(以下圖)

image

這個路徑的文件其實就是咱們打包後生成在 dist 目錄的文件(以下圖)

image

而後使用動態插入 script 標籤的方式,將對應的腳本文件插入到文檔中。(以下圖)

image

upload 對應的文件被插入後,將會自動執行。src_upload_js.js 腳本文件中將會執行 webpackJsonpCallback 方法,執行後將會在 windowwebpackChunklazyload 數組插入剛纔懶加載的模塊。(以下圖)

image

在執行該函數後,還會把 upload 模塊註冊在 __webpack_modules__ 中,後面的調用流程就和調用 download 同樣啦~(以下圖)

image

小結

從一個簡單的案例,咱們瞭解到了 webpack 的懶加載實現。

webpack 的懶加載實如今打包時會將懶加載的代碼切割出去單獨打包,而後在主包中進行按需加載,最後執行調用。

咱們最後用一張圖來梳理一下懶加載的加載執行過程。(以下圖)

image

最後一件事

若是您已經看到這裏了,但願您仍是點個贊再走吧~

您的點贊是對做者的最大鼓勵,也可讓更多人看到本篇文章!

若是以爲本文對您有幫助,請幫忙在 github 上點亮 star 鼓勵一下吧!

相關文章
相關標籤/搜索