Skip to content

webpack的开发环境调优

Webpack 开发效率插件

Webpack 拥有非常强大的生态系统,社区中相关的工具也是数不胜数。这里我们介绍几个使用较广的插件,可以从不同的方面对 Webpack 的能力进行增强。

  • webpack-dashboard: Webpack 每一次构建结束后都会在控制台输出一些打包相关的信息,但是这些信息是以列表的形式展示的,有时会显得不够直观。webpack-dashboard 就是用来更好地展示这些信息的。
  • webpack-merge: 对于需要配置多种打包环境的项目来说,webpack-merge 是一个非常实用的工具。假设我们的项目有 3 种不同的配置,分别对应本地环境、测试环境和生产环境。每一个环境对应的配置都不同,但也有一些公共的部分,那么我们就可以将这些公共的部分提取出来。
  • speed-measure-webpack-plugin: 觉得 Webpack 构建很慢但又不清楚如何下手优化吗?那么可以试试 speed-measure-webpack-plugin 这个插件(简称 SMP)。SMP 可以分析出 Webpack 整个打包过程中在各个 loader 和 plugin 上耗费的时间,这将会有助于找出构建过程中的性能瓶颈。
  • size-plugin: 一般而言,随着项目的开发,产出的资源会越来越大,最终生成的资源会逐渐变得臃肿起来。size-plugin 这个插件可以帮助我们监控资源体积的变化,尽早地发现问题。

模块热替换

在早期开发工具还比较简单和匮乏的年代,调试代码的方式基本都是改代码—刷新网页查看结果—再改代码,这样反复地修改和测试。后来,一些 Web 开发框架和工具提供了更便捷的方式——只要检测到代码改动就会自动重新构建,然后触发网页刷新。这种一般被称为 live reload。Webpack 则在 live reload 的基础上又进了一步,可以让代码在网页不刷新的前提下得到最新的改动,我们甚至不需要重新发起请求就能看到更新后的效果。这就是模块热替换功能(Hot Module Replacement,HMR)。

HMR 对于大型应用尤其适用。试想一个复杂的系统每改动一个地方都要经历资源重构建、网络请求、浏览器渲染等过程,怎么也要几秒甚至几十秒的时间才能完成;况且我们调试的页面可能位于很深的层级,每次还要通过一些人为操作才能验证结果,其效率是非常低下的。而 HMR 则可以在保留页面当前状态的前提下呈现出最新的改动,可以节省开发者大量的时间成本。

开启 HMR

HMR 是需要手动开启的,并且有一些必要条件。

首先我们要确保项目是基于 webpack-dev-server 或者 webpack-dev-middle 进行开发的,Webpack 本身的命令行并不支持 HMR。下面是一个使用 webpack-dev-server 开启 HMR 的例子。

js
const webpack = require("webpack");
module.exports = {
  // ...
  plugins: [new webpack.HotModuleReplacementPlugin()],
  devServer: {
    hot: true,
  },
};

上面配置产生的结果是 Webpack 会为每个模块绑定一个 module.hot 对象,这个对象包含了 HMR 的 API。借助这些 API 我们不仅可以实现对特定模块开启或关闭 HMR,也可以添加热替换之外的逻辑。比如,当得知应用中某个模块更新了,为了保证更新后的代码能够正常工作,我们可能还要添加一些额外的处理。

调用 HMR API 有两种方式,一种是手动地添加这部分代码;另一种是借助一些现成的工具,比如 react-hot-loader、vue-loader 等。

如果应用的逻辑比较简单,我们可以直接手动添加代码来开启 HMR。比如下面这个例子:

js
// index.js
import { add } from "util.js";
add(2, 3);

if (module.hot) {
  module.hot.accept();
}

假设 index.js 是应用的入口,那么我们就可以把调用 HMR API 的代码放在该入口中,这样 HMR 对于 index.js 和其依赖的所有模块都会生效。当发现有模块发生变动时,HMR 会使应用在当前浏览器环境下重新执行一遍 index.js(包括其依赖)的内容,但是页面本身不会刷新。

大多数时候,还是建议应用的开发者使用第三方提供的 HMR 解决方案,因为 HMR 触发过程中可能会有很多预想不到的问题,导致模块更新后应用的表现和正常加载的表现不一致。为了解决这类问题,Webpack 社区中已经有许多相应的工具提供了解决方案。比如 react 组件的热更新由 react-hot-loader 来处理,我们直接拿来用就行。

HMR 原理

在开启 HMR 的状态下进行开发,你会发现资源的体积会比原本的大很多,这是因为 Webpack 为了实现 HMR 而注入了很多相关代码。在它的实现过程里也包含了很多有意思的问题,下面我们来详细介绍一下 HMR 的工作原理。

在本地开发环境下,浏览器是客户端,webpack-dev-server(WDS)相当于是我们的服务端。HMR 的核心就是客户端从服务端拉取更新后的资源(准确地说,HMR 拉取的不是整个资源文件,而是 chunk diff,即 chunk 需要更新的部分)。

第 1 步就是浏览器什么时候去拉取这些更新。这需要 WDS 对本地源文件进行监听。实际上 WDS 与浏览器之间维护了一个 websocket,当本地资源发生变化时 WDS 会向浏览器推送更新事件,并带上这次构建的 hash,让客户端与上一次资源进行比对。通过 hash 的比对可以防止冗余更新的出现。因为很多时候源文件的更改并不一定代表构建结果的更改(如添加了一个文件末尾空行等)。

这同时也解释了为什么当我们开启多个本地页面时,代码一改所有页面都会更新。当然 webscoket 并不是只有开启了 HMR 才会有,live reload 其实也是依赖这个而实现的。

有了恰当的拉取资源的时机,下一步就是要知道拉取什么。这部分信息并没有包含在刚刚的 websocket 中,因为刚刚我们只是想知道这次构建的结果是不是和上次一样。现在客户端已经知道新的构建结果和当前的有了差别,就会向 WDS 发起一个请求来获取更改文件的列表,即哪些模块有了改动。通常这个请求的名字为[hash].hot-update.json

该返回结果告诉客户端,需要更新的 chunk 为 main,版本为(构建 hash)e388ea0f 0e0054e37cee。这样客户端就可以再借助这些信息继续向 WDS 获取该 chunk 的增量更新。

现在客户端已经获取到了 chunk 的更新,到这里又遇到了一个非常重要的问题,即客户端获取到这些增量更新之后如何处理?哪些状态需要保留,哪些又需要更新?这个就不属于 Webpack 的工作了,但是它提供了相关的 API(如前面我们提到的 module.hot.accept),开发者可以使用这些 API 针对自身场景进行处理。像 react-hot-loader 和 vue-loader 也都是借助这些 API 来实现的 HMR。

备案号:闽ICP备2024028309号-1