Skip to content

Latest commit

 

History

History
373 lines (291 loc) · 12.2 KB

15.md

File metadata and controls

373 lines (291 loc) · 12.2 KB

webpack 之外的另一种选择:rollup

webpack 对前端来说是再熟悉不过的工具了,它提供了强大的功能来构建前端的资源,包括 html/js/ts/css/less/scss ... 等语言脚本,也包括 images/fonts ... 等二进制文件。

其实,webpack 发起之初主要是为了解决以下两个问题:

  1. 代码拆分(Code Splitting): 可以将应用程序分解成可管理的代码块,可以按需加载,这样用户便可快速与应用交互,而不必等到整个应用程序下载和解析完成才能使用,以此构建复杂的单页应用程序(SPA);
  2. 静态资源(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);


/***/ })
/******/ ]);

这在以下的一些情境中就不太高效,需要寻求更好的解决方案:

  1. 需要 js 高效运行。因为 webpack 对子模块定义和运行时的依赖处理(__webpack_require__),不仅导致文件体积增大,还会大幅拉低性能;
  2. 项目(特别是类库)只有 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'
  }
};

常用的几个配置项:

  1. input: 源码入口文件,一般是一个文件,如 src/index.js
  2. output: 定义输出,如文件名,目标目录,输出模块范式(es6, commonjs, amd, umd, iife 等),模块导出名称,外部库声明,全局变量等。
  3. 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 的优势在哪里:

  1. 文件很小,几乎没什么多余代码,除了必要的 cjs, umd 头外,bundle 代码基本和源码差不多,也没有奇怪的 __webpack_require__, Object.defineProperty 之类的东西;
  2. 执行很快,因为没有 webpack bundle 中的 __webpack_require__, Object.defineProperty 之类的冗余代码;
  3. 另外,rollup 也对 es 模块输出及 iife 格式打包有很好的支持。

4. 结论

rollup 相对 webpack 而言,要小巧、干净利落一些,但不具备 webpack 的一些强大的功能,如热更新,代码分割,公共依赖提取等。

所以,一个不错的选择是,应用使用 webpack,类库使用 rollup。

5. 后续

更多博客,查看 https://github.com/deepraining/blogs

作者:深雨 (@deepraining)

版权声明:自由转载-非商用-非衍生-保持署名(创意共享 3.0 许可证