3214 字
16 分钟
Webpack 深度学习指南

Webpack 深度学习指南#

本文档帮助你系统学习 Webpack 的工作原理、常用配置和最佳实践。

第一章:Webpack 工作流程详解#

Webpack 做的事情是什么?#

简单说:把你的代码打成浏览器能理解的文件。

详细说:

源代码(多个文件)

[初始化] - 读取 webpack.config.js,初始化编译器

[编译] - 从 entry 开始,递归分析所有模块的依赖

[转换] - 用 loader 转换每个模块(TS→JS、JSX→JS 等)

[构建] - 生成模块之间的依赖图,处理 require/import

[优化] - Tree Shaking、代码分割、压缩等优化

[生成] - 输出最终的 bundle 文件

打包完成!可以在浏览器中运行

Webpack 的四个核心概念#

// webpack.config.js
module.exports = {
  // 1. entry - 入口,从哪里开始打包
  entry: './src/index.js',

  // 2. output - 出口,打包后的文件放在哪里
  output: {
    path: './dist',
    filename: 'bundle.js'
  },

  // 3. module - 模块,怎么处理各种类型的文件
  module: {
    rules: [
      // loader 就在这里配置
      { test: /\.js$/, use: 'babel-loader' }
    ]
  },

  // 4. plugin - 插件,控制整个打包流程
  plugins: [
    new HtmlWebpackPlugin()
  ]
};

这四个概念贯穿整个 Webpack 学习过程。


第二章:详解 Entry(入口)#

什么是 Entry?#

告诉 Webpack 从哪个文件开始分析依赖。Webpack 会从 entry 文件开始,找到它 import/require 的文件,再找这些文件的依赖,形成一个依赖图。

Entry 的配置方式#

方式 1:单入口(字符串)

module.exports = {
  entry: './src/index.js'
}

// Webpack 会在 dist 目录生成:
// dist/main.js

方式 2:多入口(对象)

module.exports = {
  entry: {
    main: './src/index.js',
    admin: './src/admin.js',
    user: './src/user.js'
  }
}

// Webpack 会生成多个 bundle:
// dist/main.js
// dist/admin.js
// dist/user.js

// 这样做的好处:不同页面加载不同的代码

方式 3:数组(合并入口)

module.exports = {
  entry: {
    bundle: ['./src/index.js', './src/app.js']
  }
}

// 会先加载 app.js,再加载 index.js,最后打成一个 bundle.js

实战:单页应用 vs 多页应用#

单页应用(SPA)

module.exports = {
  entry: './src/index.js',
  // 只有一个入口,所以只生成一个 bundle
  // React、Vue 项目都是这样
}

多页应用(MPA)

module.exports = {
  entry: {
    home: './src/pages/home/index.js',
    about: './src/pages/about/index.js',
    contact: './src/pages/contact/index.js'
  }
}

// 这样你可以为每个页面生成单独的 bundle
// 用户访问 home 页面只加载 home.js
// 用户访问 about 页面只加载 about.js

第三章:详解 Output(出口)#

出口配置#

module.exports = {
  output: {
    // 输出目录的绝对路径
    path: path.resolve(__dirname, 'dist'),

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

    // 输出的文件前缀路径(通常是 CDN 地址或相对路径)
    publicPath: '/',

    // 库的输出名称(如果你在打包库)
    library: 'MyLib',

    // 库的输出格式
    libraryTarget: 'umd'
  }
}

Filename 的高级用法#

静态文件名

filename: 'bundle.js'
// 输出:bundle.js

动态文件名(包含 hash)

filename: 'bundle.[hash].js'
// 输出:bundle.abc123def456.js
// 好处:不同版本的 bundle 有不同的名字,利用浏览器缓存

filename: 'bundle.[contenthash].js'
// contenthash:只有文件内容改变,hash 才改变(比 hash 更聪明)
// 这是最常用的方式

filename: 'bundle.[contenthash:8].js'
// 只用 hash 的前 8 位,缩短文件名

按照入口名称输出

entry: {
  main: './src/index.js',
  admin: './src/admin.js'
},
output: {
  filename: '[name].js'  // [name] 对应 entry 的 key
}

