返回首页

更多 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 新项目

面试要点总结

高频考点

  1. Tree Shaking:基于 ES Modules 静态特性,删除未引用代码
  2. Code Splitting:入口分割、动态导入、SplitChunks
  3. Hash 策略:hash vs chunkhash vs contenthash
  4. Loader vs Plugin:文件转换器 vs 编译扩展
  5. 构建优化:缓存、多线程、缩小范围、代码分割

回答技巧

  1. 先讲概念定义,再讲原理,最后讲实践
  2. 结合配置代码说明
  3. 区分开发环境和生产环境的不同策略
  4. 提及 Webpack 5 的新特性(持久化缓存、Asset Modules、Module Federation)