前端面经-webpack篇--定义、配置、构建流程、 Loader、Tree Shaking、懒加载与预加载、代码分割、 Plugin 机制

看完本篇你将基本了解webpack!!!

目录

[一、Webpack 的作用](#一、Webpack 的作用)

1、基本配置结构

2、配置项详解

[1. entry ------ 构建入口](#1. entry —— 构建入口)

[2. output ------ 输出配置](#2. output —— 输出配置)

[3. mode:模式设置](#3. mode:模式设置)

[4. module:模块规则](#4. module:模块规则)

[5. plugins:插件机制](#5. plugins:插件机制)

[6. resolve:模块解析配置(可选)](#6. resolve:模块解析配置(可选))

[7. devServer:开发服务器(可选)](#7. devServer:开发服务器(可选))

[8. devtool:调试工具(可选)](#8. devtool:调试工具(可选))

[9. target:目标平台(可选)](#9. target:目标平台(可选))

二、构建流程

[1. 初始化(Initialization)](#1. 初始化(Initialization))

[1. 读取配置文件](#1. 读取配置文件)

[2. 初始化 Compiler 对象](#2. 初始化 Compiler 对象)

[3. 注册所有插件(Plugin 注册阶段)](#3. 注册所有插件(Plugin 注册阶段))

[2. 构建模块图(Build Dependency Graph)](#2. 构建模块图(Build Dependency Graph))

[3. 模块转换与解析](#3. 模块转换与解析)

[示例 1:JS 文件(可能含 ES6、TS)](#示例 1:JS 文件(可能含 ES6、TS))

[示例 2:CSS 文件](#示例 2:CSS 文件)

[示例 3:图片文件](#示例 3:图片文件)

[4. 生成代码块(Chunk)与文件(Asset)](#4. 生成代码块(Chunk)与文件(Asset))

[1、 Chunk(代码块)](#1、 Chunk(代码块))

[常见的 Chunk 类型:](#常见的 Chunk 类型:)

[🔁 举个例子](#🔁 举个例子)

[为什么要把代码分成多个 chunk?](#为什么要把代码分成多个 chunk?)

[1. 性能优化:](#1. 性能优化:)

[2. 缓存优化:](#2. 缓存优化:)

2、Asset(最终产出资源文件)

[3. Chunk 转换为 Asset( bundle)](#3. Chunk 转换为 Asset( bundle))

[4. 输出 Asset 到 output.path](#4. 输出 Asset 到 output.path)

[5. 输出阶段(Emit)](#5. 输出阶段(Emit))

三、插件机制

[1、插件"注册"发生在 ------ ✅ 初始化阶段(Initialization)](#1、插件“注册”发生在 —— ✅ 初始化阶段(Initialization))

[2、插件"执行"发生在 ------ ✅ 构建过程的每一个阶段(Build Lifecycle)](#2、插件“执行”发生在 —— ✅ 构建过程的每一个阶段(Build Lifecycle))

3、compilation

[1. 模块的管理](#1. 模块的管理)

[2. 构建 Chunk](#2. 构建 Chunk)

[3. 生成 Asset(输出资源)](#3. 生成 Asset(输出资源))

[4. Plugin 的生命周期钩子](#4. Plugin 的生命周期钩子)

四、配置项之间的相互关系

[1. entry 与 module.rules](#1. entry 与 module.rules)

[2. entry 与 output](#2. entry 与 output)

[3. module.rules 与 plugins](#3. module.rules 与 plugins)

[4. mode 与其他所有配置项](#4. mode 与其他所有配置项)

[5. devServer 与 output](#5. devServer 与 output)

[6. plugins 与 output](#6. plugins 与 output)

[五、 Loader](#五、 Loader)

[1、什么是 Loader?](#1、什么是 Loader?)

[2、为什么需要 Loader?](#2、为什么需要 Loader?)

[3、常见 Loader 类型](#3、常见 Loader 类型)

[1、 什么是 babel-loader?](#1、 什么是 babel-loader?)

[2、你为什么需要 babel-loader?](#2、你为什么需要 babel-loader?)

3、核心概念说明

4、常见用途

[4、Webpack 如何调用 Loader?](#4、Webpack 如何调用 Loader?)

六、构建优化与高级功能

[1. 代码分割(Code Splitting)](#1. 代码分割(Code Splitting))

[2. Tree Shaking](#2. Tree Shaking)

[Tree Shaking 的基本原理](#Tree Shaking 的基本原理)

[3. 懒加载(Lazy Loading)与预加载](#3. 懒加载(Lazy Loading)与预加载)

[1、什么是懒加载(Lazy Loading)?](#1、什么是懒加载(Lazy Loading)?)

[Webpack 如何实现懒加载?](#Webpack 如何实现懒加载?)

2、什么是预加载(Preload)和预取(Prefetch)?

[1. webpackPrefetch: true → 浏览器空闲时加载](#1. webpackPrefetch: true → 浏览器空闲时加载)

[2. webpackPreload: true → 当前帧就加载](#2. webpackPreload: true → 当前帧就加载)


一、Webpack 的作用

Webpack 的主要作用是:

  • 模块打包:将多个模块(JS、CSS、图片等)打包成一个或多个文件。

  • 代码拆分(Code Splitting):根据需要拆分代码,提高首屏加载速度。

  • 资源处理:处理 JS 之外的资源,如 CSS、LESS、SASS、图片、字体等。

  • 开发工具支持:提供开发服务器、热更新(HMR)、调试等功能。

  • 优化性能:压缩代码、Tree Shaking(去除无用代码)、懒加载等。

1、基本配置结构

复制代码
// webpack.config.js
const path = require('path');

module.exports = {
  entry: './src/index.js',            // 入口
  output: {                           // 输出
    filename: 'bundle.js',            // 输出文件名
    path: path.resolve(__dirname, 'dist') // 输出目录(必须是绝对路径)
  },
  module: {                           // 模块处理规则
    rules: [
      {
        test: /\.css$/,               // 正则匹配 .css 文件
        use: ['style-loader', 'css-loader'] // 使用的 loader,从右向左执行
      }
    ]
  },
  plugins: [],                        // 插件列表
  mode: 'development'                 // 构建模式:development | production
};

在了解 Webpack 原理前,我们需要先了解几个核心名词的概念:

  • 入口(Entry):构建的起点 。Webpack 从这里开始执行构建。通过 Entry 配置能够确定哪个文件作为构建过程的开始,进而识别出应用程序的依赖图谱

  • 模块(Module):构成应用的单元 。在 Webpack 的视角中,一切文件皆可视为模块,包括 JavaScript、CSS、图片或者是其他类型的文件。Webpack 从 Entry 出发,递归地构建出一个包含所有依赖文件的模块网络。

  • 代码块(Chunk):代码的集合体。Chunk 由模块合并而成,被用来优化输出文件的结构。Chunk 使得 Webpack 能够更灵活地组织和分割代码,支持代码的懒加载、拆分等高级功能。

  • 加载器(Loader):模块的转换器。Loader 让 Webpack 有能力去处理那些非 JavaScript 文件(Webpack 本身只理解 JavaScript)。通过 Loader,各种资源文件可以被转换为 Webpack 能够处理的模块,如将 CSS 转换为 JS 模块,或者将高版本的 JavaScript 转换为兼容性更好的形式(降级)。

  • 插件(Plugin):构建流程的参与者。Webpack 的构建流程中存在众多的事件钩子(hooks),Plugin 可以监听这些事件的触发,在触发时加入自定义的构建行为,如自动压缩打包后的文件、生成应用所需的 HTML 文件等。

2、配置项详解

1. entry ------ 构建入口

指定 Webpack 构建的起点,支持多入口配置。

entry: './src/index.js'

作用:指定 Webpack 构建的起点文件,从这个文件出发递归分析所有依赖。

  • 默认值是 './src/index.js'

  • 支持单入口(字符串)和多入口(对象形式)。

多入口示例:

entry: {

app: './src/app.js',

admin: './src/admin.js'

}

🔁 Webpack 会为每个入口分别打包生成输出文件。

2. output ------ 输出配置

控制打包后的文件名称和路径。

作用:指定打包后文件的存储位置和命名规则。

  • filename:输出文件名,可包含占位符(如 [name], [contenthash]

  • path:输出目录,必须是绝对路径

output: {

filename: 'bundle.js', // 输出的文件名

path: path.resolve(__dirname, 'dist') // 绝对路径

}

动态命名(用于多入口):filename: '[name].[contenthash].js'

3. mode:模式设置

mode: 'development' // 或 'production'

作用:指定打包模式,Webpack 会自动启用对应的优化。

  • development

    • 开启调试(source map)

    • 不压缩代码

    • 提高构建速度

  • production

    • 自动压缩 JavaScript/CSS

    • 启用 Tree Shaking(移除未使用代码)

    • 更小体积、适合上线

4. module:模块规则

module: {

rules: [...]

}

作用 :定义对不同模块(如 CSS、JS、图片等)的处理规则,核心由 rules 数组组成。

每条规则格式如下:

{

test: /\.css$/, // 正则匹配需要处理的文件类型

use: ['style-loader', 'css-loader'] // 使用的 loader(从右到左执行)

}

📌 常见 Loader:

文件类型 Loader 示例
JS babel-loader
CSS css-loaderstyle-loader
图片 file-loaderurl-loader
字体 file-loader
Vue vue-loader

5. plugins:插件机制

复制代码
plugins: [
  new HtmlWebpackPlugin({
    template: './src/index.html'
  })
]

作用:扩展 Webpack 的功能,用于执行更复杂的构建任务。

📌 常用插件及作用:

插件名 作用
HtmlWebpackPlugin 自动生成 HTML 文件并注入打包资源
CleanWebpackPlugin 构建前自动清空输出目录
MiniCssExtractPlugin 提取 CSS 到单独文件
DefinePlugin 定义环境变量

6. resolve:模块解析配置(可选)

复制代码
resolve: {
  extensions: ['.js', '.jsx', '.json']
}

作用:指定在导入模块时可省略的文件扩展名,提高模块查找效率。

📌 例如:import App from './App'

// 实际查找:./App.js -> ./App.jsx -> ./App.json

7. devServer:开发服务器(可选)

复制代码
devServer: {
  static: './dist',
  port: 3000,
  hot: true
}

作用:启动本地开发服务器,支持热更新(HMR),提升开发效率。

HMR(Hot Module Replacement)是 Webpack 在开发环境下的一种"热更新"功能,允许你在不刷新页面的情况下替换、更新模块的内容。

配置说明:

  • static: 提供静态文件目录

  • port: 设置访问端口

  • hot: 启用热更新功能(无需刷新页面即可应用更改)

8. devtool:调试工具(可选)

复制代码
devtool: 'source-map'

作用:生成 source map,帮助调试代码时映射到源码位置。

常用选项:

  • source-map:完整 source map,最详细(用于生产)

  • eval-source-map:快且可调试(用于开发)

  • none:关闭 source map

9. target:目标平台(可选)

作用:指定构建目标环境(浏览器 / Node.js)

复制代码
target: 'web' // 或 'node'

二、构建流程

1. 初始化(Initialization)

  • 读取配置文件(webpack.config.js

  • 初始化 Compiler(核心对象)

  • 注册所有 plugin,进入生命周期钩子(基于 Tapable)

在 Webpack 中,存在两个非常重要的核心对象:compilercompilation,它们的作用如下:

  • Compiler:Webpack 的核心,贯穿于整个构建周期。Compiler 封装了 Webpack 环境的全局配置,包括但不限于配置信息、输出路径等。
  • Compilation:表示单次的构建过程及其产出。与 Compiler 不同,Compilation 对象在每次构建中都是新创建的,描述了构建的具体过程,包括模块资源、编译后的产出资源、文件的变化,以及依赖关系的状态。在watch mode 下,每当文件变化触发重新构建时,都会生成一个新的 Compilation 实例。

Compiler 是一个长期存在的环境描述,贯穿整个 Webpack 运行周期;而 Compilation 是对单次构建的具体描述,每一次构建过程都可能有所不同。

1. 读取配置文件

Webpack 启动时会查找配置文件(默认是 webpack.config.js),并加载它。

支持的文件格式包括:

  • JavaScript (webpack.config.js)

  • TypeScript (webpack.config.ts)

  • JSON(部分字段)

Webpack 会执行配置文件的内容,读取其中的:

  • entry, output

  • module.rules

  • plugins

  • mode, devtool, resolve, 等等

📌 注意 :Webpack 本质上会执行 require('./webpack.config.js'),所以你甚至可以在里面写 JS 逻辑(例如根据环境动态返回不同配置)。

2. 初始化 Compiler 对象

Webpack 内部会构造出一个核心对象: Compiler:const compiler = new Compiler(options);

这个对象是整个构建系统的"大脑",包含:

  • 所有用户配置

  • 所有构建状态

  • 所有钩子(事件系统)

  • 所有模块、chunk、asset 的数据结构

📌 Compiler 不是随便一个类,它继承了 Tapable 类,内置了很多"生命周期钩子"。

例如:

复制代码
compiler.hooks.run.tap(...)
compiler.hooks.emit.tap(...)
compiler.hooks.done.tap(...)

3. 注册所有插件(Plugin 注册阶段)

Webpack 执行 plugins 数组中每一个插件的 .apply() 方法,把插件"挂载"到 compiler 上。

复制代码
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  plugins: [
    new HtmlWebpackPlugin({ template: './index.html' })
  ]
}

HtmlWebpackPlugin 内部会执行:

复制代码
apply(compiler) {
  compiler.hooks.emit.tap('HtmlWebpackPlugin', compilation => {
    // 插件逻辑
  });
}

📌 这相当于在构建"还没开始前",就准备好了插件的监听事件,让插件在适当时机介入构建流程。

初始化阶段的内部机制:Tapable 系统

Webpack 的插件机制基于一个库:Tapable

你可以把它想象成"事件发布/订阅系统":

  • Webpack 生命周期中各个阶段都会触发钩子

  • 插件会提前通过 .tap() 注册函数

  • 等构建进行到某一阶段时,就会执行对应钩子中的插件逻辑

常见的 Hook:

Hook 名称 触发时机
run 构建开始时
compile 依赖图构建前
compilation 创建 Compilation 对象时
emit 输出资源前
done 构建结束时

初始化阶段后的准备结果是什么?

  • 所有配置信息都已解析

  • 所有插件都已注册到合适的生命周期钩子

  • 构建状态对象 compiler 构建完成

➡️ 现在,只等下一步开始正式构建了(构建依赖图、转换模块、生成 chunk)。

2. 构建模块图(Build Dependency Graph)

entry 开始递归分析:

  1. 读取入口文件

  2. 识别导入的模块(import / require

  3. 通过 module.rules 选择合适的 loader 进行转换

  4. 继续递归读取依赖,直到没有新依赖为止

  5. 建立一个 模块依赖图(Module Graph)

模块图是一个 DAG(有向无环图),表示模块间的引用关系。

假设你有这样的文件结构:

复制代码
src/
├── index.js
├── utils.js
├── style.css
└── logo.png

index.js 内容如下:

复制代码
import './style.css';
import { add } from './utils.js';
import logo from './logo.png';

utils.js 内容:

复制代码
export function add(a, b) {
  return a + b;
}

Webpack 会生成的依赖图如下:

复制代码
            [index.js]
           /    |     \
          ↓     ↓      ↓
    [style.css][utils.js][logo.png]
  • index.js 是入口

  • 它依赖了三个模块:

    • style.css → 由 css-loaderstyle-loader 处理

    • utils.js → 正常 JS 模块

    • logo.png → 被 file-loaderurl-loader 转换成链接

这个图就是模块依赖图。Webpack 会根据它打包时决定文件的组织顺序、是否合并、是否拆分、是否优化等。

如果你希望看到你的项目真实的模块依赖图 ,可以使用以下工具:webpack-bundle-analyzer

复制代码
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
plugins: [
  new BundleAnalyzerPlugin()
]

它会生成一个交互式网页,展示模块结构图,包括文件大小、依赖关系等。

3. 模块转换与解析

  • JS 文件 → Babel 转译

  • CSS 文件 → CSS loader 转换为 JS 模块

  • 图片 → file-loader / url-loader 生成可导入的 URL

  • 每一个模块最终都会被转换为 JS 函数或对象

Webpack 会在"构建模块图"的同时,对每个模块立即进行"模块解析与转换" ,这是一个"边走边处理"的过程。结合第二阶段

  1. 入口 index.js

  2. 识别其依赖 ['./a.js', './style.css']

  3. 解析 index.js → 传给 babel-loader 转换

  4. 进入 a.js,找依赖并转换

  5. 进入 style.css → css-loader → style-loader

  6. 构建出模块图 + 得到转换后的模块内容

所以当 Webpack 找到一个模块之后(如 style.cssimage.pngindex.jsx),它还不能直接打包进 JS。Webpack 必须: 用 Loader 转换为 JS 模块格式

这一步是真正"处理文件内容"的阶段。

示例 1:JS 文件(可能含 ES6、TS)
复制代码
// 原始代码
const fn = () => console.log('hi');
// 经过 babel-loader 转换为:
var fn = function() { return console.log("hi"); }
示例 2:CSS 文件
复制代码
body { color: red; }

/* css-loader 转换为 */
module.exports = "body { color: red; }";

/* style-loader 再把这段 CSS 插入到 <style> 标签中 */
示例 3:图片文件
复制代码
import img from './logo.png';

/* file-loader 转换为 */
module.exports = "https://cdn.xxx.com/assets/logo.png";

📌 Loader 负责把"非 JS 文件"变成 JS 模块,或者把 JS 新语法变旧语法。

4. 生成代码块(Chunk)与文件(Asset)

  • Chunk:是 Webpack 内部构建产物,对应一组模块集合(可用于代码分割)。

  • Asset :是最终生成的文件,例如 bundle.jsmain.css、图片等。

1、 Chunk(代码块)

  • Chunk 是 Webpack 构建产物的逻辑单元,代表一个或一组模块的集合。

  • 每个入口文件、异步模块、被共享的模块都会生成一个 chunk。

  • Chunk 本质上是"模块打包中间结果",最终会转化为一个或多个 Asset 文件。

常见的 Chunk 类型:
类型 示例
Entry Chunk 入口文件构建的 chunk(如 main.js
Lazy Chunk 动态导入的模块(如 import('./modal')
Vendor Chunk 被多个入口共享的第三方库(如 react, lodash
Runtime Chunk Webpack 运行时代码(如 webpackRuntime.js

Chunk 是模块的集合,这些模块有"某种关系",所以被打成一块儿,生成一个 JS 文件(或 CSS 文件)。

想象你在打包搬家,有很多小物品(模块),你不可能一个一个带,而是:

  • 把相关的东西打进一个箱子(chunk)

  • 每个箱子贴一个标签(chunk 名字)

  • 最后封箱、打包(生成 asset 文件)

Webpack 就是在打包项目的模块,它根据**"模块之间的引用关系"**来决定哪些模块一起放进同一个箱子(chunk),再把这些箱子输出成 .js.css 文件等。

🔁 举个例子
复制代码
src/
├── index.js          // 入口
├── a.js
├── b.js
└── c.js

index.js

复制代码
import './a.js';
import('./b.js'); // 动态导入

a.js

复制代码
console.log('a');

b.js

复制代码
import './c.js';
console.log('b');

Webpack 打包时会怎么处理?

index.js 是入口 → 它会生成一个 入口 Chunk,我们叫它 main

它包含:(因为 a.js 是同步导入的)

main chunk:

  • index.js

  • a.js

b.js 是动态导入(import()) → 它会生成一个 懒加载 Chunk,单独打包。

chunk-b:

  • b.js

  • c.js

(因为 b.js 和 c.js 是一起的、在被点击时一起加载)

🎯 打包后会生成哪些文件(Asset)?

最终输出:

/dist

├── main.[hash].js ← 入口 chunk,对应 index + a

├── chunk-b.[hash].js ← 动态 chunk,对应 b + c

为什么要把代码分成多个 chunk?
1. 性能优化:
  • 首页加载只加载必要模块(index + a)

  • 用户点击功能时,再懒加载(b + c)

2. 缓存优化:
  • 不同 chunk 对应不同 hash 名字

  • 修改 b.js 不会影响 main.js 的缓存

2、Asset(最终产出资源文件)

  • Asset 是 Webpack 输出到磁盘的文件:如 bundle.jsmain.csslogo.png

  • 它是 Chunk 的最终形态(经过 loader/plugin 处理后的文件结果)

模块依赖图(谁依赖谁)

每个模块用 loader 转换

模块被分组为 chunk(按策略)

chunk 转换为 asset(最终输出文件)

Webpack 如何生成 Chunk 与 Asset?

根据 Module Graph 拆分 Chunk

  • Webpack 会遍历整个模块依赖图,确定哪些模块属于哪个 chunk。

  • 应用分包策略(SplitChunksPlugin、动态导入)来进行 chunk 拆分。

  • 一般每个入口或异步模块会生成一个 chunk。

📌 你动态导入模块时,Webpack 会为其生成一个单独的 chunk 文件(实现懒加载)。

3. Chunk 转换为 Asset( bundle**)**

  • Chunk 只是"逻辑结构",Webpack 需要将它们转换为 JS/CSS/资源文件(Asset)。

  • 通过插件(如 MiniCssExtractPlugin)或自身模板系统,Webpack 将:

    • 各 chunk 合并成字符串内容

    • 输出 JS 文件(如 main.[hash].js

    • 输出 CSS 文件(如 style.[contenthash].css

4. 输出 Asset 到 output.path

最终所有 Asset 文件写入硬盘或开发服务器的内存:

复制代码
output: {
  filename: '[name].[contenthash].js',
  path: path.resolve(__dirname, 'dist')
}

5. 输出阶段(Emit)

  • 所有的 Asset 文件写入到配置的 output.path 目录中

  • 插件(如 HtmlWebpackPlugin)可在此阶段修改/生成资源

我们了解了webpack的构建过程以后,我们还要了解一下webpack的插件机制

三、插件机制

Webpack 的插件机制贯穿整个构建生命周期,但它的"注册"和"激活"分别发生在不同的阶段。

1、插件"注册"发生在 ------ ✅ 初始化阶段(Initialization)

Webpack 会读取 webpack.config.js 里的 plugins 数组,并对每一个插件执行:

复制代码
plugin.apply(compiler);

也就是说,每个插件通过 .apply(compiler) 方法注册到 生命周期钩子上(Hooks)

🔧 这些钩子都挂在 Webpack 的 CompilerCompilation 对象上,由 Tapable 提供事件发布订阅机制。

📌 所以,"插件机制"的准备阶段就在初始化阶段完成了。

2、插件"执行"发生在 ------ ✅ 构建过程的每一个阶段(Build Lifecycle)

注册只是开始,真正让插件起作用,是当 Webpack 构建流程进入具体某个阶段时,会调用对应的钩子,插件就"被唤醒"并执行它的逻辑。

插件发挥作用的典型阶段有:

构建阶段 生命周期钩子(hook) 插件可能做的事情
构建启动 compiler.hooks.run 打印开始标志
编译模块图前 compiler.hooks.beforeCompile 注入额外依赖
创建 Compilation compiler.hooks.compilation 操作模块处理逻辑
模块生成完毕 compilation.hooks.buildModule 修改模块源码
资源输出前 compilation.hooks.emit 添加/修改生成文件
构建结束 compiler.hooks.done 输出统计信息或清理

所以插件机制在构建期间是事件驱动的,根据构建"走到哪一步"触发哪个插件

示例:HtmlWebpackPlugin 插件如何接入机制?

复制代码
plugins: [
  new HtmlWebpackPlugin({ template: './index.html' })
]

这个插件会:

  1. compiler.hooks.thisCompilation 注册事件

  2. compilation.hooks.processAssets 阶段,插入一个生成的 HTML 文件作为 asset

所以它注册在初始化阶段,被调用在资源生成阶段(emit 前)。

compilation 是 Webpack 每次构建时用来 处理模块、生成 chunk、输出文件(asset) 的对象,它代表了一次"构建资源的具体过程"。

compiler 的关系

对象 描述
compiler 全局构建对象,贯穿整个构建生命周期(只创建一次)
compilation 表示一次"编译过程",可能有多次(如监听模式中每次变化都会重新创建)

📌 换句话说:

  • compiler 是构建管理者(总控)

  • compilation 是实际干活的工人(负责模块、chunk、asset 的生成)

3、compilation

compilation 主要包含什么功能?

Webpack 会使用 compilation 做以下事情:

1. 模块的管理

  • 所有模块都被封装为 Module 对象

  • 保存在 compilation.modules

2. 构建 Chunk

  • 将模块分组为 chunk(如入口 chunk、异步 chunk)

  • 保存在 compilation.chunks

3. 生成 Asset(输出资源)

  • 将 chunk 转换为最终要输出的 JS/CSS/Image 等文件

  • 保存在 compilation.assets 中(是一个对象,键是文件名,值是文件内容)

4. Plugin 的生命周期钩子

compilation 自己也有一套 hooks:

Hook 名称 描述
buildModule 构建每个模块时触发
optimizeChunks 生成 chunk 后优化时触发
processAssets(Webpack 5) 生成最终资源前触发
seal 准备打包所有模块和资源时触发

四、配置项之间的相互关系

了解了构建流程以后 我们看看配置项之间的相互关系

1. entrymodule.rules

  • 关系 :Webpack 从 entry 开始构建依赖图,遇到各种类型的文件(.js, .css, .png)时,会查找 module.rules 中的匹配规则。

  • 实际效果 :比如你在 index.js 中引入了 style.css,Webpack 会用 css-loaderstyle-loader 来处理它。

2. entryoutput

  • 关系entry 决定了打包的起点;output 决定了打包的终点(文件名和路径)。

  • 实际效果 :如果有多个入口,output.filename 应使用占位符 [name].js 来分别生成文件。

3. module.rulesplugins

  • 关系rules 是资源的预处理;plugins 则是在整个构建过程中插入逻辑或修改结果。

  • 实际效果 :比如通过 css-loader 把 CSS 模块化,然后使用 MiniCssExtractPlugin 把它提取为独立文件。

    module: {
    rules: [
    { test: /.css$/, use: [MiniCssExtractPlugin.loader, 'css-loader'] }
    ]
    },
    plugins: [
    new MiniCssExtractPlugin({ filename: '[name].css' })
    ]

4. mode 与其他所有配置项

  • 关系mode 是全局行为开关,会影响:

    • 是否压缩代码(output)

    • 是否启用 Tree Shaking(module)

    • 是否显示 SourceMap(devtool)

    • 插件行为(如优化)

  • mode'production' 时,默认启用 TerserPlugin 压缩 JS 和 CssMinimizerPlugin 压缩 CSS。

5. devServeroutput

  • 关系devServer 不真正写入 output.path,而是在内存中生成并托管输出文件。

  • 实际效果:更快的热更新(HMR),页面自动刷新。

6. pluginsoutput

  • 比如 HtmlWebpackPlugin 插件会在 output 中自动注入打包后的文件路径和名字(如 <script src="bundle.js">)。

  • 文件名如果包含 hash,插件会自动处理。

五、 Loader

1、什么是 Loader?

Loader 是 Webpack 的模块转换器,用于将各种非 JavaScript 文件转换为 Webpack 可以理解的模块(JavaScript 模块)。

2、为什么需要 Loader?

Webpack 默认只懂 .js.json,但项目中常常使用:

  • .css.scss

  • .ts.vue

  • 图片 .png、字体 .woff

Webpack 无法直接理解这些文件,所以我们用 Loader 来预处理它们 → 转换成 JS 模块

Loader 的本质:是一个函数

复制代码
module.exports = function(source) {
  // source 是文件原始内容(字符串)
  return transformedCode;
}

Webpack 读取文件 → 调用 Loader → 得到转换后的 JS 模块

Loader 的执行过程(串行、链式)

多个 Loader 组合时,从 右到左(从下到上) 顺序执行:

module: {

rules: [

{

test: /\.css$/,

use: ['style-loader', 'css-loader']

}

]

}

执行顺序是:css-loader → style-loader=

分工:

  • css-loader:把 CSS 转成 JS 字符串模块

  • style-loader:把字符串插入到页面 <style> 标签中

3、常见 Loader 类型

类型 Loader 名称 功能
JS 转译 babel-loader 转换 ES6/TS 等
样式处理 css-loader, style-loader, sass-loader 支持 CSS/SASS
文件资源 file-loader, url-loader, asset modules(Webpack 5) 处理图片/字体
Vue/React vue-loader, @svgr/webpack 支持框架组件文件
Markdown markdown-loader 支持 md 文件转 HTML
1、 什么是 babel-loader

babel-loader 是 Webpack 和 Babel 的桥梁,让 Webpack 可以在打包时调用 Babel 进行代码转译 。它本身并不执行语法转换,而是将文件传递给 Babel,Babel 再通过配置(如 .babelrc)完成真正的转换。

2、你为什么需要 babel-loader

现代前端项目通常使用:

  • ES6+ 新语法(如箭头函数、类、模块)

  • JSX(React)

  • TypeScript(结合 @babel/preset-typescript

这些语法不是所有浏览器都支持(尤其是 IE / 老旧 Android 浏览器)。所以我们需要 Babel + babel-loader 来 向下编译,以确保兼容性。

如何配置 babel-loader

复制代码
npm install babel-loader @babel/core @babel/preset-env --save-dev
如果你是 React 项目,还需要:
npm install @babel/preset-react --save-dev
如果是 TypeScript 项目,还需要:
npm install @babel/preset-typescript --save-dev

Webpack 配置示例:

复制代码
module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,                // 匹配 JS 文件
        exclude: /node_modules/,      // 不处理依赖库
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env'] // 使用的转换预设
          }
        }
      }
    ]
  }
};
3、核心概念说明
概念 含义
@babel/core Babel 的核心库,执行转换
babel-loader Webpack 的桥接器,调用 Babel
preset-env 根据目标浏览器自动选择转换插件
preset-react 转换 React 的 JSX
exclude: /node_modules/ 防止 Babel 转换第三方库(提高性能)
4、常见用途
使用场景 Babel preset
ES6+ 转 ES5 @babel/preset-env
JSX 转义 @babel/preset-react
TypeScript @babel/preset-typescript
Polyfill(如 Promise 配合 @babel/polyfillcore-js

4、Webpack 如何调用 Loader?

Webpack 内部会做这样的事情:

  1. 读取资源内容(如 style.css

  2. 依次调用匹配的 loader

  3. 把返回的内容作为模块打包入 bundle

六、构建优化与高级功能

1. 代码分割(Code Splitting)

  • 方式一:entry 多入口分割

  • 方式二:动态导入(如 import()

  • 方式三:使用 SplitChunksPlugin 自动提取公共代码

代码分割是指将项目代码打包成多个文件(chunk),按需加载,而不是打成一个大文件。

📌 这样做的好处:

  • 减少首次加载时间(只加载入口必要代码)

  • 实现懒加载(如路由切换时才加载页面)

  • 提升缓存命中率(比如第三方库提取为独立文件)

多入口(entry)分割

当项目有多个完全独立的入口页面(如多页面网站 MPA)。

复制代码
module.exports = {
  entry: {
    home: './src/home.js',
    admin: './src/admin.js'
  },
  output: {
    filename: '[name].bundle.js', // 输出 home.bundle.js / admin.bundle.js
    path: path.resolve(__dirname, 'dist')
  }
};
优点 缺点
简单直观 不适合组件共享和 SPA
适合多页面站点 无法自动抽取公共依赖

使用 SplitChunksPlugin 自动提取公共代码

Webpack 4+ 默认启用的优化插件 ,用于自动将多个入口之间共享的代码抽离成单独的 chunk

原因:

多个入口或异步模块之间,可能使用相同依赖(如 React、Lodash),如果不处理,会重复打包。

如何启用?

Webpack 4 起就默认开启SplitChunksPlugin,你也可以手动配置它:

复制代码
module.exports = {
  optimization: {
    splitChunks: {
      chunks: 'all', // 默认只分 async,改为 all 表示同步和异步都提取
      minSize: 20000, // 最小分割体积(单位:字节)
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          chunks: 'all'
        }
      }
    }
  }
};

打包结果:

  • main.bundle.js → 主代码

  • vendors.js → 第三方库

  • common.js → 多个模块都用到的公共代码

2. Tree Shaking

  • 移除未使用代码(只适用于 ES Module)

  • 要启用:

    • 使用 ES6 模块语法

    • mode: production 或手动启用 sideEffects: false

Tree Shaking 是一种"静态分析 + 剔除无用代码"的机制 ,可以在构建过程中移除未被使用(import)的代码,从而减小最终的 Bundle 体积。

📌 通俗说:就像把一个大树的枯枝剪掉,只留下你用到的"枝干"。

Tree Shaking 的基本原理

  • 基于静态 ES6 Module 分析模块依赖(即 import / export

  • Webpack 在构建时标记哪些导出被使用,哪些没有

  • 在压缩阶段(如使用 TerserPlugin),移除未使用的导出函数或变量

启用 Tree Shaking 的前提条件

条件 说明
✅ 使用 ES Module(ES6 import / export CommonJS(require / module.exports)不支持
✅ Webpack 运行在 production 模式下 或显式配置压缩插件
package.json 中设置 "sideEffects": false 或单独标记某些文件有副作用
✅ 没有副作用的代码 纯函数 / 纯模块更容易被剔除

3. 懒加载(Lazy Loading)与预加载

  • 使用 import() + 动态路由实现页面级懒加载

  • webpackPrefetchwebpackPreload 注释来优化加载时机

1、什么是懒加载(Lazy Loading)?

懒加载 是指将模块延迟加载------不是在首页就加载,而是在用户需要它的时候再去加载对应资源(chunk)

Webpack 如何实现懒加载?

使用 import() 动态导入语法 ,Webpack 会自动把被动态导入的模块打包成单独的 Chunk 文件,实现按需加载。

2、什么是预加载(Preload)和预取(Prefetch)?

Webpack 提供了两种注释魔法指令,来控制动态模块的"提前加载行为":

1. webpackPrefetch: true → 浏览器空闲时加载

import(/* webpackPrefetch: true */ './math.js');

使用时机:你希望 模块稍后会用到,可以让浏览器在空闲时偷偷下载它。

🔹 示例:页面 A 加载后就开始"预取"页面 B 的代码,等用户点击页面 B 时瞬间打开。

2. webpackPreload: true → 当前帧就加载

import(/* webpackPreload: true */ './utils.js');

🔹 使用时机:你希望模块尽快加载,但又不想影响主 chunk(比如大图、字体等资源)。

场景 使用方式 加载时机 适用对象
懒加载 import() 事件触发加载 页面、组件、模块
预取 Prefetch import(/* webpackPrefetch */) 浏览器空闲 页面跳转提前加载
预加载 Preload import(/* webpackPreload */) 当前帧加载 高优先级资源加载

结合使用示例

复制代码
// 当前不加载,等用户点击再加载
document.getElementById('btn').onclick = () => {
  import(/* webpackChunkName: "math" */ './math.js').then(({ add }) => {
    console.log(add(2, 3));
  });
};

// 预加载某个 chunk(立即加载,但不影响主线程)
import(/* webpackPreload: true */ './preload-heavy.js');

// 预取某个模块(浏览器空闲时加载)
import(/* webpackPrefetch: true */ './about.js');
相关推荐
海天胜景19 分钟前
jqGrid冻结列错行问题,将冻结表格(悬浮表格)与 正常表格进行高度同步
前端
清风细雨_林木木44 分钟前
解决 Tailwind CSS 代码冗余问题
前端·css
三天不学习1 小时前
VueUse/Core:提升Vue开发效率的实用工具库
前端·javascript·vue.js·vueuse
余道各努力,千里自同风1 小时前
CSS实现文本自动平衡text-wrap: balance
前端·css
Yvonne爱编码2 小时前
CSS- 4.3 绝对定位(position: absolute)&学校官网导航栏实例
前端·css·html·html5·hbuilder
繁依Fanyi3 小时前
ImgShrink:摄影暗房里的在线图片压缩工具开发记
开发语言·前端·codebuddy首席试玩官
卓律涤3 小时前
【找工作系列①】【大四毕业】【复习】巩固JavaScript,了解ES6。
开发语言·前端·javascript·笔记·程序人生·职场和发展·es6
Ten peaches3 小时前
Selenium-Java版(环境安装)
java·前端·selenium·自动化
心.c4 小时前
vue3大事件项目
前端·javascript·vue.js
姜 萌@cnblogs4 小时前
【实战】深入浅出 Rust 并发:RwLock 与 Mutex 在 Tauri 项目中的实践
前端·ai·rust·tauri