// 输出:
// main.js
// admin.js

按照chunk id输出

output: {
  filename: 'js/[name].[contenthash:8].js'
}

// 输出到 js 目录:
// js/main.abc123de.js
// js/admin.def456ab.js

PublicPath 的重要性#

output: {
  path: path.resolve(__dirname, 'dist'),
  publicPath: '/'  // 资源的基础路径
}

// 如果你的应用部署在 https://example.com/app/
// 应该设置:publicPath: '/app/'

// 这会影响打包结果中引用资源的路径
// <img src="/assets/logo.png">  // publicPath: '/'
// <img src="/app/assets/logo.png">  // publicPath: '/app/'

第四章:详解 Module 和 Loader#

Loader 是什么?#

Loader 是转换文件的工具。Webpack 默认只理解 JavaScript,但你的项目可能有:

  • TypeScript 文件
  • JSX 文件
  • CSS 文件
  • 图片文件
  • 字体文件

Loader 的作用就是把这些文件转成 Webpack 能理解的形式。

Loader 的执行原理#

源文件内容

[第一个 loader] - 处理,输出中间结果

[第二个 loader] - 继续处理,输出中间结果

[第三个 loader] - 继续处理,输出最终结果

Webpack 继续处理

重要:Loader 执行顺序是从右到左(或从下到上)!

module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
        // 执行顺序:css-loader 先执行,style-loader 后执行
      }
    ]
  }
}

常用 Loader 详解#

1. babel-loader - 转译 JavaScript#

module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,  // 不处理 node_modules 中的文件(加快速度)
        use: {
          loader: 'babel-loader',
          options: {
            presets: [
              '@babel/preset-env',      // 转译最新的 JavaScript 语法
              '@babel/preset-react'     // 处理 JSX
            ]
          }
        }
      }
    ]
  }
}

作用:把 ES6+ 语法转成浏览器能理解的 ES5 语法。

// 输入(ES6)
const name = 'Tom'
const fn = () => console.log(name)

// 输出(ES5)
var name = 'Tom'
var fn = function() { console.log(name) }

2. ts-loader - 转译 TypeScript#

module.exports = {
  module: {
    rules: [
      {
        test: /\.ts$/,
        use: 'ts-loader',
        exclude: /node_modules/
      }
    ]
  }
}

作用:把 TypeScript 代码转成 JavaScript。

// 输入
const greet = (name: string): string => {
  return `Hello, ${name}`
}

// 输出
const greet = (name) => {
  return `Hello, ${name}`
}

3. css-loader 和 style-loader - 处理 CSS#

module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      }
    ]
  }
}

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

  • css-loader:处理 CSS 中的 @importurl(),变成可以 import 的 JavaScript 模块
  • style-loader:把 CSS 注入到页面中(通过 <style> 标签)
// 你的代码
import './style.css'

// css-loader 处理后变成
const css = '.box { color: red }'

// style-loader 处理后变成
const style = document.createElement('style')
style.innerHTML = '.box { color: red }'
document.head.appendChild(style)

4. MiniCssExtractPlugin.loader - 提取 CSS 成单独文件#

const MiniCssExtractPlugin = require('mini-css-extract-plugin')

module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          MiniCssExtractPlugin.loader,  // 代替 style-loader
          'css-loader'
        ]
      }
    ]
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: 'css/[name].[contenthash].css'
    })
  ]
}

// 打包结果:
// dist/
//   main.js
//   css/main.abc123de.css  ← CSS 被提取成单独文件
//   index.html

为什么要提取 CSS?

  • 减少 JavaScript 文件体积
  • 浏览器可以并行下载 CSS 和 JS
  • 可以缓存 CSS 文件,提升性能

5. less-loader 和 sass-loader - 处理预处理器#

module.exports = {
  module: {
    rules: [
      {
        test: /\.less$/,
        use: ['style-loader', 'css-loader', 'less-loader']
        // 执行顺序:less-loader → css-loader → style-loader
      },
      {
        test: /\.scss$/,
        use: ['style-loader', 'css-loader', 'sass-loader']
      }
    ]
  }
}

