一篇讲完Vite Webpack Rollup Rspack Esbuild

55 阅读11分钟

整体对比

webpack rollup vite rspack esbuild 分别有哪些功能是其他几个没有的?

  • webpack 5: 模块联邦,独有特性
  • rollup: Tree Shaking算法是最彻底的
  • vite: 开发环境基于原生ESM的dev server独有
  • rspack,esbuild: 无功能特殊性,但速度碾压webpack rollup

一、webpack

1.1 先明确几个概念

  • Chunks:Chunk 就是 Webpack 在打包过程中,根据配置和代码依赖关系,划分出来的一个或多个模块的集合,最终会生成一个或多个输出文件(bundle)。
  • bundle: 最终输出的物理文件(如 .js 文件)
  • code splitting: Webpack 的 Code Splitting(代码分割)是一种优化技术,用于将你的 JavaScript 应用程序的代码拆分成多个“块”(chunks),然后按需加载或并行加载这些块,从而提高应用的初始加载速度和运行时性能。
  • loader:Webpack Loader 是 Webpack 生态中用于处理非 JavaScript 模块的工具,它的核心作用是将不同类型的文件(如 CSS、图片、TypeScript 等)转换为 Webpack 能够识别和处理的模块,从而让这些资源可以被纳入 Webpack 的打包流程。
  • plugin: 可以介入 Webpack 打包的整个生命周期(从编译开始到输出结果的全过程),实现各种复杂的功能,比如优化打包结果、资源管理、环境变量注入等。

1.2 打包流程

1.2.1 开发环境

它与生产环境构建的最大区别在于:一切都是在内存中快速完成,而非输出实体文件。 通过一个叫做 ​内存文件系统(In-Memory Filesystem)​ 提供给 dev server 访问

  1. 启动开发服务器(webpack-dev-server) :它会在本地启动一个 HTTP 服务器,并内置了与客户端的通信能力(通常是 WebSocket)。

  2. 内存编译与监听

    • 同样会从入口开始构建依赖图并使用 Loader 转换模块。
    • 关键区别:打包结果不会写入硬盘,而是保存在内存中,速度极快。
    • 同时启动文件监听(Watch Mode) ,持续监听源代码的变化。
  3. 极速热更新(HMR - Hot Module Replacement)

    • 当检测到文件变化时,只重新编译改动的模块及其依赖(增量构建)。
    • 开发服务器通过 WebSocket 连接主动将变更消息推送给浏览器。
    • 浏览器的 HMR Runtime(已被注入到打包的代码中)接收到消息后,动态拉取更新后的代码模块,并替换掉正在运行的老模块,实现页面无刷新更新,保持应用状态。

1.2.2 生产环境

Webpack 的打包流程可以简化为三个核心步骤:

  1. 解析依赖(Dependency Resolution) :从配置的入口(Entry)文件开始,根据代码中的 import/require 语句,递归地构建一个模块依赖图(Dependency Graph),明确所有模块的依赖关系。
  2. 转换与打包(Transformation & Bundling) :依赖图中的每个模块会根据配置的规则(Rules),被对应的 Loader 进行转换(例如,将 TypeScript 转为 JavaScript,Sass 转为 CSS)。然后,Webpack 根据依赖关系和优化策略(如代码分割 Code Splitting),将这些模块组合成一个或多个代码块(Chunks)。
  3. 输出(Output) :最后,将生成的 Chunks 转换成最终的文件(Assets),并根据配置的输出(Output)路径和文件名,写入到指定的目录(通常是 dist 或 build)中。

image.png

1.3 HMR工作原理

如何在webpack.config中启用:


1.3.1 核心实现

webpack dev server + websocket + hmr plugin + hmr runtime

HMR流程示意图

1.3.2 工作流程

  1. 建立通道webpack-dev-server (WDS) 启动后,会与浏览器客户端建立一个 WebSocket 长连接,用于后续的实时通信。

  2. 监听与编译:WDS 监听文件系统的变化。当您修改并保存一个文件时,Webpack 立即增量编译这些更改的模块,并生成一个描述变化的 manifest (一个 JSON 文件) 和更新的 chunk (JS 代码)。

  3. 消息通知:WDS 通过之前建立的 WebSocket 连接,将本次更新的 Hash 值主动推送给浏览器。

  4. 请求更新:浏览器端的 HMR Runtime(在初次编译时已注入到 bundle 中)收到通知后,会根据 manifest 信息,通过 JSONP 向 WDS 请求新增的或发生变化的 chunk 模块。

  5. 应用更新:这是最关键的步骤。HMR Runtime 获取到新的模块代码后,会检查两方面:

    • 当前更新的模块是否接受了更新(即代码中是否有 module.hot.accept 相关逻辑)。
    • 父模块是否接受了它的更新。
      如果,则直接替换掉旧的模块,并执行可能的回调函数。如果,HMR 会向上冒泡,直到找到一个接受更新的父模块。如果一直冒泡到入口点都没有处理,则会回退到刷新整个浏览器页面(live reload)
