面试官:对比下webpack和vite

114 阅读6分钟

1. 开发模式(dev)下的核心机制

Webpack

  • 核心逻辑Bundle Everything 打包一切
    • 依赖图构建:启动时要打包所有模块JS/CSS/图片等)。从入口文件(如 index.js)开始,递归地查找分析所有依赖(通过 import/require),生成一个依赖关系图。
    • 转换与打包:通过 Loader 将非 JS 文件(如 .css.vue)转换为 JS 可识别的模块。对于需要内联的资源(如 CSS-in-JS),会被合并到 JS Bundle 中;对于需要外部化的资源(如图片),会被输出到构建目录并通过路径引用。最终产物是一个或多个bundles,由 JS Bundle 和其他外部静态资源文件共同组成。
   源文件(.js/.css/.png)  
      → 通过 Loader 转换  
        → 若资源需内联(如 CSS-in-JS):合并到 JS Bundle  
        → 若资源需外部化(如图片):复制到输出目录,JS Bundle 中保留引用路径  
          → 最终产物 = JS Bundle + 外部资源文件
  • 服务启动:通过 webpack-dev-server 提供本地服务,文件修改后重新打包并刷新页面(或 HMR)。
  • 打包流程
  1. 入口分析 → 2. 递归构建依赖图 → 3. Loader 转换非 JS 资源 → 4. 应用插件优化 → 5. 分块(Code Splitting) → 6. 生成 Bundle + 外部资源
  • 问题
    • 项目越大,依赖图越复杂,启动和热更新越慢。
    • 即使修改一个小文件,也可能触发整个 Bundle 的更新(这取决于你的 Split Chunks 如何配置,如公共模块被提取到独立 Chunk 后,多个入口文件依赖它。修改公共模块会导致所有关联的 Chunk,即使代码逻辑未变,Webpack 的模块管理机制(如 __webpack_require__)可能需要更新模块间的引用关系)。

image.png

Vite

  • 核心逻辑按需编译(On-Demand Compilation)。

    • 依赖预构建:启动时仅(使用esbuild) 预构建 node_modules 中的依赖(转换为 ESM 并合并为单个文件,解决 CommonJS 兼容性),存入 node_modules/.vite
    • 源码处理:Vite 只需要在浏览器请求源码时进行转换并按需提供源码,根据情景动态导入代码——浏览器直接请求源码(如 App.vue),Vite 拦截请求,实时编译为浏览器可执行的 ESM(例如将 .vue 文件拆解为 JS/CSS)。
    • 浏览器加载:浏览器通过原生 ESM 动态加载模块,无需打包。
  • 优势

    • 启动时仅处理依赖,源码按需编译,冷启动极快。
    • 修改文件时,仅编译当前文件,HMR 直接更新浏览器中的模块,无需重建 Bundle。

image.png

image.png

2. 模块处理细节

Webpack

  • 模块解析:将非 JS 文件(如 .css.vue)转换为 JS 可识别的模块(例如通过 css-loader 将 CSS 转换为 JS 字符串,再通过 style-loader 注入 DOM)。
  • 打包产物:生成 IIFE(立即执行函数)格式的 Bundle,模块间通过 Webpack 自研的模块系统(如 __webpack_require__)通信。
  • 示例
    一个包含 index.jsutils.jsstyle.css 的项目,打包后可能合并为:
    (function() {
      // Webpack 生成的模块系统
      var __webpack_modules__ = {
        './src/utils.js': (module) => { ... },
        './src/style.css': (module) => { ... },
        './src/index.js': (module) => { ... }
      };
      // 依赖加载逻辑
      function __webpack_require__(moduleId) { ... }
      // 入口执行
      return __webpack_require__('./src/index.js');
    })();
    

Vite

  • 模块保留原生 ESM
    • 浏览器直接加载 import App from '/src/App.vue',Vite 拦截请求,将 App.vue 实时编译为多个 JS/CSS 文件(例如 App.vue?type=templateApp.vue?type=style)。
    • 依赖模块通过预构建的 ESM 直接加载(如 import XX from 'apackage' 指向预构建后的 apackage.js)。
  • 示例
    浏览器实际加载的模块可能是:
    // 编译后的 App.vue 的 JS 部分
    import { createComponent } from 'vue';
    export default createComponent({ ... });
    
    // 浏览器同时会发起对 CSS 的请求:/src/App.vue?type=style
    

