Webpack

文章目录

  • 前言
  • 一、核心概念
    • [1.1 Entry(入口)](#1.1 Entry(入口))
    • [1.2 Output(输出)](#1.2 Output(输出))
    • [1.3 完整的构建流程](#1.3 完整的构建流程)
  • 二、Loader
    • [2.1 定义](#2.1 定义)
    • [2.2 常用 Loader](#2.2 常用 Loader)
    • [2.3 Loader 执行顺序](#2.3 Loader 执行顺序)
  • 三、Plugin
    • [3.1 定义](#3.1 定义)
    • [3.2 常用 Plugin](#3.2 常用 Plugin)
    • [3.3 Loader vs Plugin](#3.3 Loader vs Plugin)
  • [四、devServer 与 HMR](#四、devServer 与 HMR)
    • [4.1 devServer](#4.1 devServer)
    • [4.2 HMR(Hot Module Replacement)](#4.2 HMR(Hot Module Replacement))
  • [五、contenthash 缓存策略](#五、contenthash 缓存策略)
    • [5.1 三种 hash 类型](#5.1 三种 hash 类型)
    • [5.2 为什么用 contenthash](#5.2 为什么用 contenthash)
    • [5.3 最佳配置](#5.3 最佳配置)
  • 六、从入口到产出:完整链路
  • 七、易混淆点
  • 八、思考与练习

前言

Webpack 是前端工程化的核心工具,理解其工作原理对构建优化、性能调优至关重要。本篇会讲清楚:

  • Entry / Output 配置
  • Loader 与 Plugin 的区别与执行顺序
  • devServer 与 HMR(热模块替换)
  • contenthash 缓存策略

一、核心概念

1.1 Entry(入口)

javascript 复制代码
// webpack.config.js
module.exports = {
  // 单入口
  entry: './src/index.js',
  
  // 多入口(多页应用)
  entry: {
    app: './src/app.js',
    admin: './src/admin.js'
  }
}

1.2 Output(输出)

javascript 复制代码
module.exports = {
  output: {
    filename: '[name].[contenthash].js',  // 输出文件名
    path: path.resolve(__dirname, 'dist'), // 输出目录
    clean: true  // 构建前清空输出目录
  }
}

1.3 完整的构建流程

复制代码
Entry → 依赖分析 → Loader 转换 → 模块图 → 生成 Chunk → Output

二、Loader

2.1 定义

Loader 是模块转换器,将非 JS 文件(CSS、图片、TypeScript 等)转换为 Webpack 可处理的模块。

2.2 常用 Loader

javascript 复制代码
module.exports = {
  module: {
    rules: [
      // CSS 处理
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader']  // 从右到左执行
      },
      // TypeScript
      {
        test: /\.tsx?$/,
        use: 'ts-loader'
      },
      // 图片
      {
        test: /\.(png|svg|jpg|gif)$/,
        type: 'asset/resource'
      },
      // Babel 转译
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env']
          }
        }
      }
    ]
  }
}

2.3 Loader 执行顺序

从右到左,从下到上

javascript 复制代码
use: ['style-loader', 'css-loader']
// 执行顺序:css-loader → style-loader
// 1. css-loader:解析 CSS 文件,处理 @import 和 url()
// 2. style-loader:将 CSS 注入到 DOM 的 <style> 标签

三、Plugin

3.1 定义

Plugin 是构建流程的扩展,可以在构建的各个阶段执行自定义逻辑。

3.2 常用 Plugin

javascript 复制代码
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer')

module.exports = {
  plugins: [
    // 生成 HTML 文件
    new HtmlWebpackPlugin({
      template: './src/index.html'
    }),
    
    // 提取 CSS 到单独文件
    new MiniCssExtractPlugin({
      filename: '[name].[contenthash].css'
    }),
    
    // 打包分析
    new BundleAnalyzerPlugin()
  ]
}

3.3 Loader vs Plugin

对比项 Loader Plugin
作用 模块转换 构建流程扩展
时机 模块加载时 构建生命周期各阶段
配置 module.rules plugins
本质 函数 类(带 apply 方法)
javascript 复制代码
// Loader 本质:一个函数
module.exports = function(source) {
  return transformedSource
}

// Plugin 本质:一个带 apply 方法的 class
class MyPlugin {
  apply(compiler) {
    compiler.hooks.done.tap('MyPlugin', (stats) => {
      console.log('构建完成!')
    })
  }
}

四、devServer 与 HMR

4.1 devServer

javascript 复制代码
module.exports = {
  devServer: {
    port: 3000,
    hot: true,          // 启用 HMR
    open: true,         // 自动打开浏览器
    proxy: {            // 代理配置
      '/api': 'http://localhost:8080'
    },
    historyApiFallback: true  // SPA 路由支持
  }
}

4.2 HMR(Hot Module Replacement)

HMR 在不刷新整个页面的情况下,替换、添加或删除模块。

javascript 复制代码
// 开启 HMR 后,Webpack 会:
// 1. 建立 WebSocket 连接
// 2. 文件变化时,通过 WebSocket 通知客户端
// 3. 客户端请求更新的模块
// 4. 替换旧模块,保持应用状态

// 手动处理 HMR(如状态管理)
if (module.hot) {
  module.hot.accept('./module', () => {
    // 模块更新后的处理逻辑
  })
}

五、contenthash 缓存策略

5.1 三种 hash 类型

javascript 复制代码
// 1. hash:每次构建都变化,所有文件共用
output: { filename: '[name].[hash].js' }

// 2. chunkhash:同一 chunk 的文件相同
output: { filename: '[name].[chunkhash].js' }

// 3. contenthash:根据文件内容变化
output: { filename: '[name].[contenthash].js' }

5.2 为什么用 contenthash

javascript 复制代码
// 假设有两个文件:app.js 和 vendor.js
// 使用 chunkhash:vendor.js 内容没变,但 app.js 变了
// 结果:vendor.js 的 hash 也变了(因为它们在同一 chunk)

// 使用 contenthash:只有内容变化的文件 hash 才变
// 结果:只有 app.js 的 hash 变化,vendor.js 保持不变

// 浏览器可以利用缓存,避免重新下载未变化的文件

5.3 最佳配置

javascript 复制代码
module.exports = {
  output: {
    filename: '[name].[contenthash:8].js',  // 8 位 hash
    chunkFilename: '[name].[contenthash:8].chunk.js'
  },
  optimization: {
    splitChunks: {
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          chunks: 'all'
        }
      }
    }
  }
}

六、从入口到产出:完整链路

复制代码
1. 读取 Entry
2. 从 Entry 开始,递归分析依赖
3. 对每个模块应用对应的 Loader 转换
4. 生成模块图(Module Graph)
5. 将模块合并为 Chunk
6. 应用 Plugin(如 HtmlWebpackPlugin)
7. 输出到 dist 目录

七、易混淆点

  1. Loader vs Plugin :Loader 是模块转换器 (处理单个文件),Plugin 是构建流程扩展(干预整个构建过程)。
  2. 执行顺序:Loader 从右到左执行;Plugin 按数组顺序执行,但会根据 hooks 类型在不同阶段触发。
  3. HMR 原理:通过 WebSocket 实现,文件变化时通知客户端请求更新的模块,替换旧模块保持应用状态。
  4. contenthash:根据文件内容计算 hash,只有内容变化时 hash 才变化,利于浏览器缓存。

八、思考与练习

1. Loader 和 Plugin 的区别是什么?

解析:

  • Loader :模块转换器,处理单个文件(如 CSS → JS),配置在 module.rules
  • Plugin :构建流程扩展,干预整个构建过程(如生成 HTML),配置在 plugins

2. Loader 的执行顺序是怎样的?

解析:从右到左 ,从下到上。例如 use: ['style-loader', 'css-loader'],先执行 css-loader,再执行 style-loader

3. HMR 是如何工作的?

解析:

  1. 建立 WebSocket 连接
  2. 文件变化时,Webpack 通过 WebSocket 通知客户端
  3. 客户端请求更新的模块
  4. 替换旧模块,保持应用状态

4. 为什么推荐使用 contenthash

解析:contenthash 根据文件内容计算 hash,只有内容变化时 hash 才变化。这使得未变化的文件可以利用浏览器缓存,避免重新下载。

5. 说一下 Webpack 从入口到产出的完整流程。

解析:

  1. 读取 Entry
  2. 递归分析依赖
  3. 对每个模块应用 Loader 转换
  4. 生成模块图
  5. 合并为 Chunk
  6. 应用 Plugin
  7. 输出到 dist 目录
相关推荐
kyriewen8 小时前
我手写了一个 EventEmitter,面试官追问了 6 个问题——第 4 个我没答上来
前端·javascript·面试
IT_陈寒9 小时前
Java的Date类又坑了我一次,改用时间戳真香
前端·人工智能·后端
小林攻城狮9 小时前
使用 Transport 节流解决 Vercel AI SDK 流式渲染卡死问题
前端·react.js
前端缘梦9 小时前
告别 TS 运行时类型漏洞!Zod 完整入门实战教程(前端 / 全栈必备)
前端·react.js·全栈
the_answer10 小时前
Webpack vs Vite 深度对比分析
前端·webpack
转转技术团队10 小时前
验证码识别实战:前端不写页面,改训模型了?
前端
MomentYY10 小时前
Temperature:AI 的“脑洞旋钮”
前端·llm·ai编程
远航_10 小时前
OpenSpec 完整详细介绍
前端·后端
召钱熏11 小时前
状态枚举正确≠渲染正确:一个语音按钮的状态机边界修复实录
android·前端
SkyWalking中文站11 小时前
认识 Horizon UI · 1/17:SkyWalking 新一代可观测性控制台
运维·前端·监控