HMR Runtime

HMR Runtime 作为客户端和服务器之间的桥梁,实现了无需完全刷新页面即可更新模块的能力。 HMR Rumtime 主要工作:

  • 与 Webpack Dev Server 建立和维护 WebSocket 连接
  • 接收服务器发出的更新通知
  • 下载更新后的模块代码
  • 替换应用程序中的旧模块
  • 执行模块的 accept 处理函数
HotModuleReplacementPlugin主要作用

HotModuleReplacementPlugin主要作用: 一个常见的误区:仅仅在 webpack-dev-server 配置中设置 hot: true 会自动添加这个插件。也就是说,hot: true 的本质就是让 Webpack 自动帮你 new HotModuleReplacementPlugin()

总结一句话HotModuleReplacementPlugin 是 HMR 的客户端运行时注入器,它将处理热更新所需的“智能”和“方法”添加到打包后的代码中,使得浏览器端的模块能够被安全地替换和更新。注入 HMR Runtime****提供 HMR API

对于绝大多数基于 Vue 或 React 等现代框架开发的项目,HMR 是开箱即用的。背后的魔法是由 vue-loaderreact-refresh 等工具完成的,您无需在自己的每个业务组件或模块中编写重复的 module.hot.accept 代码。您只需要在需要有特殊更新逻辑的地方才手动介入。

1.4 Tapable

Tapable 是 Webpack 的核心底层库,它提供了一个强大的发布-订阅事件钩子机制。你可以把它理解为 Webpack 的事件流和插件引擎,是整个 Webpack 的“神经中枢”,负责连接和驱动整个编译打包过程。

1.4.1 Tapable 常见的钩子类型

Tapable 提供了多种钩子来控制插件的执行逻辑,这是它的强大之处:

  • SyncHook(同步钩子) :最基本的钩子,同步执行所有注册的回调。
  • SyncBailHook(同步熔断钩子) :如果某个回调返回了非 undefined 的值,则停止后续所有回调的执行。
  • SyncWaterfallHook(同步瀑布钩子) :将上一个回调的返回值,作为参数传递给下一个回调。
  • AsyncSeriesHook(异步串行钩子) :异步地、按顺序执行所有回调。
  • AsyncParallelHook(异步并行钩子) :异步地、并行执行所有回调。

1.4.2 在 Webpack 中的体现

  • Compiler Hooks:代表整个 Webpack 实例,生命周期钩子如 beforeRunrunemitdone
  • Compilation Hooks:代表一次单独的编译过程,处理模块依赖、优化等,钩子如 buildModuleoptimizeChunks

1.5 loader原理

本质上是一个转换器,负责将非 JavaScript 模块转换为 Webpack 能够处理的 JavaScript 模块。

  1. 识别文件类型:Webpack 根据配置中的 test 正则规则匹配文件
  2. 应用 Loader 链:按照从右到左/从下到上的顺序应用多个 Loader
  3. 转换为 JavaScript:每个 Loader 对文件内容进行转换
  4. 纳入依赖图:最终输出的 JavaScript 被加入到 Webpack 的依赖图中

image.png

image.png

1.6 plugin原理

Webpack Plugin 的本质是:通过 Tapable 提供的钩子机制,在 Webpack 构建的特定生命周期阶段执行自定义逻辑,从而扩展构建功能

1.7 关于source map

1.8 dynamic Imports动态导入

Dynamic Imports(动态导入)是 ECMAScript 2020 中引入的一个重要特性,它允许开发者在运行时按需加载 JavaScript 模块。 在代码执行到该语句时才会开始加载模块,返回一个 ​Promise

Webpack 会:

  1. 为动态导入的模块创建单独的 chunk
  2. 在需要时通过 JSONP 方式异步加载这些 chunk
  3. 执行加载的代码并将模块导出

rollup也支持dynamic imports

require.ensure 是 ​Webpack 特有的旧版 API,用于以 ​CommonJS 风格 实现 ​动态加载模块

1.9 Tree Shaking

Tree Shaking 是指在前端打包过程中,通过静态分析技术移除 JavaScript 上下文中未引用代码(dead-code)  的优化技术。 webpack如何设置? (rollup默认就Tree Shaking)

对于被标记为有副作用的模块(如 CSS 文件),Webpack 会跳过静态分析阶段,不会尝试移除这些模块中"看似未使用"的代码。它会假设整个模块都是必需的。

