Skip to content

Webpack

Webpack是一个现代JavaScript应用程序的静态模块打包工具,它通过分析应用程序的依赖关系,将各种资源(如JavaScript、CSS、图片等)打包成一个或多个静态资源文件[bundle],供浏览器加载使用。

核心概念

Webpack有五个核心概念:

1. 入口(Entry)

指定Webpack开始构建依赖图的起点文件,默认是./src/index.js。可以是单入口或多入口配置。

2. 输出(Output)

配置打包后文件的输出位置和命名规则,默认输出到./dist/main.js

3. 加载器(Loader)

Webpack本身只能处理JavaScript和JSON文件,Loader用于处理其他类型的文件,将其转换为有效模块。例如:

  • css-loader:处理CSS依赖关系,但不将样式应用到页面上
  • style-loader:将CSS插入到HTML的<style>标签中
  • babel-loader:将ES6+代码转换为ES5
  • file-loader:处理图片、字体等文件

4. 插件(Plugin)

用于执行更广泛的任务,如打包优化、资源管理、环境变量注入等。常用插件包括:

  • HtmlWebpackPlugin:生成HTML文件并自动引入打包资源
  • CleanWebpackPlugin:清理打包目录
  • MiniCssExtractPlugin:将CSS提取为独立文件

5. 模式(Mode)

设置构建模式:development(开发模式,注重调试)或production(生产模式,自动优化代码)。

工作原理

Webpack的构建流程分为三个阶段:

1. 初始化阶段

读取配置文件,创建Compiler对象,加载插件,注册生命周期钩子。

2. 编译阶段

从入口文件开始,递归分析依赖关系,构建依赖图。对每个模块使用对应的Loader进行转换,然后通过AST分析找出模块依赖。

3. 输出阶段

根据依赖关系将模块组装成Chunk,生成最终的打包文件。在生成过程中,插件可以在特定时机介入执行优化任务。

核心优势

  • 模块化支持:完美支持ES Modules、CommonJS、AMD等多种模块规范
  • 资源统一管理:将CSS、图片、字体等都作为模块引入
  • 代码分割:支持按需加载,优化首屏加载速度
  • Tree Shaking:自动移除未使用的代码,减小打包体积
  • 热模块替换(HMR):开发时无需刷新页面即可看到更新

基本配置示例

javascript
const path = require("node:path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const TerserPlugin = require("terser-webpack-plugin");
// const {BundleAnalyzerPlugin} = require("webpack-bundle-analyzer");
// const webpack = require("webpack")

module.exports = {
  // development: 开发模式,优化构建速度,不压缩代码,便于调试
  // production: 生产模式,会进行代码压缩等优化[1,8](@ref)
  mode: "development",
  entry: "./src/index.js",
  // 控制如何生成源代码映射(Source Map),用于调试转换压缩后的代码[2,8](@ref)
  // 'inline-source-map': 将Source Map作为DataURL内联在打包后的文件中,适合开发环境[1](@ref)
  devtool: "inline-source-map",
  devServer: {
    static: "./dist",
    port: 3001,
    // hot: true,
    open: true
  },
  output: {
    // filename: "[name].[contenthash:8].js",
    filename: "dist.js",
    path: path.resolve(__dirname, "dist")
  },
  resolve: {
    alias: {
      utils: path.resolve(__dirname, "src/utils"),
      "@": path.resolve(__dirname, "src")
    }
  },
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: ["style-loader", "css-loader"]
         // 注意:加载器从右向左执行,这里顺序不对会报错
      },
      {
        test: /\.(png|svg|jpg|jpeg|gif)$/i,
        type: "asset/resource" // webpack 5内置了资源模块类型,取代了file-loader的功能,
        generator: {
          filename: 'images/[name].[hash:8][ext]'
        }
      },
      {
        test: /\.js$/i,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader",
          options: {
            presets: ["@babel/preset-env"]
          }
        }
      }
    ]
  },
  optimization: {
    minimize: true,
    minimizer: [new TerserPlugin()],
  },
  plugins: [
    new HtmlWebpackPlugin({
      title: "博客列表"
    }),
    // new BundleAnalyzerPlugin() // 启用后会生成一个可视化报告,分析打包后各模块的体积
    // new webpack.HotModuleReplacementPlugin()
  ]
}

如何优化Webpack的构建速度

核心思路: 利用缓存减少工作量

具体实现:

1. 开启缓存:最为有效的一步,尤其是Webpack 5自带的持久化缓存(cache: { type: 'filesystem' }。生成的缓存文件在 node_modules/.cache/webpack 目录下),能让第二次及以后的构建速度飞升 。

2. 多进程处理:使用thread-loader为babel-loader这类耗时任务开启多进程并行处理,用多核性能换时间。