// less-loader 作用:Less 语法 → CSS
// .box {
//   width: 100px;
//   &:hover { width: 110px; }
// }
// 转换为:
// .box { width: 100px; }
// .box:hover { width: 110px; }

6. file-loader / asset module - 处理图片和字体#

// 旧写法(file-loader)
module.exports = {
  module: {
    rules: [
      {
        test: /\.(png|jpg|gif|jpeg)$/,
        use: {
          loader: 'file-loader',
          options: {
            name: '[name].[hash:8].[ext]',
            outputPath: 'images/'
          }
        }
      }
    ]
  }
}

// 新写法(webpack 5+ 推荐,用 asset module)
module.exports = {
  module: {
    rules: [
      {
        test: /\.(png|jpg|gif)$/,
        type: 'asset/resource',
        generator: {
          filename: 'images/[name].[hash:8][ext]'
        }
      }
    ]
  }
}

作用:把图片复制到输出目录,返回路径。

import logo from './logo.png'

// 打包后变成
const logo = '/images/logo.abc123de.png'

// HTML 中可以使用
<img src={logo} />

7. url-loader - 小文件内联#

// 旧写法
module.exports = {
  module: {
    rules: [
      {
        test: /\.(png|jpg|gif)$/,
        use: {
          loader: 'url-loader',
          options: {
            limit: 8192,  // 小于 8KB 的文件转成 data URL
            fallback: 'file-loader'  // 大于 8KB 用 file-loader
          }
        }
      }
    ]
  }
}

// 新写法(webpack 5+ 推荐)
module.exports = {
  module: {
    rules: [
      {
        test: /\.(png|jpg|gif)$/,
        type: 'asset',
        parser: {
          dataUrlCondition: {
            maxSize: 8 * 1024  // 8 KB
          }
        }
      }
    ]
  }
}

作用:小文件转成 Base64 嵌入到代码中。

// 小于 8KB 的图片
import logo from './logo.png'
// logo = 'data:image/png;base64,iVBORw0KGgo...'

// 直接嵌入 HTML,减少网络请求
<img src={logo} />

Loader 的配置模式#

模式 1:字符串(简单情况)

use: 'babel-loader'

模式 2:对象(需要传参)

use: {
  loader: 'babel-loader',
  options: {
    presets: ['@babel/preset-env']
  }
}

模式 3:数组(多个 loader)

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

模式 4:数组 + 对象(复杂情况)

use: [
  'style-loader',
  {
    loader: 'css-loader',
    options: {
      modules: true  // 启用 CSS Modules
    }
  },
  'less-loader'
]

实战配置:完整的 loader 配置#

const path = require('path')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')

module.exports = {
  mode: 'development',
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].[contenthash:8].js'
  },
  module: {
    rules: [
      // JavaScript
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env', '@babel/preset-react']
          }
        }
      },
      // TypeScript
      {
        test: /\.tsx?$/,
        use: 'ts-loader',
        exclude: /node_modules/
      },
      // CSS
      {
        test: /\.css$/,
        use: [
          MiniCssExtractPlugin.loader,
          'css-loader'
        ]
      },
      // Less
      {
        test: /\.less$/,
        use: [
          MiniCssExtractPlugin.loader,
          'css-loader',
          'less-loader'
        ]
      },
      // 图片
      {
        test: /\.(png|jpg|gif)$/,
        type: 'asset',
        parser: {
          dataUrlCondition: {
            maxSize: 8 * 1024
          }
        },
        generator: {
          filename: 'images/[name].[hash:8][ext]'
        }
      },
      // 字体
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/,
        type: 'asset/resource',
        generator: {
          filename: 'fonts/[name].[hash:8][ext]'
        }
      }
    ]
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: 'css/[name].[contenthash:8].css'
    })
  ]
}

第五章:详解 Plugins(插件)#

Plugin 是什么?#

Plugin 可以在 Webpack 打包的整个过程中插入自定义逻辑。