3. 热更新(HMR)机制

Webpack

  • 流程
    1. 代码修改后,Webpack 重新构建受影响模块的依赖链。
    2. 通过 WebSocket 通知浏览器旧的模块 Hash 和新的模块代码。
    3. 浏览器替换旧模块,并重新执行相关代码(可能触发整个组件树的重新渲染)。
  • 缺点
    • 模块替换依赖 Webpack 的运行时逻辑,可能触发不必要的全局更新。
    • 大型项目中,HMR 延迟明显(需重新构建依赖链)。

Vite

  • 流程
    1. 代码修改后,Vite 仅编译当前文件(如 Button.vue)。
    2. 通过 WebSocket 通知浏览器模块更新。
    3. 浏览器直接通过 ESM 重新请求新模块,替换旧模块(例如 import('/src/Button.vue?t=1623393025840'))。
  • 优势
    • 依赖浏览器原生 ESM,模块替换更精准(如 Vue/React 组件可保持状态更新)。
    • 无 Bundle 重建,HMR 速度接近原生。

4. 生产构建的差异

Webpack

  • 打包策略
    • 通过 optimization.splitChunks 拆分代码(如按入口、动态导入、公共依赖)。
    • 支持多种优化插件(如 TerserPlugin 压缩代码,MiniCssExtractPlugin 提取 CSS)。
  • 优势
    • 成熟的生产优化方案(如长期缓存、懒加载)。
    • 可通过复杂配置实现极致优化。

Vite

  • 默认使用 Rollup
    • Rollup 天然支持 ESM 打包,Tree Shaking 更高效(未使用的代码更易识别)。
    • 配置继承自开发环境,减少重复(如 vite.config.ts 直接定义生产构建行为)。
  • 优化方向
    • 自动代码分割(基于动态导入)。
    • 支持 @vitejs/plugin-legacy 生成旧浏览器兼容代码(通过 nomodule 属性)。

5. 对比

场景WebpackVite
打包机制基于bundle 启动时打包所有模块ES模块 按需编译 启动更快
开发服务器构建webpack自己的一套打包流程开发环境用esbuild预构建 浏览器支持原生ESM 热更新更快; 生产环境还是用Rollup可能两者差不多
配置和生态配置较复杂 生态更完善 可使用的插件更多配置相对简单
HMR需要重新打包部分模块 速度有影响即时生效

6. 其他功能对比

功能WebpackVite
CSS 处理需配置 css-loader + style-loader内置支持,直接 import './app.css'
TypeScriptts-loaderbabel-loader开箱即用(基于 esbuild 转译)
Monorepo需配合 lerna 或自定义配置内置支持(通过 workspaces 配置)
SSR需手动配置模块加载逻辑内置支持, 提供 vite-ssr 等轻量化方案

7. 如何选择?

  • 用 Webpack 的情况

    • 需要兼容 IE11 或旧版 Safari(Vite 生产依赖现代浏览器 Polyfill)。
    • 项目重度依赖 Webpack 特有插件(如 DLLPlugin 或自定义 Loader)。
    • 需要精细控制打包流程(如特殊代码拆分逻辑)。
  • 用 Vite 的情况

    • 新项目,技术栈为 Vue/React/Svelte 等现代框架。
    • 开发阶段追求极致速度 尤其是大型项目。
    • 希望减少配置复杂度 快速上手。

总结

  • Vite 本质是“下一代前端工具链”,通过浏览器原生 ESM 和按需编译,解决开发阶段的性能瓶颈。
  • Webpack 是“工业化打包方案”,适合需要深度定制和兼容性的场景。

如果想彻底理解差异,可以尝试以下实验:

  1. 分别用 Webpack 和 Vite 创建一个空项目,观察 node_modules 体积和启动时间。
  2. 在两者中引入一个大型依赖(如 lodash-es),对比热更新速度。
  3. 检查生产构建的产物结构(Webpack 的 Chunk 分割 vs Vite 的 Rollup 输出)。