更多 Webpack 面试题
题目9:什么是 Tree Shaking?原理是什么?
问题解析
Tree Shaking 是 Webpack 的重要优化特性,考察对代码优化和 ES Modules 静态特性的理解。
核心概念
Tree Shaking(摇树优化)是指删除未引用代码(Dead Code Elimination)的优化技术。它基于 ES Modules 的静态结构特性,在编译时分析模块依赖关系,只打包被使用的代码。
┌─────────────────────────────────────────────────────────────────┐
│ Tree Shaking 原理 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 源代码 打包结果 │
│ ┌─────────────────────┐ ┌─────────────────────┐ │
│ │ utils.js │ │ 只包含 used 代码 │ │
│ │ export const add │ │ │ │
│ │ export const sub │ --> │ export const add │ │
│ │ export const mul │ │ │ │
│ │ export const div │ │ sub/mul/div 被删除 │ │
│ └─────────────────────┘ └─────────────────────┘ │
│ │
│ 使用:import { add } from './utils' // 只用 add │
│ │
└─────────────────────────────────────────────────────────────────┘
详细解答
Tree Shaking 的工作原理
// 1. ES Modules 的静态特性
// 导入导出必须在顶层,不能动态
import { foo } from './module'; // ✅ 静态,可分析
const path = './module';
import(path).then(m => m.foo()); // ❌ 动态,无法分析
// 2. Webpack 分析依赖图
// 标记哪些导出被使用(usedExports)
// 在压缩阶段删除未使用的代码
// 3. 需要配合代码压缩
// development 模式不会 Tree Shaking
// production 模式自动启用
配置示例
module.exports = {
mode: 'production', // 自动启用 Tree Shaking
optimization: {
// 显式配置
usedExports: true, // 标记未使用的导出
sideEffects: false, // 告知无副作用,可安全删除
// 代码压缩时删除未使用代码
minimize: true
}
};
package.json 配置
{
"name": "my-lib",
"sideEffects": [
"*.css",
"*.scss",
"./src/polyfill.js"
]
}
// sideEffects: false - 所有文件都没有副作用,可安全 Tree Shaking
// sideEffects: [...] - 指定有副作用的文件,其他可 Tree Shaking
最佳实践
// 1. 使用 ES Modules 导出
// ✅ 推荐
export const helper = () => {};
export default Component;
// ❌ 避免 CommonJS
exports.helper = () => {};
module.exports = Component;
// 2. 避免副作用代码
// ❌ 这种代码会阻止 Tree Shaking
const data = fetchData(); // 副作用
export const result = data.map(...);
// ✅ 改为纯函数
export const getResult = (data) => data.map(...);
// 3. 按需引入库
// ✅ 推荐
import { debounce } from 'lodash-es';
import Button from 'antd/es/button';
// ❌ 避免全量引入
import _ from 'lodash';
import Antd from 'antd';
题目10:什么是 Code Splitting?有哪些实现方式?
核心概念
Code Splitting(代码分割)是将代码分割成多个 chunk,按需加载或并行加载,减少首屏加载时间。
实现方式
// 方式一:入口分割(Entry Points)
module.exports = {
entry: {
main: './src/index.js',
vendor: './src/vendor.js' // 分离第三方库
},
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist')
}
};
// 方式二:动态导入(Dynamic Imports)- 推荐
// 按需加载,实现真正的代码分割
const loadComponent = () => import('./Component');
// React 中使用
const LazyComponent = React.lazy(() => import('./Component'));
// Vue 中使用
const LazyComponent = () => import('./Component.vue');
// 方式三:SplitChunksPlugin(内置)
module.exports = {
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
// 提取第三方库
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
priority: 10
},
// 提取公共代码
common: {
minChunks: 2,
chunks: 'all',
enforce: true
}
}
}
}
};
题目11:Webpack 的 hash、chunkhash、contenthash 有什么区别?
核心概念
| 类型 | 说明 | 使用场景 |
|---|---|---|
| hash | 每次构建生成的唯一 hash,所有文件相同 | 不推荐 |
| chunkhash | 基于入口 chunk 内容的 hash | JS 文件 |
| contenthash | 基于文件内容的 hash | CSS 文件(推荐) |
配置示例
module.exports = {
output: {
filename: '[name].[chunkhash:8].js', // JS 用 chunkhash
path: path.resolve(__dirname, 'dist')
},
plugins: [
new MiniCssExtractPlugin({
filename: 'css/[name].[contenthash:8].css' // CSS 用 contenthash
})
]
};
// 为什么 CSS 用 contenthash?
// 当 JS 改变但 CSS 不变时:
// - chunkhash: CSS hash 也会变(因为属于同一个 chunk)
// - contenthash: CSS hash 不变,可以利用缓存
题目12:什么是 Webpack 的 Module Federation?
核心概念
Module Federation(模块联邦)是 Webpack 5 的新特性,实现微前端架构,允许多个独立构建的应用共享模块。
┌─────────────────────────────────────────────────────────────────┐
│ Module Federation 架构 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Host App │ │ Remote App │ │
│ │ │ │ │ │
│ │ import() ────┼──────────────┼──▶ exposes │ │
│ │ Button │ │ Button │ │
│ │ │ │ │ │
│ └──────────────┘ └──────────────┘ │
│ │
│ Host 可以动态加载 Remote 暴露的模块 │
│ │
└─────────────────────────────────────────────────────────────────┘
配置示例
// Remote App (暴露模块)
// webpack.config.js
const { ModuleFederationPlugin } = require('webpack').container;
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'remoteApp',
filename: 'remoteEntry.js',
exposes: {
'./Button': './src/components/Button',
'./Header': './src/components/Header'
},
shared: ['react', 'react-dom'] // 共享依赖
})
]
};
// Host App (消费模块)
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'hostApp',
remotes: {
remoteApp: 'remoteApp@http://localhost:3001/remoteEntry.js'
},
shared: ['react', 'react-dom']
})
]
};
// 使用远程模块
import('remoteApp/Button').then(({ default: Button }) => {
// 使用 Button 组件
});
题目13:Webpack 中如何处理图片资源?
Webpack 5 之前
module.exports = {
module: {
rules: [
{
test: /\.(png|jpg|gif|svg)$/,
use: {
loader: 'url-loader',
options: {
limit: 8192, // 8KB 以下转 base64
name: 'images/[name].[hash:8].[ext]',
fallback: 'file-loader' // 超过 limit 使用 file-loader
}
}
}
]
}
};
Webpack 5 推荐(Asset Modules)
module.exports = {
module: {
rules: [
{
test: /\.(png|jpg|gif|svg)$/,
type: 'asset', // 替代 url-loader
parser: {
dataUrlCondition: {
maxSize: 8 * 1024 // 8KB
}
},
generator: {
filename: 'images/[name].[hash:8][ext]'
}
}
]
}
};
// type 选项:
// asset/resource - 替代 file-loader,发送单独文件
// asset/inline - 替代 url-loader,导出 data URI
// asset/source - 替代 raw-loader,导出源码
// asset - 替代 url-loader,自动选择
题目14:Webpack 中如何处理 CSS?
开发环境配置
module.exports = {
mode: 'development',
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader', // 将 CSS 注入 DOM
'css-loader', // 解析 CSS
'postcss-loader' // 添加浏览器前缀
]
},
{
test: /\.scss$/,
use: [
'style-loader',
'css-loader',
'postcss-loader',
'sass-loader' // 编译 SCSS
]
}
]
}
};
生产环境配置
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
module.exports = {
mode: 'production',
module: {
rules: [
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader, // 提取 CSS 为单独文件
'css-loader',
'postcss-loader'
]
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: 'css/[name].[contenthash:8].css'
})
],
optimization: {
minimizer: [
new CssMinimizerPlugin() // 压缩 CSS
]
}
};
题目15:什么是 Webpack 的 Shimming?
核心概念
Shimming(垫片)用于解决旧库或不兼容模块的兼容性问题,将全局变量转换为模块导出。
const webpack = require('webpack');
module.exports = {
module: {
rules: [
// 方式一:imports-loader
// 将 this 指向 window,并导入 jQuery
{
test: require.resolve('some-old-library'),
use: 'imports-loader?this=>window,jquery=>jquery'
},
// 方式二:exports-loader
// 将全局变量导出为模块
{
test: require.resolve('some-old-library'),
use: 'exports-loader?exports=default SomeGlobal'
}
]
},
plugins: [
// 方式三:ProvidePlugin
// 自动加载模块,无需 import
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery',
_: 'lodash'
})
]
};
// 使用 ProvidePlugin 后,以下代码无需导入即可使用
// $('#item'); // 自动注入 jquery
// _.debounce(); // 自动注入 lodash
题目16:Webpack 的 mode 配置有什么作用?
核心概念
mode 配置告知 Webpack 使用相应模式的内置优化。
module.exports = {
mode: 'development', // 或 'production' 或 'none'
};
| mode | 特点 | 默认配置 |
|---|---|---|
| development | 开发优化,注重构建速度和调试 | devtool: 'eval', NamedChunksPlugin |
| production | 生产优化,注重代码体积 | 启用 Tree Shaking、代码压缩、Scope Hoisting |
| none | 不启用任何默认优化 | 无 |
development 模式默认优化
// 等同于 mode: 'development'
module.exports = {
devtool: 'eval',
cache: true,
optimization: {
moduleIds: 'named',
chunkIds: 'named',
minimize: false
}
};
production 模式默认优化
// 等同于 mode: 'production'
module.exports = {
devtool: 'source-map',
optimization: {
moduleIds: 'deterministic',
chunkIds: 'deterministic',
minimize: true,
minimizer: [new TerserPlugin()],
usedExports: true, // Tree Shaking
sideEffects: true, // Tree Shaking
splitChunks: { chunks: 'all' },
runtimeChunk: true
}
};
题目17:Webpack 中如何实现国际化(i18n)?
方案一:使用 i18n-loader
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.i18n\.json$/,
use: [
{
loader: 'i18n-loader',
options: {
language: process.env.LANGUAGE || 'zh-CN'
}
}
]
}
]
}
};
// 使用
import messages from './messages.i18n.json';
方案二:多语言打包
// webpack.config.js
const languages = ['zh-CN', 'en-US', 'ja-JP'];
module.exports = languages.map(lang => ({
name: lang,
entry: './src/index.js',
output: {
path: path.resolve(__dirname, `dist/${lang}`),
filename: '[name].js'
},
plugins: [
new webpack.DefinePlugin({
'process.env.LANGUAGE': JSON.stringify(lang)
})
]
}));
题目18:Webpack 如何处理循环依赖?
核心概念
循环依赖(Circular Dependency)是指模块 A 依赖 B,B 又依赖 A 的情况。
// a.js
import { b } from './b';
export const a = 'a';
console.log(b); // 可能为 undefined
// b.js
import { a } from './a';
export const b = 'b';
console.log(a); // 可能为 undefined
检测循环依赖
const CircularDependencyPlugin = require('circular-dependency-plugin');
module.exports = {
plugins: [
new CircularDependencyPlugin({
exclude: /node_modules/,
include: /src/,
failOnError: true, // 发现循环依赖时构建失败
allowAsyncCycles: false,
cwd: process.cwd()
})
]
};
解决方案
// 方案一:重构代码,提取公共部分
// common.js
export const shared = {};
// a.js
import { shared } from './common';
shared.a = 'a';
// b.js
import { shared } from './common';
shared.b = 'b';
// 方案二:延迟导入
// a.js
export const a = 'a';
export function init() {
const { b } = require('./b'); // 延迟导入
console.log(b);
}
// 方案三:事件驱动/依赖注入
class ServiceA {
constructor(serviceB) {
this.serviceB = serviceB;
}
}
题目19:Webpack 的 Scope Hoisting 是什么?
核心概念
Scope Hoisting(作用域提升)是 Webpack 3 引入的优化,将多个模块合并到一个函数中,减少函数声明和闭包开销。
// 优化前(未启用 Scope Hoisting)
// 每个模块一个函数
(function(module, exports, __webpack_require__) {
module.exports = 'module a';
}),
(function(module, exports, __webpack_require__) {
module.exports = 'module b';
})
// 优化后(启用 Scope Hoisting)
// 合并到一个函数
(function() {
var a = 'module a';
var b = 'module b';
})
配置
module.exports = {
mode: 'production', // 自动启用
optimization: {
// 显式配置
concatenateModules: true // 启用 Scope Hoisting
}
};
限制条件
- 必须使用 ES Modules(import/export)
- 不能是异步模块
- 模块不能包含 eval()
- 模块的 exports 不能被动态使用
题目20:Webpack 与 Vite 的区别?
核心对比
| 特性 | Webpack | Vite |
|---|---|---|
| 开发模式 | 先打包再服务 | 原生 ESM,按需编译 |
| 冷启动 | 慢(需要打包) | 快(无需打包) |
| HMR | 中等 | 极快(ESM 原生支持) |
| 生产构建 | 打包 | 使用 Rollup 打包 |
| 配置复杂度 | 复杂但灵活 | 简单约定优于配置 |
| 生态 | 成熟丰富 | 快速发展中 |
原理对比
Webpack 开发模式:
源代码 --> 打包 --> 内存 --> 浏览器
↑___________________________↓
修改后重新打包
Vite 开发模式:
源代码 --> 直接服务 --> 浏览器(原生 ESM)
↑___________________________↓
按需编译修改的模块
适用场景
- Webpack:大型复杂项目、需要高度定制、生态依赖多
- Vite:中小型项目、快速开发、Vue 3/React 新项目
面试要点总结
高频考点
- Tree Shaking:基于 ES Modules 静态特性,删除未引用代码
- Code Splitting:入口分割、动态导入、SplitChunks
- Hash 策略:hash vs chunkhash vs contenthash
- Loader vs Plugin:文件转换器 vs 编译扩展
- 构建优化:缓存、多线程、缩小范围、代码分割
回答技巧
- 先讲概念定义,再讲原理,最后讲实践
- 结合配置代码说明
- 区分开发环境和生产环境的不同策略
- 提及 Webpack 5 的新特性(持久化缓存、Asset Modules、Module Federation)