1.10 模块联邦

模块联邦让一个应用能够直接使用另一个应用暴露的模块,就像使用本地模块一样,但这些模块实际上是从远程服务器加载的。 最常用的场景是微前端架构。

image.png

二、Rollup

  • 设计理念:以 ESM 为核心,利用 ESM 的静态分析特性实现更高效的打包(如 Tree-Shaking),输出更简洁、冗余更少的代码。

  • Tree-Shaking:利用 ESM 的静态导入 / 导出特性,在打包时自动移除未使用的代码(死代码),减小输出体积。

    • 仅对 ESM 有效,对 CommonJS 模块支持有限(因 CommonJS 依赖运行时动态逻辑,无法静态分析)。
  • 输出多种模块格式:可同时输出 ESM、CommonJS、UMD、AMD 等格式,满足不同场景需求(如库需要支持多种引入方式)。

    • 常见格式:es(ESM)、cjs(CommonJS)、umd(通用模块定义,支持浏览器全局变量和 AMD)、iife(立即执行函数,适合浏览器环境)。
  • 原生支持 ESM:优先处理 ESM 模块,对 ESM 的依赖解析、导入导出处理更自然。

  • 插件化机制:通过插件扩展功能(如处理非 JS 文件、转换语法、优化输出等),核心插件生态包括:

    • @rollup/plugin-babel:集成 Babel 转换 ES6+ 语法。
    • @rollup/plugin-node-resolve:解析 node_modules 中的第三方模块(Rollup 默认不处理 node_modules)。
    • @rollup/plugin-commonjs:将 CommonJS 模块转换为 ESM,以便 Rollup 处理。
    • rollup-plugin-terser:压缩输出代码。

三、vite

作者:youyuxi

开发环境 浏览器原生支持ESM模块,生产环境使用rollup打包

1.1 编译流程

  1. 启动服务器与依赖预构建
    运行 vite 或 vite dev 后,Vite 会启动一个开发服务器8。Vite 会使用 esbuild 对你项目中的第三方依赖(如 lodashreact)进行一次性的预构建5。这是因为许多第三方包可能是 CommonJS 格式或存在大量内部导入,预构建可以将它们转换为单一的 ESM 模块,并缓存到 node_modules/.vite 目录下5。这减少了浏览器大量的请求,提升了后续加载性能。
  2. 浏览器请求与按需编译
    当你通过浏览器访问应用时,浏览器会先获取 index.html8。在解析 index.html 时,遇到类型为 module 的 script 标签(如 <script type="module" src="/src/main.js"></script>),浏览器会向 Vite 开发服务器发起对这些模块的请求。对于遇到的每一个模块请求(如 .vue.ts.jsx.css 等),Vite 都会在服务器端按需进行实时编译转换,转换成浏览器能够直接执行的 JavaScript58。Vite 内部通过插件容器 (PluginContainer)  机制来调度和执行一系列编译插件(例如处理 Vue/SFC、CSS、TypeScript 的插件),完成这些转换工作3。
  3. 热更新 (HMR)
    当你修改代码时,Vite 通过 HMR 机制只编译变更的模块5,并通过 WebSocket 连接将更新推送给浏览器1。浏览器随后请求新的模块,应用更新而无需完全刷新页面,保持了应用状态。

依赖预构建

解决依赖格式不统一依赖模块碎片化:的问题。

作用:

  1. 将 CommonJS 依赖转换为 ESM,确保浏览器兼容;
  2. 合并碎片化模块,减少请求次数;
  3. 通过缓存机制提升启动速度。

?vite 如何规避 开发和生产环境不一致带来的问题?

四、Rspack

Rspack 是一个基于 Rust 语言开发的高性能构建工具,设计上兼容 Webpack 生态,目标是提供比 Webpack ​快得多的构建速度,同时保持相近的使用体验和配置方式。​**

字节团队出品

  1. 兼容webpack 配置
  2. rust

五、Esbuild

esbuild 是一个用 Go 语言编写的极速 JavaScript / TypeScript 打包器(bundler)和压缩器(minifier),同时也支持代码转换(如 JSX → JS、TS → JS),它的设计目标是:快!快!快!​

六、应用场景

6.1 SPA

目标:减小初始加载体积、实现按需加载、提升缓存利用率

  1. 路由级代码分割
  2. SPA 中第三方依赖(如 React、Vue、lodash)体积通常较大,且很少变动。将这些依赖与业务代码分离,可提升缓存利用率
  3. Tree Shaking 与死代码移除

6.2 打包库文件

假设组件库使用了dynamic imports怎么办?

动态导入的路径是相对于组件库自身的!​