☰
Webpack → Vite / Rspack 迁移:高阶面试题整理
2026/10/3 7:24:42 网站建设 项目流程

面试题 1:Webpack 迁移到 Vite / Rspack,真正难在哪里?

核心思路(一句话)

迁移不是“换配置”,而是评估模块规范、Loader/Plugin 生态、运行时行为、构建产物和 CI 基建的兼容性。

结构图

Webpack │ ├─ Loader / Plugin ├─ CommonJS / ESM ├─ require.context ├─ 环境变量 ├─ CSS / Less / Sass ├─ 多入口 ├─ Source Map └─ CI / 产物 / 监控 │ ▼ 迁移兼容性评估 │ ┌────┴────┐ ▼ ▼ Vite Rspack │ │ 原生 ESM Webpack 兼容生态 开发模式 Rust 高性能实现

一、第一步不是迁移,而是盘点

重点检查:

维度需要检查什么
模块CommonJS、ESM、动态require
Webpack APIrequire.context等
Loader自定义 Loader、特殊 Loader
PluginWebpack 专属 Plugin
CSSLess/Sass 全局变量、Loader 配置
环境变量process.env等
多入口MPA、多页面配置
产物文件名、目录结构、HTML 注入
Source Map错误监控上传
CI/CD构建命令、缓存、部署流程

主要矛盾:构建生态兼容性。

次要矛盾:配置语法差异。

所以:

配置改写通常不是最难的,真正困难的是 Webpack 生态和项目代码对 Webpack 特有能力的依赖。


面试题 2:Webpack 为什么迁移到 Vite 后,开发环境会明显变快?

核心思路(一句话)

Webpack 开发模式通常需要先构建模块依赖图,而 Vite 开发模式利用浏览器原生 ESM 按需提供模块,并对依赖进行预构建。

传统 Webpack

源码 ↓ 解析依赖 ↓ 构建 Module Graph ↓ Loader ↓ Plugin ↓ 生成 Bundle ↓ 浏览器

项目越大,初始构建涉及的模块越多。

Vite 开发模式

浏览器请求页面 ↓ 原生 ESM ↓ 按需请求模块 ↓ Vite 转换当前模块 ↓ 浏览器执行

同时:

node_modules ↓ 依赖预构建 ↓ CommonJS → ESM 等处理 ↓ 缓存

因此 Vite 的核心优势不是简单的“代码比 Webpack 快”,而是:

开发阶段减少了传统 Bundle 全量构建的成本。


面试题 3:Webpack → Vite 迁移,常见坑有哪些?

1. 环境变量

Webpack 项目常见:

process.env.NODE_ENVprocess.env.API_URL

Vite 常见:

import.meta.env.MODEimport.meta.env.VITE_API_URL

例如:

if(import.meta.env.DEV){console.log('development');}

注意:

不能简单认为 Vite 会自动把所有process.env替换掉。

尤其是第三方依赖中的:

process.env.NODE_ENV

需要结合依赖兼容策略处理。


2. CommonJS 依赖

老项目中经常存在:

constlodash=require('lodash');

或者依赖包本身就是 CommonJS。Vite 开发环境通常会通过依赖预构建处理 CommonJS 依赖,但不能理解成:

“Vite 只支持 ESM。”

更准确地说:

项目源码 │ ├── ESM │ ↓ │ 原生处理 │ └── CommonJS 依赖 ↓ 依赖预构建 ↓ ESM

因此迁移时重点检查:

  • CommonJS 依赖
  • 动态require
  • 非标准模块行为
  • Node.js 内置模块依赖

面试题 4:require.context为什么是 Webpack → Vite 迁移的大坑?

核心思路(一句话)

require.context是 Webpack 提供的编译时模块上下文 API,而 Vite 没有直接等价 API。

Webpack:

constmodules=require.context('./components',true,/\.js$/);modules.keys().forEach(key=>{constmodule=modules(key);});

Webpack 会在构建阶段分析:

./components │ ├── A.js ├── B.js ├── C.js └── D.js

然后构造一个模块上下文。


Vite 的对应方案

通常使用:

import.meta.glob('./components/*.js')

例如:

constmodules=import.meta.glob('./components/*.js');

得到的是:

{'./components/A.js':()=>import('./components/A.js'),'./components/B.js':()=>import('./components/B.js'),'./components/C.js':()=>import('./components/C.js')}

如果希望直接加载:

constmodules=import.meta.glob('./components/*.js',{eager:true});

关键区别

Webpack require.context() ↓ Webpack 专属 API ↓ 构建阶段生成模块上下文 Vite import.meta.glob() ↓ Vite 专属编译能力 ↓ 构建阶段展开 glob

所以不能说:

“Vite 没有 require.context,所以全部手动改 import。”

更准确的回答是:

需要识别require.context的实际业务语义,再使用import.meta.glob、显式 import 或其他方案重构。


面试题 5:如果项目里有 200 个require.context,怎么办?

这是非常好的架构师追问。

