一文搞懂 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 鼓励一下吧!

相关文章
相关标签/搜索