进阶配置
Webpack 进阶场景:多页面(MPA)、devServer、环境变量注入、Source Map、Module Federation 等。基础见 工作原理。
一、多页面应用配置
多页面应用(MPA)需要生成多个 HTML 文件,每个文件对应不同的入口。
1. 动态生成多个 entry 与 HtmlWebpackPlugin 实例
// webpack.config.js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const glob = require("glob");
// 约定页面目录:src/pages/下每个子文件夹代表一个页面
const pages = glob.sync("./src/pages/*/index.js");
const entry = {};
const htmlPlugins = [];
pages.forEach((pagePath) => {
const pageName = path.basename(path.dirname(pagePath));
entry[pageName] = pagePath;
htmlPlugins.push(
new HtmlWebpackPlugin({
template: `./src/pages/${pageName}/index.html`,
filename: `${pageName}.html`,
chunks: [pageName, "vendor"], // 指定引入的chunk
}),
);
});
module.exports = {
entry,
plugins: [...htmlPlugins],
};
2. 多页面共享 vendor chunk
为了避免每个页面重复打包公共库,使用 splitChunks 提取 node_modules 中的依赖:
optimization: {
splitChunks: {
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendor',
chunks: 'all', // 对所有入口生效
priority: 10,
},
common: {
minChunks: 2, // 至少被2个入口引用
name: 'common',
chunks: 'all',
priority: 5,
reuseExistingChunk: true,
},
},
},
},
然后在 HtmlWebpackPlugin 的 chunks 数组中包含 vendor 和 common,确保生成的 HTML 会引用这些共享 chunk。
二、开发服务器(webpack-dev-server)进阶
1. 代理配置(proxy)解决跨域、路径重写
开发环境下,前端请求后端 API 时若遇到跨域问题,可以通过 devServer 代理将请求转发到真实服务器。
devServer: {
proxy: {
'/api': {
target: 'http://localhost:3000',
pathRewrite: { '^/api': '' }, // 将 /api/users 重写为 /users
changeOrigin: true, // 修改请求头中的 origin
secure: false, // 接受 HTTPS 自签名证书
},
'/socket': {
target: 'ws://localhost:8080',
ws: true, // 支持 WebSocket 代理
},
},
},
2. 热更新(HMR)原理
工作原理:
- 构建时,
HotModuleReplacementPlugin给模块添加 HMR runtime 代码。 - 运行时,通过 WebSocket 与 devServer 通信,接收模块更新通知。
- 当某个模块更新时,客户端只请求变更的 chunk,并执行模块的热替换逻辑(需要模块自身支持,如 Vue、React 的 HMR API)。
启用 HMR:
devServer: {
hot: true, // 启用 HMR,自动注入 HotModuleReplacementPlugin
},
自定义模块热接受:
if (module.hot) {
module.hot.accept("./module.js", () => {
console.log("module 更新了");
// 重新执行副作用
});
}
3. 自定义 dev-server 中间件
onBeforeSetupMiddleware / onAfterSetupMiddleware(v3 之前为 before / after)已在 webpack-dev-server v5 中移除,统一改用 setupMiddlewares:用 middlewares.unshift(...) 在内部中间件之前注入(对应旧的 onBeforeSetupMiddleware),用 middlewares.push(...) 在之后注入(对应旧的 onAfterSetupMiddleware),并需要 return middlewares。
devServer: {
setupMiddlewares: (middlewares, devServer) => {
if (!devServer) {
throw new Error("webpack-dev-server is not defined");
}
// 对应旧的 onBeforeSetupMiddleware:在内部中间件之前执行
middlewares.unshift((req, res, next) => {
if (req.path === "/custom") {
return res.json({ custom: "response" });
}
next();
});
// 对应旧的 onAfterSetupMiddleware:在内部中间件之后执行
middlewares.push((req, res, next) => {
console.log(`[${new Date()}] ${req.method} ${req.url}`);
next();
});
return middlewares;
},
},
三、环境变量注入
1. DefinePlugin vs EnvironmentPlugin
| 插件 | 作用 | 用法 |
|---|---|---|
DefinePlugin | 定义全局常量,可用于任意字符串替换 | new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify('production') }) |
EnvironmentPlugin | 读取系统环境变量并注入到代码中,底层仍使用 DefinePlugin | new webpack.EnvironmentPlugin(['NODE_ENV', 'API_BASE']) |
注意:传入的值会被 JavaScript 表达式求值,因此必须使用 JSON.stringify 包裹字符串,否则会被当成变量名。
new webpack.DefinePlugin({
__VERSION__: JSON.stringify("1.0.0"),
__API_BASE__: process.env.API_BASE ? JSON.stringify(process.env.API_BASE) : '"/api"',
});
2. 类型安全的全局变量注入(配合 TypeScript)
如果在代码中使用了全局变量(如 __API_BASE__),TypeScript 会报错,需要声明其类型。
创建 global.d.ts:
declare const __API_BASE__: string;
declare const __VERSION__: string;
然后在任意 .ts 文件中便可直接使用,类型安全。
四、source map 策略
devtool 选项决定了 source map 的生成方式,需要在构建速度、重建速度、调试体验之间权衡。
| devtool 类型 | 构建速度 | 重建速度 | 生产环境 | 调试体验 | 说明 |
|---|---|---|---|---|---|
eval | 最快 | 最快 | ❌ | 一般 | 每个模块用 eval 执行,报错只显示行号,无源码映射 |
eval-cheap-source-map | 快 | 快 | ❌ | 较好 | 每行映射,不包含列信息,丢失原始源文件(loader 转换后) |
eval-cheap-module-source-map | 中等 | 中等 | ❌ | 好 | 包含列信息,保留 loader 处理后的源码映射,开发推荐 |
source-map | 慢 | 慢 | ✅ | 最佳 | 生成完整独立 .map 文件,减速构建,生产可用但不推荐(暴露源码) |
hidden-source-map | 慢 | 慢 | ✅ | 不直接 | 生成 source map 但不关联,用于错误上报平台 |
nosources-source-map | 慢 | 慢 | ✅ | 有限 | 只显示堆栈位置,不包含源代码内容,适合生产环境 |
cheap-module-source-map | 较慢 | 较慢 | ✅ | 较好 | 不包含列,但保留原始源码,适合生产调试 |
推荐组合:
- 开发环境:
eval-cheap-module-source-map(快速重建,调试友好) - 生产环境:
hidden-source-map或nosources-source-map(不暴露源码,但可提供错误追踪)
module.exports = {
devtool:
process.env.NODE_ENV === "production" ? "hidden-source-map" : "eval-cheap-module-source-map",
};
五、模块联邦(Module Federation)
模块联邦是 Webpack 5 的核心特性,用于实现微前端架构,允许多个独立构建的应用在运行时共享模块。
1. exposes 与 remotes 配置
exposes:当前应用暴露给其他应用的模块。remotes:当前应用引用的远程应用入口。
host 应用(shell):
new ModuleFederationPlugin({
name: "shell", // 应用名称,必须唯一
remotes: {
app1: "app1@http://localhost:3001/remoteEntry.js",
app2: "app2@http://localhost:3002/remoteEntry.js",
},
shared: ["react", "react-dom"],
});
remote 应用:
new ModuleFederationPlugin({
name: "app1",
filename: "remoteEntry.js", // 远程入口文件
exposes: {
"./Button": "./src/Button",
"./utils": "./src/utils",
},
shared: ["react", "react-dom"],
});
2. 共享依赖(shared)策略
shared 配置用于避免重复加载公共库,同时解决版本冲突。
shared: {
react: {
singleton: true, // 全局唯一实例
eager: false, // 默认 false,不提前加载(懒加载)
requiredVersion: '^18.0.0',
version: '18.2.0',
},
'react-dom': {
singleton: true,
eager: false,
},
lodash: {
singleton: false, // 允许多版本共存
},
}
singleton:是否只使用一个共享实例。如果多个应用版本不兼容,Webpack 会在控制台发出警告,并选择最高兼容版本。eager:是否在初始 chunk 中直接加载该共享模块,而不是异步加载。如果设置为true,可能会增加首屏体积,但避免异步加载的额外请求。version/requiredVersion:用于版本冲突时的匹配。
3. 构建时与运行时的模块加载机制
构建时:
- 每个联邦构建会生成一个
remoteEntry.js文件(或者其他名称),该文件包含了暴露模块的映射以及加载逻辑。 - 构建时并不解析远程模块的具体代码,只记录其暴露路径和共享依赖关系。
运行时:
- 当 host 应用执行
import('app1/Button')时,Webpack 运行时会根据remotes配置动态加载app1的remoteEntry.js。 remoteEntry中定义了一个__webpack_require__.l动态加载方法,可以按需获取暴露模块的 chunk。- 共享依赖的检测:加载远程模块前,运行时会检查本地是否已有匹配的共享模块版本,如果有则直接提供,避免重复加载。
图示流程:
- Host 引用
app1/Button→ 调用__webpack_require__.f.remotes - 检查缓存中是否已有该模块,没有则动态加载
app1/remoteEntry.js - 执行
remoteEntry代码,注册app1的模块工厂 - 从远程模块工厂中获取
Button模块,同时处理共享依赖(优先使用 host 提供的版本)
总结
| 配置主题 | 核心要点 |
|---|---|
| MPA | 动态生成 entry + HtmlWebpackPlugin,splitChunks 提取共享 vendor |
| devServer | proxy 代理跨域,HMR 原理(插件+WebSocket),自定义中间件扩展 |
| 环境变量 | DefinePlugin vs EnvironmentPlugin;配合 TypeScript declare 保证类型安全 |
| source map | 开发用 eval-cheap-module-source-map,生产用 hidden-source-map 或 nosources-source-map,权衡构建速度和调试信息 |
| 模块联邦 | exposes/remotes 暴露/引用模块,shared 控制依赖共享策略(singleton、eager),实现微前端运行时模块共享 |
掌握这些高级配置能让 Webpack 适应更复杂的业务场景,并显著提升开发体验和生产性能。
参考文献
以下链接在编写时均可正常访问:
| 资料 | 说明 |
|---|---|
| Module Federation | 官方 |
| DevServer | 配置 |
| Source Map | devtool |
相关文章
工作原理
Webpack 是静态模块打包器:从入口递归解析依赖,经 Loader 转换后输出一个或多个 bundle。与 Vite 对比见 Webpack 与 Vite。
产物优化
Webpack 通过代码分割、Tree Shaking、压缩与资源模块等减少体积、改善缓存。原理背景见 工作原理。
企业级实践
大型前端项目在 Webpack 场景下的多环境拆分、Docker 构建缓存、自定义 CLI 与微前端(Module Federation / single-spa)等实践。入门见 工程化概览。
Loader
Loader 在 Webpack 构建链中把源文件转为可打包的 JS 模块(如 TS、SCSS、Vue SFC)。见 工作原理。
Plugin
Webpack 插件在构建生命周期钩子上扩展能力(修改资源、生成 HTML、上传 CDN 等)。背景见 工作原理;与 Loader 分工:Loader 转换单文件,插件面向整体构建。
函数进阶
this 绑定、高阶函数、纯函数、记忆化、递归与 IIFE 等是日常开发与面试中的核心主题。前置:语言基础、作用域与闭包。
Series
webpack
1 / 6