整体对比
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 访问
-
启动开发服务器(webpack-dev-server) :它会在本地启动一个 HTTP 服务器,并内置了与客户端的通信能力(通常是 WebSocket)。
-
内存编译与监听:
- 同样会从入口开始构建依赖图并使用 Loader 转换模块。
- 关键区别:打包结果不会写入硬盘,而是保存在内存中,速度极快。
- 同时启动文件监听(Watch Mode) ,持续监听源代码的变化。
-
极速热更新(HMR - Hot Module Replacement) :
- 当检测到文件变化时,只重新编译改动的模块及其依赖(增量构建)。
- 开发服务器通过 WebSocket 连接主动将变更消息推送给浏览器。
- 浏览器的 HMR Runtime(已被注入到打包的代码中)接收到消息后,动态拉取更新后的代码模块,并替换掉正在运行的老模块,实现页面无刷新更新,保持应用状态。
1.2.2 生产环境
Webpack 的打包流程可以简化为三个核心步骤:
- 解析依赖(Dependency Resolution) :从配置的入口(Entry)文件开始,根据代码中的
import
/require
语句,递归地构建一个模块依赖图(Dependency Graph),明确所有模块的依赖关系。 - 转换与打包(Transformation & Bundling) :依赖图中的每个模块会根据配置的规则(Rules),被对应的 Loader 进行转换(例如,将 TypeScript 转为 JavaScript,Sass 转为 CSS)。然后,Webpack 根据依赖关系和优化策略(如代码分割 Code Splitting),将这些模块组合成一个或多个代码块(Chunks)。
- 输出(Output) :最后,将生成的 Chunks 转换成最终的文件(Assets),并根据配置的输出(Output)路径和文件名,写入到指定的目录(通常是
dist
或build
)中。
1.3 HMR工作原理
如何在webpack.config中启用:
1.3.1 核心实现
webpack dev server + websocket + hmr plugin + hmr runtime
1.3.2 工作流程
-
建立通道:
webpack-dev-server
(WDS) 启动后,会与浏览器客户端建立一个 WebSocket 长连接,用于后续的实时通信。 -
监听与编译:WDS 监听文件系统的变化。当您修改并保存一个文件时,Webpack 立即增量编译这些更改的模块,并生成一个描述变化的 manifest (一个 JSON 文件) 和更新的 chunk (JS 代码)。
-
消息通知:WDS 通过之前建立的 WebSocket 连接,将本次更新的 Hash 值主动推送给浏览器。
-
请求更新:浏览器端的 HMR Runtime(在初次编译时已注入到 bundle 中)收到通知后,会根据 manifest 信息,通过 JSONP 向 WDS 请求新增的或发生变化的 chunk 模块。
-
应用更新:这是最关键的步骤。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-loader
、react-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 实例,生命周期钩子如
beforeRun
,run
,emit
,done
。 - Compilation Hooks:代表一次单独的编译过程,处理模块依赖、优化等,钩子如
buildModule
,optimizeChunks
。
1.5 loader原理
本质上是一个转换器,负责将非 JavaScript 模块转换为 Webpack 能够处理的 JavaScript 模块。
- 识别文件类型:Webpack 根据配置中的
test
正则规则匹配文件 - 应用 Loader 链:按照从右到左/从下到上的顺序应用多个 Loader
- 转换为 JavaScript:每个 Loader 对文件内容进行转换
- 纳入依赖图:最终输出的 JavaScript 被加入到 Webpack 的依赖图中
1.6 plugin原理
Webpack Plugin 的本质是:通过 Tapable 提供的钩子机制,在 Webpack 构建的特定生命周期阶段执行自定义逻辑,从而扩展构建功能。
1.7 关于source map
1.8 dynamic Imports动态导入
Dynamic Imports(动态导入)是 ECMAScript 2020 中引入的一个重要特性,它允许开发者在运行时按需加载 JavaScript 模块。 在代码执行到该语句时才会开始加载模块,返回一个 Promise
Webpack 会:
- 为动态导入的模块创建单独的 chunk
- 在需要时通过 JSONP 方式异步加载这些 chunk
- 执行加载的代码并将模块导出
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 模块联邦
模块联邦让一个应用能够直接使用另一个应用暴露的模块,就像使用本地模块一样,但这些模块实际上是从远程服务器加载的。 最常用的场景是微前端架构。
二、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 编译流程
- 启动服务器与依赖预构建:
运行vite
或vite dev
后,Vite 会启动一个开发服务器8。Vite 会使用 esbuild 对你项目中的第三方依赖(如lodash
,react
)进行一次性的预构建5。这是因为许多第三方包可能是 CommonJS 格式或存在大量内部导入,预构建可以将它们转换为单一的 ESM 模块,并缓存到node_modules/.vite
目录下5。这减少了浏览器大量的请求,提升了后续加载性能。 - 浏览器请求与按需编译:
当你通过浏览器访问应用时,浏览器会先获取index.html
8。在解析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。 - 热更新 (HMR) :
当你修改代码时,Vite 通过 HMR 机制只编译变更的模块5,并通过 WebSocket 连接将更新推送给浏览器1。浏览器随后请求新的模块,应用更新而无需完全刷新页面,保持了应用状态。
依赖预构建
解决依赖格式不统一:依赖模块碎片化:的问题。
作用:
- 将 CommonJS 依赖转换为 ESM,确保浏览器兼容;
- 合并碎片化模块,减少请求次数;
- 通过缓存机制提升启动速度。
?vite 如何规避 开发和生产环境不一致带来的问题?
四、Rspack
Rspack 是一个基于 Rust 语言开发的高性能构建工具,设计上兼容 Webpack 生态,目标是提供比 Webpack 快得多的构建速度,同时保持相近的使用体验和配置方式。**
字节团队出品
- 兼容webpack 配置
- rust
五、Esbuild
esbuild 是一个用 Go 语言编写的极速 JavaScript / TypeScript 打包器(bundler)和压缩器(minifier),同时也支持代码转换(如 JSX → JS、TS → JS),它的设计目标是:快!快!快!
六、应用场景
6.1 SPA
目标:减小初始加载体积、实现按需加载、提升缓存利用率
- 路由级代码分割
- SPA 中第三方依赖(如 React、Vue、lodash)体积通常较大,且很少变动。将这些依赖与业务代码分离,可提升缓存利用率
- Tree Shaking 与死代码移除
6.2 打包库文件
假设组件库使用了dynamic imports怎么办?
动态导入的路径是相对于组件库自身的!