webpack 之外的另一种选择:rollup
webpack 对前端来说是再熟悉不过的工具了,它提供了强大的功能来构建前端的资源,包括 html/js/ts/css/less/scss ...
等语言脚本,也包括 images/fonts ...
等二进制文件。
其实,webpack 发起之初主要是为了解决以下两个问题:
- 代码拆分(Code Splitting): 可以将应用程序分解成可管理的代码块,可以按需加载,这样用户便可快速与应用交互,而不必等到整个应用程序下载和解析完成才能使用,以此构建复杂的单页应用程序(SPA);
- 静态资源(Static Assets): 可以将所有的静态资源,如 js、css、图片、字体等,导入到应用程序中,然后由 webpack 使用 hash 重命名需要的资源文件,而无需为文件 URL 增添 hash 而使用 hack 脚本,并且一个资源还能依赖其他资源。
正是因为 webpack 拥有如此强大的功能,所以 webpack 在进行资源打包的时候,就会产生很多冗余的代码(如果你有查看过 webpack 的 bundle 文件,便会发现)。
比如,把 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);
/***/ })
/******/ ]);
这在以下的一些情境中就不太高效,需要寻求更好的解决方案:
- 需要 js 高效运行。因为 webpack 对子模块定义和运行时的依赖处理(
__webpack_require__
),不仅导致文件体积增大,还会大幅拉低性能; - 项目(特别是类库)只有 js,而没有其他的静态资源文件,使用 webpack 就有点大才小用了,因为 webpack bundle 文件的体积略大,运行略慢,可读性略低。
在这种情况下,就想要寻求一种更好的解决方案,这便是 rollup.
现在已经有很多类库都在使用 rollup 进行打包了,比如:react, vue, preact, three.js, moment, d3 等。
1. 工具
安装
npm i -g rollup # 全局安装
npm i -D rollup # 本地安装
使用
rollup -c # 使用一个配置文件,进行打包操作
更多详细的用法,参考 rollup.js - command-line-flags.
2. 配置
rollup 的配置与 webpack 的配置类似,定义在 rollup.config.js
文件中,比如:
// rollup.config.js
export default {
input: 'src/index.js',
output: {
file: 'bundle.js',
// amd, cjs, esm, iife, umd, system
format: 'cjs'
}
};
常用的几个配置项:
-
input
: 源码入口文件,一般是一个文件,如src/index.js
。 -
output
: 定义输出,如文件名,目标目录,输出模块范式(es6
,commonjs
,amd
,umd
,iife
等),模块导出名称,外部库声明,全局变量等。 -
plugins
: 插件,比如 rollup-plugin-json 可以让 rollup 从.json
文件中导入 json 数据。
更多详细的配置,参考 rollup.js - configuration-files.
3. rollup 与 webpack 对比
先拿段代码来来看看他们打包之后各自是什么效果。
源代码
# 目录
|-- 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));
配置
# webpack.config.js
module.exports = {
entry: './src/index.js',
output: {
filename: 'dist/webpack.bundle.js',
library: 'demo',
libraryTarget: 'umd'
}
};
# rollup.config.js
export default {
input: 'src/index.js',
output: {
file: 'dist/rollup.bundle.js',
name: 'demo',
format: 'umd'
}
};
运行
# webpack 打包
webpack
# rollup 打包
rollup -c
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}`);
/***/ })
/******/ ]);
});
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;
})));
其实,你也基本上看出来了,在这种场景下,rollup 的优势在哪里:
- 文件很小,几乎没什么多余代码,除了必要的
cjs
,umd
头外,bundle 代码基本和源码差不多,也没有奇怪的__webpack_require__
,Object.defineProperty
之类的东西; - 执行很快,因为没有 webpack bundle 中的
__webpack_require__
,Object.defineProperty
之类的冗余代码; - 另外,rollup 也对 es 模块输出及 iife 格式打包有很好的支持。
4. 结论
rollup 相对 webpack 而言,要小巧、干净利落一些,但不具备 webpack 的一些强大的功能,如热更新,代码分割,公共依赖提取等。
所以,一个不错的选择是,应用使用 webpack,类库使用 rollup。
5. 后续
更多博客,查看 https://github.com/senntyou/blogs
作者:深予之 (@senntyou)
版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)