字数
2567 字
阅读时间
11 分钟
Rollup 和 Webpack 都是前端构建工具,它们的主要区别如下:
打包方式:Rollup 是采用 ES Module 进行打包,只会打包代码中用到的部分,支持 Tree Shaking,能够生成更小的打包文件,而 Webpack 采用的是 CommonJS 的方式进行打包,无法做到像 Rollup 一样的精简打包。
插件生态:Webpack 的插件生态更加丰富,支持更多的功能,如代码分割、热更新、文件压缩等等,而 Rollup 的插件相对较少。
使用场景:Rollup 适用于构建类库或组件库,Webpack 适用于构建复杂的应用程序。
性能:由于 Rollup 打包出来的文件更小,因此在加载和解析代码的时候更快,但在处理大型应用时可能不如 Webpack 快
Webpack 和 Rollup 作为前端端主流的模块打包工具,核心目标都是将分散的模块(如 JS、CSS 等)整合为可运行的产物,但两者的底层设计理念和实现原理存在显著差异,这些差异直接决定了它们的适用场景。以下从核心原理、模块处理、依赖解析、产物优化等维度对比两者的底层差异:
一、核心设计理念差异
这是两者所有差异的根源:
- Webpack:定位为「前端应用打包工具」,设计初衷是处理复杂应用的依赖管理(尤其是浏览器环境),支持多种类型资源(JS、CSS、图片等),强调「万物皆模块」和「灵活性」,可通过插件扩展几乎所有功能。
- Rollup:定位为「JavaScript 模块打包工具」,专注于构建库(Library)或简洁应用,核心目标是生成「精简、高效的代码」,强调「原生 ES 模块(ESM)的 tree-shaking 能力」和「输出代码的可读性」。
二、模块系统与依赖解析
1. 模块格式支持
Webpack:
- 底层支持多种模块格式:ES Modules(
import/export)、CommonJS(require/module.exports)、AMD、UMD 等,甚至可以通过 loader 处理非 JS 模块(如 CSS、图片等)。 - 原理:内部实现了一套「模块适配器」,将不同格式的模块统一转换为 Webpack 自有模块格式(类似 CJS 的 wrapper),例如对 ESM 的
import会转换为__webpack_require__方法调用,对 CJS 的require也会包装为同名方法,从而实现跨模块格式的依赖管理。
- 底层支持多种模块格式:ES Modules(
Rollup:
- 原生优先支持 ES Modules(ESM),对 CommonJS 的支持需要通过插件(如
@rollup/plugin-commonjs)转换,且转换过程相对复杂(因 CJS 是动态的,与 ESM 的静态分析特性冲突)。 - 原理:依赖 ESM 的「静态结构」(导入导出在编译时可确定),通过遍历 ESM 的
import语句构建依赖树,不支持非 JS 模块(需插件扩展,但并非核心能力)。
- 原生优先支持 ES Modules(ESM),对 CommonJS 的支持需要通过插件(如
2. 依赖解析方式
Webpack:
- 采用「递归依赖解析 + 缓存」:从入口文件开始,递归解析所有
require/import语句,将每个模块转换为「模块对象」(包含 id、依赖列表、代码等),并缓存已解析的模块避免重复处理。 - 支持「动态依赖」:例如
require('./' + filename)这类动态路径,Webpack 会通过正则匹配可能的文件路径,在打包时将所有可能的模块都包含进来(称为「context module」),灵活性高但可能导致冗余。
- 采用「递归依赖解析 + 缓存」:从入口文件开始,递归解析所有
Rollup:
- 采用「静态依赖分析」:基于 ESM 的静态导入语句(
import './a.js'),在编译时即可确定所有依赖路径,构建出清晰的依赖树(无动态依赖支持,需手动配置external或插件处理)。 - 解析过程更简洁:因 ESM 静态特性,无需处理动态路径,依赖树构建效率更高,也为 tree-shaking 奠定了基础。
- 采用「静态依赖分析」:基于 ESM 的静态导入语句(
三、代码打包与输出逻辑
1. 模块包装方式
Webpack:
- 所有模块被包裹在
__webpack_modules__对象中,每个模块对应一个函数(参数为module、exports、__webpack_require__等),通过__webpack_require__(moduleId)实现模块加载。 - 示例(简化):javascript
// 模块注册表 const __webpack_modules__ = { './src/a.js': (module, exports, __webpack_require__) => { module.exports = 'a'; }, './src/index.js': (module, exports, __webpack_require__) => { const a = __webpack_require__('./src/a.js'); console.log(a); } }; // 加载入口模块 __webpack_require__('./src/index.js'); - 特点:通过运行时(runtime)代码管理模块加载,支持代码分割(chunk)、懒加载等动态特性,但会引入额外的运行时代码(增加产物体积)。
- 所有模块被包裹在
Rollup:
- 采用「扁平化打包」:将所有模块的代码合并到一个文件中,通过变量提升(hoisting)避免作用域冲突,直接执行代码逻辑,无额外运行时。
- 示例(简化):javascript
// a.js 的代码 const a = 'a'; // index.js 的代码 console.log(a); - 特点:输出代码接近手写逻辑,可读性高、体积小,但不支持动态加载(需通过插件模拟,如
@rollup/plugin-dynamic-import-vars)。
2. 代码分割(Code Splitting)
Webpack:
- 核心特性之一,支持多入口、动态
import()、splitChunks配置等多种代码分割方式,可生成多个 chunk 文件。 - 原理:通过「chunk 图」管理模块与 chunk 的关系,每个 chunk 包含独立的模块集合和运行时代码,chunk 间通过
jsonp或script标签加载(浏览器环境),支持复杂的依赖拆分逻辑。
- 核心特性之一,支持多入口、动态
Rollup:
- 早期不支持代码分割,后来通过 ESM 的动态
import()支持,但功能较简单,主要用于生成多个输出文件(如按入口分割)。 - 原理:基于 ESM 的静态分析,将动态
import()对应的模块拆分为单独的 chunk,依赖浏览器原生 ESM 加载能力(或通过插件转换为 CJS 兼容格式),不支持splitChunks这类复杂的公共代码提取(需手动配置manualChunks)。
- 早期不支持代码分割,后来通过 ESM 的动态
四、优化策略实现
1. Tree Shaking(移除未使用代码)
Webpack:
- 依赖 ESM 的静态结构,在
mode: 'production'下通过terser-webpack-plugin实现,需要结合package.json的sideEffects标记副作用文件。 - 原理:先标记所有未被引用的导出(
unused harmony export),再通过压缩工具(Terser)删除这些代码。但因 Webpack 支持多种模块格式,Tree Shaking 效果受模块格式影响(CJS 无法被 Tree Shaking)。
- 依赖 ESM 的静态结构,在
Rollup:
- Tree Shaking 是核心特性,原生支持且效果更彻底,同样基于 ESM 的静态分析。
- 原理:在依赖解析阶段即可识别未被使用的导出(如
export const unused = 1且无引用),直接在打包时剔除这些代码,无需依赖压缩工具。因专注 ESM,无 CJS 转换的干扰,Tree Shaking 更精准。
2. 产物优化
Webpack:
- 侧重「应用级优化」,如代码分割、缓存策略(
contenthash)、压缩(JS/CSS/图片)等,通过插件生态实现多样化优化。 - 产物中包含运行时代码和 chunk 管理逻辑,适合浏览器环境的复杂应用,但体积相对较大。
- 侧重「应用级优化」,如代码分割、缓存策略(
Rollup:
- 侧重「库级优化」,输出代码简洁、无冗余,支持多种模块格式输出(ESM、CJS、UMD 等),适合作为库的打包工具。
- 产物体积更小,可读性更高,但缺乏 Webpack 对多资源类型和复杂场景的支持。
五、插件系统设计
Webpack:
- 插件系统基于「事件流」(Tapable 库),通过钩子(hook)在构建的各个阶段(如
compile、make、emit等)插入逻辑,几乎可以干预打包的每一步。 - 灵活性极高,可实现 loader 处理非 JS 资源、自定义 chunk 分割、注入环境变量等复杂功能。
- 插件系统基于「事件流」(Tapable 库),通过钩子(hook)在构建的各个阶段(如
Rollup:
- 插件系统基于「钩子函数」,围绕模块解析、转换、生成等阶段设计(如
resolveId、transform、generateBundle等),更专注于 JS 模块的处理。 - 设计更简洁,插件逻辑通常与 ESM 处理强相关,扩展非 JS 资源时不如 Webpack 自然。
- 插件系统基于「钩子函数」,围绕模块解析、转换、生成等阶段设计(如
总结:底层差异的核心对比
| 维度 | Webpack | Rollup |
|---|---|---|
| 核心目标 | 复杂应用打包(多资源、动态加载) | 库或简洁应用打包(精简代码) |
| 模块支持 | 多格式(ESM/CJS/AMD 等)+ 非 JS 资源 | 原生 ESM,CJS 需插件转换 |
| 依赖解析 | 支持动态依赖,递归解析 + 缓存 | 静态依赖分析,仅支持 ESM 静态导入 |
| 代码包装 | 运行时管理(__webpack_require__) | 扁平化合并,无运行时 |
| 代码分割 | 强大,支持多策略(动态导入、公共 chunk) | 基础支持,依赖 ESM 动态导入 |
| Tree Shaking | 依赖压缩工具,效果受模块格式影响 | 原生支持,基于 ESM 静态分析,效果更优 |
| 产物特点 | 体积较大,包含运行时 | 体积小,可读性高,接近原生代码 |
| 插件生态 | 庞大,覆盖全场景 | 精简,专注 JS 模块处理 |
简言之,Webpack 是「全能型选手」,适合处理复杂应用的依赖和资源管理;Rollup 是「专精型选手」,适合生成精简、高效的库或简单应用代码。两者的底层差异本质上是「灵活性与精简性」「多场景支持与单一目标优化」的取舍。
参考
rollup打包产物解析及原理(对比webpack) - 掘金