Loader vs Plugin:

  • Loader:处理单个文件,转换文件内容
  • Plugin:作用在整个打包过程,可以监听各个环节

Plugin 的工作原理#

Webpack 打包过程中有很多”钩子”(生命周期事件),Plugin 可以监听这些钩子。

初始化

emit (即将输出文件)

输出文件

done (打包完成)

Plugin 就是在这些时刻执行自定义代码。

常用 Plugin 详解#

1. HtmlWebpackPlugin - 生成 HTML#

const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  plugins: [
    new HtmlWebpackPlugin({
      // HTML 模板文件
      template: './src/index.html',

      // 输出文件名
      filename: 'index.html',

      // 是否压缩 HTML
      minify: {
        removeComments: true,          // 删除注释
        collapseWhitespace: true,      // 删除空白
        removeRedundantAttributes: true // 删除冗余属性
      },

      // 在 HTML 中注入变量
      inject: 'body'  // 把 script 标签插入到 body 末尾
    })
  ]
}

原理

<!-- 源文件:src/index.html -->
<html>
  <head>
    <title>My App</title>
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>

<!-- 打包后:dist/index.html -->
<html>
  <head>
    <title>My App</title>
  </head>
  <body>
    <div id="root"></div>
    <script src="main.abc123de.js"></script>  <!-- 自动插入 -->
  </body>
</html>

多页应用场景

const pages = ['home', 'about', 'contact']

module.exports = {
  entry: {
    home: './src/pages/home/index.js',
    about: './src/pages/about/index.js',
    contact: './src/pages/contact/index.js'
  },
  plugins: [
    // 为每个页面生成 HTML
    ...pages.map(page => new HtmlWebpackPlugin({
      template: `./src/pages/${page}/index.html`,
      filename: `${page}.html`,
      chunks: [page]  // 只注入对应的 JS
    }))
  ]
}

2. MiniCssExtractPlugin - 提取 CSS#

前面已经讲过了,这里再强调一下用法:

const MiniCssExtractPlugin = require('mini-css-extract-plugin')

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

// 打包结果:
// dist/
//   main.abc123de.js
//   css/main.def456ab.css
//   index.html

3. TerserPlugin - 压缩 JavaScript#

const TerserPlugin = require('terser-webpack-plugin')

module.exports = {
  mode: 'production',  // 生产模式自动启用压缩
  
  optimization: {
    minimize: true,
    minimizer: [
      new TerserPlugin({
        terserOptions: {
          // 保留注释中的许可证信息
          format: {
            comments: false
          }
        },
        extractComments: false  // 不提取注释
      })
    ]
  }
}

// 原始代码
const message = 'Hello World'
console.log(message)

// 压缩后
const a="Hello World";console.log(a)

4. CssMinimizerPlugin - 压缩 CSS#

const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')

module.exports = {
  optimization: {
    minimizer: [
      new CssMinimizerPlugin()
    ]
  }
}

// 原始 CSS
.box {
  color: red;
  margin: 10px;
}

// 压缩后
.box{color:red;margin:10px}

5. CleanWebpackPlugin - 清空输出目录#

const { CleanWebpackPlugin } = require('clean-webpack-plugin')

module.exports = {
  plugins: [
    new CleanWebpackPlugin()  // 每次打包前删除 dist 目录
  ]
}

// 这样可以避免旧文件留在 dist 中

6. DefinePlugin - 定义全局变量#

const webpack = require('webpack')

module.exports = {
  plugins: [
    new webpack.DefinePlugin({
      'process.env.NODE_ENV': JSON.stringify('production'),
      API_URL: JSON.stringify('https://api.example.com'),
      DEBUG: false
    })
  ]
}

// 在你的代码中可以直接使用
console.log(process.env.NODE_ENV)  // 'production'
console.log(API_URL)                // 'https://api.example.com'
console.log(DEBUG)                  // false

重要:注意要用 JSON.stringify() 包装字符串值!

7. ProvidePlugin - 自动注入模块#

const webpack = require('webpack')

module.exports = {
  plugins: [
    new webpack.ProvidePlugin({
      $: 'jquery',
      jQuery: 'jquery',
      _: 'lodash'
    })
  ]
}

