Webpack 和 Rollup:相同但不同
致读者
本译文在忠于原文的基础上,融入了译者基于自身领域知识所添加的解释性内容和本地化表达。这些精心添加的内容旨在增进读者对原文核心信息的理解。如对内容有任何疑问,欢迎您参与评论区讨论,或查阅原文以作参考。
本周,Facebook
将一个重要的 pull request
合并到 react
中,用基于 rollup
的构建流程取代了现有的构建系统。这引发了许多人提问:“为什么选择 rollup
而不是 webpack
?”
这个问题很合理。webpack
是现代 javascript
社区最成功的项目之一,每月有数百万次下载量,为数万个网站和应用程序提供支持。它拥有庞大的生态系统、众多贡献者,而且——这在社区开源项目中很少见——获得了实质性的财务支持。
相比之下,rollup
就像一条小鱼。但 react
并不是唯一选择 rollup
的项目——vue
、ember
、preact
、d3
、three.js
、moment
以及其他许多知名库也都在使用 rollup
。那么这是为什么呢?为什么我们不能只有一个所有人都认可的 javascript
模块打包工具呢?
两个打包工具的故事
webpack
由 Tobias Koppers
于 2012
年创建,目标是在解决现有工具无法解决的难题:构建复杂的单页应用程序(SPA)。特别是以下两个特性彻底改变了这一切:
代码分割
使得将应用拆分成可 按需加载 的可管理块成为可能。这意味着用户可以不用等待整个应用下载和解析,就可以获得交互式网站体验。你可以手动完成这项工作,实现起来会有些复杂度。
静态资源处理
图片和
css
等静态资源可以被导入到应用中,并被视为模块依赖图中的一个特殊的节点。开发者不需要再小心翼翼地将文件放在正确的文件夹中,也不需要使用特殊的脚本来为文件URL
添加哈希值,因为这些任务webpack
可以为你很好的处理。
rollup
项目创建目的则与之不同:它的核心目的是尽可能高效地构建 javascript
库的平面分发(flat distributables
)版本,实现上充分利用 ES2015
模块的巧妙设计。其他模块打包工具(例如 webpack
等)的工作方式是将每个模块包装在一个函数中,将它们放在一个包含浏览器友好的 require
实现的包中,然后逐个解析它们。如果你的应用需要 按需加载 等功能,这样做是值得的,但若不需要那么这有点浪费。而且如果你有很多模块,情况会变得更糟。
ES2015
模块启用了一种不同解析方式,也就是 rollup
所实现的方式。所有代码都放在同一个作用域中并一次性求值,从而产生更精简、更简单的代码、启动更快。你可以在 rollup
的 REPL 中自己体验这一点。
rollup
处理方式虽好但这是有代价的,代码分割 实现起来更为棘手。在撰写本文时 rollup
还不支持这个功能。同样,rollup
也不支持热模块替换(HMR
)特性。
对于使用 rollup
的人来说,可能最大的痛点是 —— 虽然 rollup
可以处理大多数 commonjs
模块(通过 @rollup/plugin-commonjs
插件),但有些东西就是无法将 commonjs
转换为 ES2015
(主要原因是 commonjs
的高度动态性和原生支持 非严格模式,而 ES2015
模块只能在严格模式下加载),但 webpack
则可以从容地处理你扔给它的任何模块。
我应该使用哪个?
到这里,希望已经清楚为什么这两个工具可以共存并相互支持,因为他们服务于不同的目的,简而言之:
使用
webpack
构建应用程序,使用rollup
构建库
这并不是一个硬性规定,许多网站和应用程序都是用 rollup
构建的,许多库也是用 webpack
构建的。但这是一个很好的经验法则。
如果你需要代码分割,或者有很多静态资源,或者正在构建具有大量 commonjs
依赖的项目,webpack
是更好的选择。但如果你的代码库是通过 ES2015
模块编写的,而且你正在制作供其他人使用的东西,你可能更需要 rollup
。
包作者:使用 pkg.module
长期以来,使用 javascript
库有点像碰运气。因为你和库作者实际上必须就模块系统达成一致。如果你使用 browserify
而库作者更喜欢 amd
,你就必须在实际构建任何东西之前将这两种模块系统粘在一起(类似在 esm
中加载 commonjs
模块时,rollup
需要将 commonjs
转换为 esm
)。通用模块定义(umd
)格式在某种程度上解决了这个问题,但因为它没有在任何地方强制执行,你永远不知道会得到什么。
ES2015
改变了这一切,因为 import
和 export
是语言的一部分。在未来,这个规范将不会有歧义,事情会运作得更加顺畅。不幸的是,由于浏览器(大多数)和 node
还不支持 import
和 export
语义,我们仍然需要提供 umd
的产物(如果你构建的产物是仅用于 node
执行,则需要 commonjs
产物)。
通过在库的 package.json
文件中添加 "module": "dist/my-library.es.js"
字段(又称 pkg.module
),现在就可以同时提供 umd
和 ES2015
。这很重要,因为 webpack
和 rollup
都可以使用 pkg.module
来生成最高效的代码——在某些情况下,他们甚至都可以将库中未使用的代码剔除掉(tree-shaking
)。
在 rollup
的 wiki 上了解更多关于 pkg.module
的信息。