Skip to content

Webpack and Rollup: the same but different

A Note to Our Readers

While maintaining fidelity to the source material, this translation incorporates explanatory content and localized expressions, informed by the translator's domain expertise. These thoughtful additions aim to enhance readers' comprehension of the original text's core messages. For any inquiries about the content, we welcome you to engage in the discussion section or consult the source text for reference.

本周,Facebook 将一个重要的 pull request 合并到 react 中,用基于 rollup 的构建流程取代了现有的构建系统。这引发了许多人提问:“为什么选择 rollup 而不是 webpack?”

这个问题很合理。webpack 是现代 javascript 社区最成功的项目之一,每月有数百万次下载量,为数万个网站和应用程序提供支持。它拥有庞大的生态系统、众多贡献者,而且——这在社区开源项目中很少见——获得了实质性的财务支持

相比之下,rollup 就像一条小鱼。但 react 并不是唯一选择 rollup 的项目——vueemberpreactd3three.jsmoment 以及其他许多知名库也都在使用 rollup。那么这是为什么呢?为什么我们不能只有一个所有人都认可的 javascript 模块打包工具呢?

A tale of two bundlers

webpackTobias Koppers2012 年创建,目标是在解决现有工具无法解决的难题:构建复杂的单页应用程序(SPA)。特别是以下两个特性彻底改变了这一切:

  1. 代码分割

    使得将应用拆分成可 按需加载 的可管理块成为可能。这意味着用户可以不用等待整个应用下载和解析,就可以获得交互式网站体验。你可以手动完成这项工作,实现起来会有些复杂度。

  2. 静态资源处理

    图片和 css 等静态资源可以被导入到应用中,并被视为模块依赖图中的一个特殊的节点。开发者不需要再小心翼翼地将文件放在正确的文件夹中,也不需要使用特殊的脚本来为文件 URL 添加哈希值,因为这些任务 webpack 可以为你很好的处理。

rollup 项目创建目的则与之不同:它的核心目的是尽可能高效地构建 javascript 库的平面分发(flat distributables)版本,实现上充分利用 ES2015 模块的巧妙设计。其他模块打包工具(例如 webpack 等)的工作方式是将每个模块包装在一个函数中,将它们放在一个包含浏览器友好的 require 实现的包中,然后逐个解析它们。如果你的应用需要 按需加载 等功能,这样做是值得的,但若不需要那么这有点浪费。而且如果你有很多模块,情况会变得更糟。

ES2015 模块启用了一种不同解析方式,也就是 rollup 所实现的方式。所有代码都放在同一个作用域中并一次性求值,从而产生更精简、更简单的代码、启动更快。你可以在 rollupREPL 中自己体验这一点。

rollup 处理方式虽好但这是有代价的,代码分割 实现起来更为棘手。在撰写本文时 rollup 还不支持这个功能。同样,rollup 也不支持热模块替换(HMR)特性。

对于使用 rollup 的人来说,可能最大的痛点是 —— 虽然 rollup 可以处理大多数 commonjs 模块(通过 @rollup/plugin-commonjs 插件),但有些东西就是无法将 commonjs 转换为 ES2015(主要原因是 commonjs 的高度动态性和原生支持 非严格模式,而 ES2015 模块只能在严格模式下加载),但 webpack 则可以从容地处理你扔给它的任何模块。

So which should I use?

到这里,希望已经清楚为什么这两个工具可以共存并相互支持,因为他们服务于不同的目的,简而言之:

使用 webpack 构建应用程序,使用 rollup 构建库

这并不是一个硬性规定,许多网站和应用程序都是用 rollup 构建的,许多库也是用 webpack 构建的。但这是一个很好的经验法则。

如果你需要代码分割,或者有很多静态资源,或者正在构建具有大量 commonjs 依赖的项目,webpack 是更好的选择。但如果你的代码库是通过 ES2015 模块编写的,而且你正在制作供其他人使用的东西,你可能更需要 rollup

Package authors: use pkg.module

长期以来,使用 javascript 库有点像碰运气。因为你和库作者实际上必须就模块系统达成一致。如果你使用 browserify 而库作者更喜欢 amd,你就必须在实际构建任何东西之前将这两种模块系统粘在一起(类似在 esm 中加载 commonjs 模块时,rollup 需要将 commonjs 转换为 esm)。通用模块定义(umd)格式在某种程度上解决了这个问题,但因为它没有在任何地方强制执行,你永远不知道会得到什么。

ES2015 改变了这一切,因为 importexport 是语言的一部分。在未来,这个规范将不会有歧义,事情会运作得更加顺畅。不幸的是,由于浏览器(大多数)和 node 还不支持 importexport 语义,我们仍然需要提供 umd 的产物(如果你构建的产物是仅用于 node 执行,则需要 commonjs 产物)。

通过在库的 package.json 文件中添加 "module": "dist/my-library.es.js" 字段(又称 pkg.module),现在就可以同时提供 umdES2015。这很重要,因为 webpackrollup 都可以使用 pkg.module 来生成最高效的代码——在某些情况下,他们甚至都可以将库中未使用的代码剔除掉(tree-shaking)。

rollupwiki 上了解更多关于 pkg.module 的信息。

Contributors

Changelog

Discuss

Released under the CC BY-SA 4.0 License. (2619af4)