// 在你的代码中可以直接使用,不需要 import
$(document).ready(function() {
  // 可以直接用 $,不需要 import 'jquery'
})

_.debounce(fn, 300)  // 可以直接用 _

8. WebpackBundleAnalyzer - 分析包体积#

const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin

module.exports = {
  plugins: [
    new BundleAnalyzerPlugin({
      analyzerMode: 'static',  // 生成静态 HTML 报告
      reportFilename: 'report.html'
    })
  ]
}

// 打包后会生成一个可视化报告,显示各个模块的大小
// 帮助找出哪些模块最大

实战配置:完整的 Plugin 配置#

const path = require('path')
const webpack = require('webpack')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin

const isDev = process.env.NODE_ENV === 'development'

module.exports = {
  mode: isDev ? 'development' : 'production',
  
  entry: './src/index.js',
  
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'js/[name].[contenthash:8].js'
  },

  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env', '@babel/preset-react']
          }
        }
      },
      {
        test: /\.css$/,
        use: [
          isDev ? 'style-loader' : MiniCssExtractPlugin.loader,
          'css-loader'
        ]
      }
    ]
  },

  plugins: [
    // 清空输出目录
    new CleanWebpackPlugin(),

    // 生成 HTML
    new HtmlWebpackPlugin({
      template: './src/index.html',
      minify: !isDev
    }),

    // 提取 CSS(仅生产环境)
    ...(isDev ? [] : [
      new MiniCssExtractPlugin({
        filename: 'css/[name].[contenthash:8].css'
      })
    ]),

    // 定义全局变量
    new webpack.DefinePlugin({
      'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development'),
      APP_VERSION: JSON.stringify('1.0.0')
    }),

    // 包体积分析(仅在需要时启用)
    ...(process.env.ANALYZE ? [
      new BundleAnalyzerPlugin()
    ] : [])
  ]
}

第六章:常用配置详解#

1. Mode - 开发模式#

module.exports = {
  // development:开发模式
  // 不压缩代码,便于调试,打包快速
  mode: 'development',

  // production:生产模式
  // 压缩代码,性能优化,打包较慢
  mode: 'production',

  // none:不做任何优化
  mode: 'none'
}

2. DevtoolSourceMap(源码映射)#

module.exports = {
  // cheap-module-source-map:推荐用于生产环境
  // 行映射,文件较小,满足大部分需求
  devtool: 'cheap-module-source-map',

  // source-map:最完整的映射,文件最大
  devtool: 'source-map',

  // eval-cheap-module-source-map:推荐用于开发环境
  // 速度快,增量编译快
  devtool: 'eval-cheap-module-source-map',

  // false:不生成 source map
  devtool: false
}

实际应用

const isDev = process.env.NODE_ENV === 'development'

module.exports = {
  devtool: isDev ? 'eval-cheap-module-source-map' : 'cheap-module-source-map'
}

3. DevServer(开发服务器)#

module.exports = {
  devServer: {
    // 监听端口
    port: 3000,

    // 自动打开浏览器
    open: true,

    // 热更新(HMR)
    hot: true,

    // 当路由不存在时,重定向到 index.html(SPA 必需)
    historyApiFallback: true,

    // 代理后端 API
    proxy: {
      '/api': {
        target: 'http://localhost:8080',
        pathRewrite: { '^/api': '' },  // 移除 /api 前缀
        changeOrigin: true
      }
    },

    // 压缩
    compress: true,

    // 允许跨域
    headers: {
      'Access-Control-Allow-Origin': '*'
    }
  }
}

常见场景

// SPA 应用配置
devServer: {
  port: 3000,
  open: true,
  historyApiFallback: true,  // 路由都重定向到 index.html
  hot: true
}

// API 代理配置
devServer: {
  proxy: {
    '/api': {
      target: 'http://api.example.com',
      changeOri
Webpack 深度学习指南
https://fuwari.vercel.app/posts/webpack-深度学习指南/
作者
Kellen
发布于
2026-01-26
许可协议
CC BY-NC-SA 4.0