从 Webpack 到 Rollup
2017年4月份,Facebook 将 React 的构建工具换成了 Rollup。这一举动,使得一些人非常疑惑:“WebPack不也是由Facebook开发的吗?为什么它们会选用 Rollup 而抛弃 WebPack?”
这是一个完全合理的问题。WebPack自诞生之初就备受注目,可以算是现代 JavaScript 社区最成功的故事之一,每月有着几百万的下载量,为成千上万的网站及应用提供技术支持。它拥有活跃的社区,庞大的生态系统,众多的贡献者,而且作为一个社区开源项目,还有一个有意义的财务支持。
相比之下,Rollup 并不起眼。但我们会发现,包括 React 在内的大型 JavaScript 库都在使用着 Rollup,如:Vue,Ember,Three.js,Moment以及其他一些知名的库。
那么,为什么这些知名的库纷纷抛弃 WebPack,转投入 Rollup 的怀抱呢?
WebPack
WebPack 诞生于 2012 年,由 Tobias Koppers 发起,用于解决当时现有工具未解决的一个难题:构建复杂的单页应用程序(SPA)。它时一个强力的模块打包器。所谓包(bundle)就是一个 JavaScript 文件,他把所有的资源(assets)整合在一起,以便于它们可以在同一个文件请求中发回给客户端。包中可以包含 JavaScript、CSS 样式、HTML 以及很多其他类型的文件。
WebPack 的特点
代码分割(Code Splitting)
简单来说,就是将一个巨大的应用程序整体切割成一块一块的可管理的代码块,可以按需加载,当访问相应页面时再进行页面加载,这意味着可以更快速的获取交互性网站,而不必等到整个应用程序下载和解析完成。
静态资源(Static assets)
类似 image 和 CSS 文件导入到你的应用程序中,而且还可以被作为依赖图中的另一个节点。我们不需要再去关心文件是否放入正确的文件夹中,也不需要为文件 URL 添加 hash 而使用 hack 脚本,因为 WebPack 会帮助我们处理这些事情。
加载器(Loader)
WebPack 本身只能够处理原生的 JavaScript 模块,但是依托于 loader 转换器,可以帮助 WebPack 将各种类型的资源转换成 JavaScript 模块。这样,任何资源都可以成为 WebPack 可以处理的模块。
智能解析
WebPack 内部存在这一个智能解析器,几乎可以处理任何第三方库,无论他们的模块形式是 CommonJS、AMD还是普通的 JS 文件。甚至再加载依赖的时候,允许使用动态表达式 require('./templates/'+name+'.jade')。
插件系统
WebPack 内置了功能丰富的插件系统。大多数内容功能都是基于这个插件系统运行的,还可以开发和使用开源的 WebPack 插件,来满足各式各样的需求。
WebPack 的缺点
正是由于 WebPack 拥有如此强大的功能,使得它再进行资源打包的时候,会产生过多冗余的代码。
举个例子:把export default str => str
这段代码用 WebPack 进行打包会得到如下的结果:
/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, {
/******/ configurable: false,
/******/ enumerable: true,
/******/ get: getter
/******/ });
/******/ }
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(__webpack_require__.s = 0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
/* harmony default export */ __webpack_exports__["default"] = (str => str);
/***/ })
/******/ ]);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
这在以下的一些情境中就不太高效了,需要寻求更好的解决方案:
- 需要 JavaScript 高效运行。由于 WebPack 对子模块定义和运行时的依赖处理(`__ webpack_require__),不仅导致文件体积增大,还会大幅度拉低性能;
- 项目(特别是类库)只存在 JavaScript 文件,而没有其他的静态资源文件,使用 WebPack 有点大材小用了,因为 webpack bundle 文件的体积略大,运行略慢,可读性略低。
在上述情况下,就需要寻求一种更好的解决方案,这便是 Rollup 诞生的原因。
Rollup
Rollup 是下一代 JavaScript 模块打包工具。开发者可以在你的应用或库中使用 ES6 模块,然后高效地将它们打包成一个单一的文件用于浏览器和 Node.js 使用。Rollup 最令人激动的地方是,能够让打包文件体积很小。这么说可能很难理解,更详细的解释:相比于其他 JavaScript 打包工具,Rollup 总能打出更小,更快的包。因为 Rollup 基于 ES6 模块,比 WebPack 和 Browserify 使用的 CommonJS 模块机制更高效。这也让 Rollup从模块中删除无用代码,即 tree-shaking 更加容易。
Rollup的特点
Tree-Shaking
这个特点,是 Rollup 最初推出时的一大特点。Rollup 通过对代码的静态分析,分析出冗余代码,在最终的打包文件中将这些冗余代码删除掉,进一步缩小打包文件的体积。这是目前大部分构建工具所不具备的特点(WebPack 2.0+ 开始支持Tree-Shaking,但由于文件类型庞杂,较 Rollup 稍显逊色)。
ES6 模块打包支持
该特点也是其他构建工具所不具备的。Rollup 直接不需要通过 babel 将 import 转换成 CommonJS 的 require 方式,极大的利用 ES6 模块的优势。
WebPack 与 Rollup 对比
同样一段代码,分别用不同的打包工具进行打包。
# 目录
|-- src/
|-- index.js
|-- prefix.js
|-- suffix.js
# prefix.js
const prefix = 'prefix';
export default str => `${prefix} | ${str}`;
# suffix.js
const suffix = 'suffix';
export default str => `${str} | ${suffix}`;
# index.js
import prefix from './prefix';
import suffix from './suffix';
export default str => suffix(prefix(str));
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// webpack.bundle.js
(function webpackUniversalModuleDefinition(root, factory) {
if(typeof exports === 'object' && typeof module === 'object')
module.exports = factory();
else if(typeof define === 'function' && define.amd)
define([], factory);
else if(typeof exports === 'object')
exports["demo"] = factory();
else
root["demo"] = factory();
})(typeof self !== 'undefined' ? self : this, function() {
return /******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, {
/******/ configurable: false,
/******/ enumerable: true,
/******/ get: getter
/******/ });
/******/ }
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(__webpack_require__.s = 0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__prefix__ = __webpack_require__(1);
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__suffix__ = __webpack_require__(2);
/* harmony default export */ __webpack_exports__["default"] = (str => Object(__WEBPACK_IMPORTED_MODULE_1__suffix__["a" /* default */])(Object(__WEBPACK_IMPORTED_MODULE_0__prefix__["a" /* default */])(str)));
/***/ }),
/* 1 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
const prefix = 'prefix';
/* harmony default export */ __webpack_exports__["a"] = (str => `${prefix} | ${str}`);
/***/ }),
/* 2 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
const suffix = 'suffix';
/* harmony default export */ __webpack_exports__["a"] = (str => `${str} | ${suffix}`);
/***/ })
/******/ ]);
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
// rollup.bundle.js
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global.demo = factory());
}(this, (function () { 'use strict';
const prefix = 'prefix';
var prefix$1 = str => `${prefix} | ${str}`;
const suffix = 'suffix';
var suffix$1 = str => `${str} | ${suffix}`;
var index = str => suffix$1(prefix$1(str));
return index;
})));
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
从上述打包的文件基本可以看出,Rollup 的优势在哪里了:
- 文件小,几乎没有多余的代码,除了必要的
cjs
,umd
头外,bundle 代码基本与源代码差不多,也没有冗余的__ webpack_require__
,Object.definePropery
之类的代码; - 执行速度快,由于没有了多余的代码,所以比之 WebPack ,Rollup 打包速度,构建速度更快。
如何选择
通过上述分别对 WebPack 和 Rollup 进行介绍,不难看出,WebPack 和 Rollup 在不同场景之下,都发挥着自身的优势,这种优势是彼此都不具备的。WebPack 对于代码分割和静态资源处理有着先天优势,并且支持热模块替换(HMR),而 Rollup 并不支持,所以当用户项目需要用到以上功能时,则可以优先考虑 WebPack。但是,Rollup 对于代码的 Tree-Shaking和 ES6 模块上有着算法优势上的支持,如果你的项目只需要打包出一个简单的 bundle 包,并且是基于 ES6 模块开发的,那么选择 Rollup 就很有必要了。
其实,自 WebPack 2.0+ 开始支持 Tree-Shaking,并在使用babel-loader
的情况下支持了 ES6 module 的打包之后,Rollup 已经逐渐失去了当初的优势。但是它并没有被抛弃,反而因为其简单的 API、使用方式而被广大的 JS 库开发者青睐,如:React、Vue 等,都在使用 Rollup 作为构建工具。而 WebPack 在中大型项目中使用非常广泛。
总而言之,用一句话概括:在开发应用时使用 WebPack,开发库时使用 Rollup
总结
WebPack 和 Rollup 作为构建工具,都拥有着各自的优势和使用场景,我们不能因为它们的一些缺点而弃之,相反,我们在开发过程中,如果能够利用好其优点,就会给我们的生产效率带来极大的提高。