常见面试题
介绍一下 webpack
Webpack 是一个现代 JavaScript 应用程序的静态模块打包工具(module bundler)。它的核心作用是将项目中各种资源(如 JavaScript、CSS、图片、字体等)视为模块,并根据它们之间的依赖关系,构建出一个或多个优化后的静态资源包(bundle),供浏览器加载使用。
一、Webpack 的作用
- 模块打包:将多个模块(包括 ES6 模块、CommonJS、AMD 等)打包成一个或多个 bundle。
- 依赖管理:自动分析模块间的依赖关系,按需加载。
- 代码转换:通过 loader 将非 JavaScript 资源(如 TypeScript、SCSS、图片等)转换为 JavaScript 模块。
- 代码优化:支持代码分割(code splitting)、懒加载、Tree Shaking(去除未使用代码)、压缩混淆等。
- 开发效率提升:提供开发服务器(webpack-dev-server)、热更新(HMR)、Source Map 等功能。
- 插件扩展:通过 plugin 实现更复杂的构建逻辑,如 HTML 自动生成、环境变量注入、资源压缩等。
二、Webpack 的主要组成部分
-
Entry(入口)
指定打包的起点模块。Webpack 会从这个文件开始递归解析依赖。 -
Output(出口)
配置打包后文件的输出路径和文件名。 -
Loader(加载器)
用于对模块的源代码进行转换。例如:babel-loader:将 ES6+ 转为 ES5css-loader+style-loader:处理 CSS 文件file-loader/url-loader:处理图片、字体等静态资源
-
Plugin(插件)
用于执行更广泛的任务,比如打包优化、环境变量定义、HTML 文件生成等。例如:HtmlWebpackPlugin:自动生成 HTML 并自动引入 bundleMiniCssExtractPlugin:将 CSS 提取到单独文件DefinePlugin:定义全局常量(如process.env.NODE_ENV)
-
Mode(模式)
可设为development或production,Webpack 会自动启用相应的优化配置。 -
Resolve(解析规则)
配置模块如何被解析,例如别名(alias)、扩展名(extensions)等。 -
DevServer(开发服务器)
提供本地开发服务器,支持热更新、自动刷新等。
三、Loader 和 Plugin 的区别
| 特性 | Loader | Plugin |
|---|---|---|
| 作用时机 | 在模块被 加载时 对单个文件进行转换 | 在 整个构建过程 中执行更广泛的自定义任务 |
| 作用对象 | 单个模块/文件(如 .js, .css, .png) | 整个编译过程(compiler)或编译结果(compilation) |
| 使用方式 | 在 module.rules 中配置,基于文件类型匹配 | 在 plugins 数组中实例化使用 |
| 典型用途 | 转译代码(Babel)、加载样式、处理图片等 | 生成 HTML、提取 CSS、压缩代码、定义环境变量等 |
| 调用方式 | 函数式,接收源文件内容并返回转换后的内容 | 基于 Webpack 的生命周期钩子(如 emit, done)进行扩展 |
webpack 代码分块实现方式是什么
Webpack 的代码分块(Code Splitting) 是一种将代码拆分成多个较小 bundle(或 chunk)的技术,目的是减少初始加载体积、提升页面加载速度、实现按需加载。它在大型应用中尤为重要。
1. 入口起点(Entry Points)手动分割
通过配置多个 entry,手动指定不同的入口文件,从而生成多个 bundle。
// webpack.config.js
module.exports = {
entry: {
main: './src/main.js',
vendor: './src/vendor.js'
},
output: {
filename: '[name].[contenthash].js',
path: path.resolve(__dirname, 'dist')
}
};
✅ 优点:简单直接
❌ 缺点:容易重复打包(如两个入口都引用了 lodash),无法自动去重
2. SplitChunksPlugin(推荐)—— 自动代码分割
Webpack 4+ 内置了 SplitChunksPlugin,可自动识别重复依赖并提取公共代码(如 node_modules 中的第三方库)。
默认行为(mode: 'production' 时启用):
- 自动提取 node_modules 中的模块到
vendorschunk - 提取被多个 chunk 共享的模块
- 新建 chunk 需满足最小体积(默认 ≥ 20KB)
自定义配置示例:
// webpack.config.js
module.exports = {
optimization: {
splitChunks: {
chunks: 'all', // 对所有类型的 chunk(包括动态和静态)生效
cacheGroups: {
// 提取第三方库
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
},
// 提取项目公共代码
common: {
minChunks: 2, // 至少被 2 个 chunk 引用
name: 'common',
chunks: 'all',
}
}
}
}
};
✅ 优点:自动去重、灵活配置、避免重复加载
📌 这是目前最主流、最高效的代码分割方式
3. 动态导入(Dynamic Imports)—— 按需加载
使用 ES6 的 import() 语法(符合 TC39 提案),实现懒加载(Lazy Loading) 或路由级代码分割。
示例(React 中常用):
// 动态导入组件
const LazyComponent = React.lazy(() => import('./LazyComponent'));
// 或普通 JS
button.addEventListener('click', async () => {
const { heavyFunction } = await import('./heavy-module.js');
heavyFunction();
});
Webpack 会为每个 import() 创建一个独立的 chunk,并在运行时按需加载。
✅ 优点:真正实现“用到才加载”,极大优化首屏性能
💡 配合React.lazy+Suspense可实现组件级懒加载
总结
| 方式 | 适用场景 | 是否自动 | 是否按需 |
|---|---|---|---|
| 多 Entry | 简单多页应用 | ❌ 手动 | ❌ 全量加载 |
| SplitChunksPlugin | 提取公共/第三方代码 | ✅ 自动 | ❌(但减少重复) |
| 动态 import() | 路由、功能模块懒加载 | ✅ 自动分块 | ✅ 按需加载 |
💡 现代前端工程推荐组合:
SplitChunksPlugin(处理 vendor + common) +动态 import()(实现懒加载) = 最佳性能方案
2. Tree Shaking
Tree Shaking 是一种 JavaScript 优化技术,用于去除 JavaScript 中的未使用的代码。
Tree Shaking 的实现依赖于两个关键前提:
1. 使用 ES6 模块(ESM)语法
import/export是静态的(在编译时就能确定依赖关系)- 对比:CommonJS(
require())是动态的,无法在构建时分析哪些代码被使用
✅ 支持 Tree Shaking:
import { foo } from 'lib';
export const bar = () => {};❌ 不支持 Tree Shaking:
const lib = require('lib'); // 动态,运行时才确定
if (condition) exports.baz = ...;
2. 构建工具进行静态分析 + 压缩器删除无用代码
Webpack(或其他打包工具如 Rollup、Vite)的 Tree Shaking 分为两步:
步骤 1️⃣:标记(Marking)—— 静态依赖分析
- Webpack 在构建时解析所有 ES6 模块
- 构建 依赖图(Dependency Graph)
- 标记出哪些导出(exports)被其他模块 实际使用(used exports)
- 未被使用的导出被标记为 “unused harmony export”
这一步由 Webpack 的 "UsedExportsPlugin" 完成(内置)
步骤 2️⃣:删除(Dropping)—— 压缩阶段移除死代码
- Webpack 本身不直接删除代码,而是依赖 Terser(或 UglifyJS)等压缩工具
- 在
mode: 'production'下,Webpack 自动启用 TerserPlugin - Terser 根据 Webpack 提供的“未使用标记”,安全地删除 dead code
🔍 注意:如果未启用压缩(如开发环境),Tree Shaking 不会生效!
三、如何确保 Tree Shaking 生效?
✅ 必要条件:
- 使用 ES6 模块语法(不能混用 CommonJS)
- 打包模式为 production(或手动配置 Terser)
- 第三方库提供 ESM 版本(查看是否有
module或esm字段)
⚠️ 常见破坏 Tree Shaking 的情况:
| 问题 | 说明 |
|---|---|
使用 import * as _ from 'lodash' | 全量导入,无法摇掉 |
| 库只提供 CommonJS 版本 | 如 lodash(非 lodash-es) |
| 副作用代码(side effects) | Webpack 默认认为模块可能有副作用,不敢删除 |
解决副作用问题:
在 package.json 中声明 "sideEffects": false,告诉 Webpack 该模块无副作用,可安全摇树:
{
"name": "my-lib",
"sideEffects": false
}
或指定哪些文件有副作用:
{
"sideEffects": ["*.css", "./polyfill.js"]
}
总结
- Tree Shaking = 静态 ES6 模块分析 + 压缩器删除未使用代码
- 核心前提:使用
import/export+ 无副作用 + 生产构建 - 目的:减小 bundle 体积,提升加载速度
- 最佳实践:
- 使用
lodash-es而非lodash - 避免
import * - 在库中设置
"sideEffects": false
- 使用
3. DllPlugin 和 DllReferencePlugin 有什么区别和联系
DllPlugin是一个用于创建动态链接库(Dynamic Link Library)的插件。它的作用是将一些不经常变更的代码打包成一个单独的动态链接库,以便在构建过程中能够被重复利用,从而提升构建速度。DllReferencePlugin用于在项目的 Webpack 配置中引用动态链接库,以便在构建时重用其中的模块。DllReferencePlugin会根据DllPlugin生成的 manifest 文件,告诉 Webpack 在构建时去哪里找到动态链接库。
具体来说,工作流程如下:
- 将一组模块打包成一个单独的动态链接库。
- 生成一个 manifest 文件,记录了模块的映射关系和路径。
- 在 webpack 配置中使用
DllReferencePlugin引入这个 manifest 文件,告诉 Webpack 在构建时去哪里找到这个动态链接库。 - 当 Webpack 构建时,它会根据 manifest 文件中的映射关系去动态链接库中寻找对应的模块,从而避免了对这些模块的重复打包工作,提升了构建速度。
总的来说,DllPlugin 的作用是优化 Webpack 构建过程,通过将不经常变更的代码提取成动态链接库,减少了重复打包的时间,提高了构建效率。