3. 缩小处理范围:通过exclude/include规则让loader避开node_modules等无需处理的目录,并配置resolve.alias减少模块查找时间。

4. 使用分析工具:用speed-measure-webpack-plugin分析构建速度,找出耗时的loader和插件,有针对性的优化。

Webpack 5之前的缓存方案

  • cache-loader:这是官方推荐的缓存方案,可以将上一个 loader 处理的结果缓存到磁盘上。配置方式是在耗时的 loader(如 babel-loader、vue-loader)前添加 cache-loader:
js
module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        use: ['cache-loader', 'babel-loader'],
        include: path.resolve('src')
      }
    ]
  }
}
  • hard-source-webpack-plugin:相比 cache-loader,这个插件可以缓存更多构建过程中的中间数据,包括模块、模块关系、模块解析结果、chunks、assets 等,效果几乎与 Webpack 5 自带的缓存对齐 。

  • DllPlugin & DllReferencePlugin:对不经常改变版本的第三方依赖(如 React、Vue、lodash)单独生成动态链接库,提高构建速度 。

  • 自带缓存的 loader:如 babel-loader、eslint-loader、eslint-webpack-plugin、stylelint-webpack-plugin 等都有内置的缓存功能 。

speed-measure-webpack-plugin 使用方法

安装:

bash
npm install --save-dev speed-measure-webpack-plugin
# 或
yarn add -D speed-measure-webpack-plugin

基本配置:

js
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");

const smp = new SpeedMeasurePlugin();

module.exports = smp.wrap({
  // 正常的 Webpack 配置
});

如何优化Webpack的打包体积

优化Webpack打包体积的核心思路是”减负“和”拆分”,目标是让用户浏览器尽可能少加载、快加载代码。具体策略如下:

1.分析打包体积

优化前,先试用webpack-bundle-analyzer分析打包结果,可视化查看哪些模块体积过大,从而有针对性地优化。

2.精简代码

  • 启用Tree Shaking: 通过mode: 'production'和optimization.usedExports: true配置,移除js中未使用的代码。确保使用ES6模块语法,因为Tree Shaking依赖于静态分析。并可在package.json中配置sideEffects字段来标记无副作用的文件。

  • 压缩代码: 使用 TerserWebpackPlugin压缩 JavaScript,使用 CssMinimizerPlugin压缩 CSS,有效减小文件体积。

  • 按需引入: 对于组件库(如 Antd、Element-UI),使用 babel-plugin-import等插件实现按需引入,避免打包整个库。

3.拆分代码与按需加载

  • 代码分割: 使用 SplitChunksPlugin将第三方库(如 React、Lodash)提取到单独的 vendorchunk 中,利用浏览器缓存。

  • 动态导入: 使用 import()语法实现路由级或组件级的懒加载,将非首屏代码拆分成独立的 chunk,在需要时再加载。

4.优化第三方库与资源

  • 替换或外置库:用更轻量的库替代庞大盘,如用 day.js代替 moment.js。或将稳定的大型库(如 Vue)通过 externals配置排除打包,转用 CDN 引入。

  • 压缩静态资源: 使用 image-webpack-loader等工具压缩图片。

与其他打包工具的比较

  • Webpack 功能强大,配置灵活但相对复杂。
  • Parcel 配置简单,上手快,性能也不错,但灵活性稍逊。
  • Rollup 更适合打包库,能生成更小、更高效的代码。

Webpack 适合大型复杂项目,Parcel 适合小型项目快速开发,Rollup 则在库开发方面有优势。

热模块替换HMR

当代码发生更改时,Webpack 会监测到变化,并尝试只更新发生改变的模块,而不是重新加载整个页面。

具体来说,Webpack 会在开发服务器和应用之间建立一个 WebSocket 连接。当模块更新时,Webpack 会通过这个连接将更新的模块信息发送到客户端。客户端接收到更新信息后,会尝试使用新的模块代码替换旧的模块代码,同时保持应用的当前状态,比如组件的实例、用户输入等不变。

在 Webpack 配置中启用热模块替换(HMR):

首先是 Webpack 配置(webpack.config.js)

js
const path = require('path');

module.exports = {
  //... 其他配置

  devServer: {
    hot: true,  // 启用 HMR
    contentBase: path.resolve(__dirname, 'dist'),  // 静态文件的根目录
    port: 8080  // 服务端口
  },

  //... 其他配置
};

然后,在模块代码中(例如一个简单的 JavaScript 文件)添加对 HMR 的支持:

js
if (module.hot) {
  module.hot.accept();  // 接受模块的更新
}

module.hot 是 Webpack 在运行时注入到模块中的对象,用于支持热模块替换(HMR)的相关操作。