rollup 使用实践

908 阅读5分钟

「这是我参与2022首次更文挑战的第9天,活动详情查看:2022首次更文挑战

前言

我们都用过构建工具,webpack应该是最经常用的,除了webpack以外还有一个构建工具也是非常优秀的,那就是Rollup,所以今天我们来学习一下Rollup。

Tree-shaking

Tree-shaking是rollup提出的,这也是rollup一个非常重要的feature,那什么是Tree-shaking,在构建代码时,静态分析代码中的 import,并将排除任何未实际使用的代码,只打包使用到的代码。这样的好处是减少代码的体积。举个例子: 我们在根目录中创建两个文件,index.js 和 math.js

// math.js
export function plus (a) {
    return a + a;
}
export function square (a) {
    return a * a;
}
// index.js
import { square } from './math.js';
console.log( square( 5 ) ); 

然后运行 rollup index.js --o bundle.js --f iife 其中 --o 代表输出文件,如果不传会直接在控制台输出,--f 是输出的文件类型,iife 代表立即执行函数,其他几种类型是

  • amd - AMD
  • cjs -CommonJS
  • es - ES6 modules
  • umd - UMD
  • system - SystemJS loader

构建完后我们看看bundle.js

(function () {
  'use strict';

  // math.js
  function square (a) {
    return a * a;
  }

  console.log( square( 5 ) );

}());

我们发现这里只出现了我们引入的square函数,并没有把plus函数也打包进来。

webpack 和 rollup

Webpack 在 2012 年创建,当时的主要作用是用来构建复杂的单页应用(SPA)。尤其是它的两个特点改变了一切:

  1. 代码分割
  2. 静态资源的导入

而Rollup 的开发理念则不同,它利用 ES2015 模块的巧妙设计,尽可能高效地构建精简且易分发的 JavaScript 库。而webpack是通过将模块分别封装进函数中,然将这些函数通过能在浏览器中实现的 require 方法打包(__webpack_require__),最后依次处理这些函数。在你需要实现按需加载的时候,这种做法非常的方便,但是这样做引入了很多无关代码,比较浪费资源。

webpack构建输出的代码其实有三种。

  • 你的业务逻辑代码
  • Runtime - 代码执行的引导
  • Manifest - 模块依赖关系的记录

比如上面那个例子,我们运行webpack index.js --mode development 进行构建,打包出来的文件是这样子的:

/******/ (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, { enumerable: true, get: getter });
/******/ 		}
/******/ 	};
/******/
/******/ 	// define __esModule on exports
/******/ 	__webpack_require__.r = function(exports) {
/******/ 		if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ 			Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ 		}
/******/ 		Object.defineProperty(exports, '__esModule', { value: true });
/******/ 	};
/******/
/******/ 	// create a fake namespace object
/******/ 	// mode & 1: value is a module id, require it
/******/ 	// mode & 2: merge all properties of value into the ns
/******/ 	// mode & 4: return value when already ns object
/******/ 	// mode & 8|1: behave like require
/******/ 	__webpack_require__.t = function(value, mode) {
/******/ 		if(mode & 1) value = __webpack_require__(value);
/******/ 		if(mode & 8) return value;
/******/ 		if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
/******/ 		var ns = Object.create(null);
/******/ 		__webpack_require__.r(ns);
/******/ 		Object.defineProperty(ns, 'default', { enumerable: true, value: value });
/******/ 		if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
/******/ 		return ns;
/******/ 	};
/******/
/******/ 	// 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 = "./index.js");
/******/ })
/************************************************************************/
/******/ ({

/***/ "./index.js":
/*!******************!*\
  !*** ./index.js ***!
  \******************/
/*! no exports provided */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _math_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./math.js */ \"./math.js\");\n\n\nconst num = Object(_math_js__WEBPACK_IMPORTED_MODULE_0__[\"square\"])( 5 ) ;\nconsole.log( num ); \n\n//# sourceURL=webpack:///./index.js?");

/***/ }),

/***/ "./math.js":
/*!*****************!*\
  !*** ./math.js ***!
  \*****************/
/*! exports provided: plus, square */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"plus\", function() { return plus; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"square\", function() { return square; });\n// math.js\nfunction plus (a) {\n  return a + a;\n}\nfunction square (a) {\n  return a * a;\n}\n\n//# sourceURL=webpack:///./math.js?");

/***/ })

/******/ });
  • 可以看到构建结果中的业务逻辑代码,Runtime和Manifest
  • 在Manifest中记录中依赖关系,通过__webpack_require__加载
  • 构建结果中包含了没有使用到的plus
  • 构建体积明显比rollup中iife格式大
  • 代码执行的时候,rollup中iife输出格式,代码执行的速度更快,webpack构建出来的还有依赖查找,而且每个模块通过一个函数包裹形式,执行的时候,就形成了一个个的闭包,占用了内存

所以rollup 比较适合打包 js 的 sdk 或者封装的框架等,例如,vue 源码就是 rollup 打包的。而 webpack 比较适合打包一些应用,例如 SPA 或者同构项目等等。

使用

除了直接用命令后执行构建外,也可以使用配置文件的来配置一些属性。在根目录下创建 rollup.config.js

// rollup.config.js
export default {
  input: 'src/index.js',
  output: {
    file: 'bundle.js',
    format: 'cjs'
  }
};

然后执行 rollup -c 生成bundle.js文件

'use strict';

// math.js
function square (a) {
  return a * a;
}

console.log( square( 5 ) ); 

可以看到,这和我们上面构建的是一样的,只不过我们设置文件类型是 cjs。
Rollup中也有很强大的插件系统,我们修改一下index.js

import { version } from '../package.json';

export default function() {
    console.log('version: ' + version);
}

直接引入package.json获取到version,然后在执行rollup -c,这时候命令行是会报错的,因为json文件是不能被引入的,但是有一个插件rollup-plugin-json可以解决这个问题,我们先安装一下,npm install rollup-plugin-json --save,修改rollup.config.js


import json from 'rollup-plugin-json'; // 引入插件
export default {
  input: 'src/index.js',
  output: {
    file: 'bundle.js',
    format: 'cjs'
  },
  plugins: [ json() ]   // 使用插件
};

然后在执行构建命令发现,这次构建成功了

'use strict';

var version = "1.0.0";

function index() {
    console.log('version: ' + version);
}

module.exports = index;

成功从 package.json里获取到了version属性。

外链(external -e/--external)

使用rollup打包,比如我们在自己的库中需要使用第三方库,例如lodash等,又不想在最终生成的打包文件中出现lodash。这个时候我们就需要使用external属性。

import _ from 'lodash'

_.chunk(['a', 'b', 'c', 'd'], 2);

构建后我们发现最终文件里把整个 lodash给打包进来了,这样就加大了我们包的体积,所以我们需要使用 external 属性。

import resolve from 'rollup-plugin-node-resolve'; // rollup无法识别node_modules中的包,需要用这个插件
export default {
  input: 'src/index.js',
  output: {
    file: 'bundle.js',
    format: 'cjs'
  },
  external: ['lodash'],
  plugins: [ resolve() ]   // 使用插件
};

构建后

'use strict';

function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }

var _ = _interopDefault(require('lodash'));

_.chunk(['a', 'b', 'c', 'd'], 2);

最终文件不会把 lodash打包进来。

其他属性

rollup提供了很多个性化的配置属性,比如 footer和banner,可以为文件头部和尾部添加文字,更多属性可以看 文档