错误答案

全部改成import.meta.glob。

因为这只是机械替换,没有解决迁移成本问题。

正确思路

200 个 require.context ↓ 统计使用场景 ↓ 分类 ┌──────┼────────┐ ▼ ▼ ▼ 组件扫描 路由扫描 自动注册 └──────┼────────┘ ↓ 设计统一迁移方案 ↓ 批量 Codemod / AST 转换 ↓ 测试行为一致性

例如可以通过AST/Codemod自动识别:

require.context('./components',true,/\.vue$/)

再生成:

import.meta.glob('./components/**/*.vue')

真正体现工程化能力的是:

不是“我知道怎么改”,而是“我知道怎么低成本批量迁移”。


面试题 6:Rspack 为什么更容易从 Webpack 迁移?

核心思路

Rspack 的核心目标之一就是保持较高的 Webpack 生态兼容性,同时使用 Rust 实现核心构建能力。

架构可以理解成:

Webpack 项目 │ ├── webpack.config.js ├── Loader ├── Plugin └── Webpack API │ ▼ Rspack │ Rust 核心实现 │ ▼ 更快的构建性能

因此对于大型 Webpack 项目:

Webpack → Rspack

通常比:

Webpack → Vite

更容易保持原有构建模型。

但这里一定要注意:

“兼容 Webpack” ≠ “100% 所有 Loader/Plugin 都兼容”。

迁移前仍然需要验证:

  • Loader
  • Plugin
  • Webpack API
  • 自定义构建逻辑
  • Module Federation
  • CSS
  • HTML
  • Source Map
  • CI/CD

面试题 7:Webpack → Rspack 最大的迁移优势是什么?

一句话

不是因为 Rspack 配置长得像 Webpack,而是因为它尽量保持 Webpack 的模块、Loader、Plugin 和配置模型,从而降低迁移成本。

尤其是大型项目:

Webpack │ ├── 复杂 Loader ├── Plugin ├── 自定义构建逻辑 └── Webpack 生态 │ ▼ Rspack │ 尽量复用现有体系

这和 Vite 的思路不同:

Webpack → Vite

更多是:

重新适配构建模型。

而:

Webpack → Rspack

更接近:

保留构建模型,替换底层实现。


面试题 8:CommonJS 和 ESM 的 Tree Shaking 本质区别是什么?

这是整段内容里最值得掌握的核心题。

核心思路(一句话)

Tree Shaking 的关键不是“ESM 天生能摇树”,而是 ESM 的导入导出关系具有静态结构,构建工具可以在编译阶段确定模块依赖和未使用导出。


ESM

// utils.jsexportfunctionadd(){}exportfunctionsub(){}exportfunctionmultiply(){}
import{add}from'./utils.js';add();

构建工具可以分析:

utils.js ├── add ← 使用 ├── sub ← 未使用 └── multiply ← 未使用

于是:

保留 add 删除 sub 删除 multiply

CommonJS

constutils=require('./utils');utils.add();

甚至可以:

constmoduleName=getModuleName();constmodule=require(moduleName);

依赖路径可能直到运行时才能确定。

因此:

ESM 静态 import/export ↓ 编译阶段分析 ↓ 确定依赖关系 ↓ Tree Shaking CommonJS 动态 require ↓ 依赖关系可能运行时确定 ↓ 静态分析困难 ↓ Tree Shaking 受限

面试题 9:是不是 CommonJS 就完全不能 Tree Shaking?

CommonJS 的动态特性使可靠、完整的静态 Tree Shaking 很困难,但现代构建工具可以对部分 CommonJS 代码进行静态分析或转换,从而获得一定程度的优化。

因此面试不要回答:

CommonJS 不能 Tree Shaking。

应该回答:

ESM 的静态模块结构天然适合 Tree Shaking;CommonJS 由于require()的动态性,静态分析能力受限。构建工具可以通过 CommonJS → ESM 转换和静态分析处理部分场景,但优化能力和可靠性通常不如原生 ESM。

这才是准确的高阶答案。


面试题 10:Tree Shaking 真正依赖哪些条件?

① 模块必须具有可静态分析的结构

优先:

import{foo}from'./utils.js';

而不是:

require(variable);

② 构建工具需要正确识别副作用

例如:

{"sideEffects":false}

它表示:

模块可以被认为没有副作用,因此未使用的模块代码可以安全删除。

但要注意:

sideEffects: false不是 Tree Shaking 的必要条件。

它主要帮助构建工具进行模块级副作用判断。


③ 代码本身必须允许删除

例如:

exportfunctionfoo(){}exportfunctionbar(){console.log('bar');}

如果:

import{foo}from'./utils.js';

那么bar可能被删除。

但如果模块存在:

import'./register.js';

而register.js执行:

window.xxx=...

它就存在副作用,不能简单删除。


面试题 11:sideEffects: false和 Tree Shaking 是什么关系?

这是很容易被问深的问题。

{"sideEffects":false}

