跳到主要内容

常见面试题

介绍一下 webpack

Webpack 是一个现代 JavaScript 应用程序的静态模块打包工具(module bundler)。它的核心作用是将项目中各种资源(如 JavaScript、CSS、图片、字体等)视为模块,并根据它们之间的依赖关系,构建出一个或多个优化后的静态资源包(bundle),供浏览器加载使用。


一、Webpack 的作用

  1. 模块打包:将多个模块(包括 ES6 模块、CommonJS、AMD 等)打包成一个或多个 bundle。
  2. 依赖管理:自动分析模块间的依赖关系,按需加载。
  3. 代码转换:通过 loader 将非 JavaScript 资源(如 TypeScript、SCSS、图片等)转换为 JavaScript 模块。
  4. 代码优化:支持代码分割(code splitting)、懒加载、Tree Shaking(去除未使用代码)、压缩混淆等。
  5. 开发效率提升:提供开发服务器(webpack-dev-server)、热更新(HMR)、Source Map 等功能。
  6. 插件扩展:通过 plugin 实现更复杂的构建逻辑,如 HTML 自动生成、环境变量注入、资源压缩等。

二、Webpack 的主要组成部分

  1. Entry(入口)
    指定打包的起点模块。Webpack 会从这个文件开始递归解析依赖。

  2. Output(出口)
    配置打包后文件的输出路径和文件名。

  3. Loader(加载器)
    用于对模块的源代码进行转换。例如:

    • babel-loader:将 ES6+ 转为 ES5
    • css-loader + style-loader:处理 CSS 文件
    • file-loader / url-loader:处理图片、字体等静态资源
  4. Plugin(插件)
    用于执行更广泛的任务,比如打包优化、环境变量定义、HTML 文件生成等。例如:

    • HtmlWebpackPlugin:自动生成 HTML 并自动引入 bundle
    • MiniCssExtractPlugin:将 CSS 提取到单独文件
    • DefinePlugin:定义全局常量(如 process.env.NODE_ENV
  5. Mode(模式)
    可设为 developmentproduction,Webpack 会自动启用相应的优化配置。

  6. Resolve(解析规则)
    配置模块如何被解析,例如别名(alias)、扩展名(extensions)等。

  7. DevServer(开发服务器)
    提供本地开发服务器,支持热更新、自动刷新等。


三、Loader 和 Plugin 的区别

特性LoaderPlugin
作用时机在模块被 加载时 对单个文件进行转换整个构建过程 中执行更广泛的自定义任务
作用对象单个模块/文件(如 .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 中的模块到 vendors chunk
  • 提取被多个 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 生效?

✅ 必要条件:

  1. 使用 ES6 模块语法(不能混用 CommonJS)
  2. 打包模式为 production(或手动配置 Terser)
  3. 第三方库提供 ESM 版本(查看是否有 moduleesm 字段)

⚠️ 常见破坏 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 在构建时去哪里找到动态链接库。

具体来说,工作流程如下:

  1. 将一组模块打包成一个单独的动态链接库。
  2. 生成一个 manifest 文件,记录了模块的映射关系和路径。
  3. 在 webpack 配置中使用DllReferencePlugin引入这个 manifest 文件,告诉 Webpack 在构建时去哪里找到这个动态链接库。
  4. 当 Webpack 构建时,它会根据 manifest 文件中的映射关系去动态链接库中寻找对应的模块,从而避免了对这些模块的重复打包工作,提升了构建速度。

总的来说,DllPlugin 的作用是优化 Webpack 构建过程,通过将不经常变更的代码提取成动态链接库,减少了重复打包的时间,提高了构建效率。