Rollup was designed with libraries rather than apps in mind, and it is a perfect fit for React’s use case.
在Behind the Scenes: Improving the Repository Infrastructure – React Blog看到了这个,有些惊讶,这样好的东西,为什么只是面向类库呢?什么原因致使它不适合用来构建App?
webpack致力于复杂SPA的模块化构建,非常吸引人的是各种loader:
Essentially, webpack loaders transform all types of files into modules that can be included in your application’s dependency graph.
以一致的方式处理各种资源依赖,通过loader屏蔽掉了资源类型差异(js是module,css是module,img也是module……),优势如下:
No more carefully placing your files in the right folders and hacked-together scripts for adding hashes to file URLs — webpack can take care of it for you.
另一些非常强大的特性包括:
rollup一开始就是面向ES6 module的:
Next-generation ES6 module bundler.
当时AMD、CMD、UMD的格式之争还很火热,ES6 module还没有浏览器实现。rollup就这样冒了出来
Rollup was created for a different reason: to build flat distributables of JavaScript libraries as efficiently as possible, taking advantage of the ingenious design of ES2015 modules.
(引自Webpack and Rollup: the same but different,rollup作者亲述)
希望充分利用ES6 module机制,构建出结构扁平,性能出众的类库bundle,即面向library设计的
It solves one problem well: how to combine multiple modules into a flat file with minimal junk code in between.
rollup让人惊艳的是其bundle的干净程度,尤其是iife
格式,内容非常干净,没什么多余代码,真的只是把各模块按依赖顺序,先后拼接起来了
这与rollup的模块处理思路有关:
To achieve this, instead of turning modules into functions like many other bundlers, it puts all the code in the same scope, and renames variables so that they don’t conflict. This produces code that is easier for the JavaScript engine to parse, for a human to read, and for a minifier to optimize.
把所有模块都扁平地放在bundle文件内最外层作用域中,模块之间没有作用域隔离,依靠重命名来解决同一作用域下命名冲突的问题。几个显而易见的好处:
这样做的缺点也很明显:
如果只是面向lib的话,第一点不支持也不要紧,但第二点着实头疼,二级依赖是不可控的,总是不可避免地会遇到cjs模块无法转自动换到ES6 module的一些问题,例如:
‘foo’ is not exported by bar.js (imported by baz.js)
一些场景可以按照Troubleshooting通过namedExports
的方式不太愉快地解决,另一些时候通过external
或globals
绕过去,甚至还有需要调整plugin应用顺序的解法……但没有办法彻底解决这类问题:
Webpack gets around the need for namedExports by keeping everything as CommonJS modules and implementing Node’s require system in the browser, which is why the resulting bundles are larger and take longer to start up. Both options are valid, but that’s the tradeoff to be aware of — more config (Rollup, when using modules like React), or larger bundles (webpack).
(引自Is “named exports” feature or bug?)
虽然cjs终将成为历史,但目前以及眼下,npm仍然存在相当多的cjs模块,无论是SPA还是library,仍然面经常临处理cjs模块依赖的问题
Use webpack for apps, and Rollup for libraries
构建App的话,webpack比较合适,如果是类库,当然rollup更好
webpack构建App的优势体现在以下几方面:
__webpack_require__
没这些烦恼而rollup没有这些优势,做代码拆分等会遇到一些不太容易解决的问题,没有足够的时间和把握的话,不要轻易尝试把rollup作为App构建工具
rollup的优势在于高效率的bundle,这正是类库所追求的,即便费一点周折(正如React 16所做的),为了性能也是值得的
注意,这个原则只是说用合适的工具做合适的事情,适用于多数一般场景,用rollup构建App,用webpack构建类库的也很常见:
That’s not a hard and fast rule — lots of sites and apps are built with Rollup, and lots of libraries are built with webpack. But it’s a good rule of thumb.
典型的,如果业务本身没太多第三方模块依赖,并且风格约定遵循ES6 module,用rollup构建App也很合适(Code Splitting等也不是完全做不到)
P.S.另外,rollup也不太容易像glup或webpack一样进行基于stream的扩展,比如从一个vue
文件中分离出三部分分别处理(vue插件好像还不支持ts)
对于React之类的类库,应该尽可能地作为第三方依赖独立出去,而不是build进bundle,几个原因:
rollup下可以通过external
+ globals
配置来标记外部依赖:
external: ['react', 'react-dom'],
output: {
globals: {
'react': 'React',
'react-dom': 'ReactDOM'
}
}
这样生成的bundle为:
// iife
(function (React,ReactDOM) {
//...
}(React,ReactDOM));// cjs
var React = _interopDefault(require('react'));
var ReactDOM = _interopDefault(require('react-dom'));
所以一般把业务代码打包成iife
,再通过script
引用CDN第三方类库:
<script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
<!-- 或者聚合的版本 -->
<script crossorigin src="//cdn.jsdelivr.net/combine/npm/react@16.0.0/umd/react.production.min.js,npm/react-dom@16.0.0/umd/react-dom.production.min.js"></script>
P.S.rollup的external
与globals
有些奇怪,无论是key
还是value
,还是这两个东西竟然要配合使用,更多信息请查看[question] Difference between externals and globals