本质是在告诉构建工具:

这个包中的模块 如果没有被使用 可以认为不存在必须保留的顶层副作用

例如:

// polyfill.jsArray.prototype.foo=function(){};

这种代码明显具有副作用。如果错误配置:

{"sideEffects":false}

可能导致构建工具把:

import'./polyfill.js';

错误地认为可以删除。

所以:

sideEffects: false配错可能导致运行时功能丢失。


面试题 12:为什么 CommonJS 依赖会影响 Tree Shaking?

假设:

// CommonJSmodule.exports={foo,bar,baz};

应用:

const{foo}=require('./utils');

构建工具很难像 ESM 一样直接利用:

exportfooexportbarexportbaz

这样的静态导出关系。而 ESM:

export{foo};export{bar};export{baz};

模块导出关系在语法层面就是明确的。所以:

ESM ↓ 静态 Module Graph ↓ Exports Analysis ↓ Used Exports Analysis ↓ Dead Code Elimination ↓ Tree Shaking

这是面试时最值得讲的完整链路。


面试题 13:Barrel Export 为什么可能影响 Tree Shaking?

例如:

// index.jsexport*from'./a';export*from'./b';export*from'./c';export*from'./d';

业务代码:

import{foo}from'./index.js';

现代构建工具通常仍然能够进行 Tree Shaking,不能简单说 Barrel Export 一定会导致 Tree Shaking 失效。

但 Barrel Export 可能:

  • 增加模块图复杂度
  • 增加分析成本
  • 让依赖关系更加间接
  • 在某些工具链、CommonJS 混用等情况下削弱优化效果

所以面试最好说:

Barrel Export 不是 Tree Shaking 失效的必然原因,但过度使用会增加模块图复杂度,在复杂依赖和 CommonJS 混用场景下可能影响优化效果。


最后一个架构师级问题:Webpack → Vite / Rspack 到底怎么做?

可以直接背这套:

① 现状评估 ↓ ② 统计 Webpack 特性使用情况 ↓ ③ 检查 CommonJS / ESM ↓ ④ 检查 Loader / Plugin ↓ ⑤ 检查 require.context 等 Webpack API ↓ ⑥ 检查环境变量 / CSS / 多入口 ↓ ⑦ 检查 CI / Source Map / 产物 ↓ ⑧ 建立兼容性清单 ↓ ⑨ 小范围 POC ↓ ⑩ 性能 + 产物 + 行为对比 ↓ ⑪ 批量迁移 / Codemod ↓ ⑫ 灰度验证 ↓ ⑬ 切换构建链

面试“满分答案”

Webpack 迁移到 Vite 或 Rspack,我不会先改配置,而是先做兼容性和收益评估。

第一,看项目是否依赖 Webpack 特有能力,比如require.context、自定义 Loader、Plugin,以及大量 CommonJS 依赖。

第二,看目标工具的构建模型。Vite 开发环境核心是浏览器原生 ESM + 依赖预构建,因此开发体验和 Webpack 的 Bundle 模型存在明显差异;Rspack 则更强调 Webpack 生态兼容,通常更适合大型存量 Webpack 项目渐进迁移。

第三,重点验证环境变量、CommonJS、动态require、CSS、Source Map、多入口、CI/CD 和构建产物。

**其中最容易被低估的是 CommonJS 和 Webpack 特有 API。**例如require.context没有直接等价物,需要根据业务场景改造成import.meta.glob或其他模块发现方案;如果有大量调用,还应该考虑通过 AST/Codemod 批量迁移,而不是人工修改。

Tree Shaking 方面,核心不是简单记忆“ESM 能摇、CommonJS 不能摇”,而是理解背后的原因:ESM 的import/export具有静态结构,构建工具可以在编译阶段建立 Module Graph、分析 Used Exports,再进行 Dead Code Elimination;CommonJS 的require()可以动态执行,依赖关系可能运行时才能确定,因此静态分析和 Tree Shaking 能力受到限制。

最后我会通过 POC 对比构建速度、开发启动、HMR、产物体积、运行时行为、Source Map、内存和 CI 稳定性,确认迁移收益覆盖迁移成本后,再逐步切换。

所以,构建工具迁移的本质不是“把 webpack.config.js 改成另一种配置”,而是一次构建体系迁移。

真正需要记住的 5 个点

1. Vite ≠ Webpack 换皮 → 开发构建模型不同 2. Rspack → 尽量兼容 Webpack 生态 → 降低存量项目迁移成本 3. require.context → Webpack 特有能力 → Vite 没有直接等价 API → import.meta.glob 是常见替代方案 4. ESM Tree Shaking → 核心是“静态可分析” → Module Graph → Used Exports → Dead Code Elimination 5. CommonJS → 不是“绝对不能 Tree Shaking” → 而是动态 require 限制静态分析 → 转 ESM 后可能获得更多优化机会

这 5 个点串起来,基本就从“会配置 Webpack”提升到了真正理解构建工程化迁移的层次。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询