【第九期】Rollup:下一代ES模块打包工具

Vue,React,D3等众多知名项目都使用rollup进行构建打包,为何没有选择咱们项目中常用的webpack呢?这篇文章主要分析下rollup同webpack相比打包的优点以及它的一些基本使用方式。

背景

rollup从设计之初就是面向ES module的,它诞生时AMD、CMD、UMD的格式之争还很火热,做者但愿充分利用ES module机制,构建出结构扁平性能出众的类库。前端

ES module机制

ES module的设计思想是尽可能的静态化,使得编译时就能肯定模块的依赖关系,以及输入和输出的变量。CommonJS 和 AMD 模块,都只能在运行时肯定这些东西,举例来讲:node

  1. ES import只能做为模块顶层的语句出现,不能出如今 function 里面或是 if 里面。
  2. ES import的模块名只能是字符串常量。
  3. 无论 import 的语句出现的位置在哪里,在模块初始化的时候全部的 import 都必须已经导入完成。
  4. import binding 是 immutable 的,相似 const。好比说你不能 import { a } from './a' 而后给 a 赋值个其余什么东西。

这些设计虽然使得灵活性不如CommonJS的require,但却保证了 ES modules 的依赖关系是肯定的,和运行时的状态无关,从而也就保证了ES modules是能够进行可靠的静态分析的。webpack

rollup对比webpack

咱们经过一个案例看一下webpackrollup打包后的代码结构。web

源文件:json

// index.js
import a from './a.js'
import b from './b.js' 

export default () => {
  console.log(a())
}

// a.js
export default () => 'a'

// b.js
export default () => 'b'
复制代码

webpack打包生成:浏览器

(function(modules) { // webpackBootstrap
  // 大量的runtime代码
  // ...
})
({
  "./src/a.js":
  (function(module, __webpack_exports__, __webpack_require__) {
 "use strict";
  eval("__webpack_require__.r(__webpack_exports__);\n/* harmony default export */ __webpack_exports__[\"default\"] = (() => 'a');\n\n//# sourceURL=webpack:///./src/a.js?");

  }),

  "./src/b.js":
  (function(module, __webpack_exports__, __webpack_require__) {
 "use strict";
  eval("__webpack_require__.r(__webpack_exports__);\n/* harmony default export */ __webpack_exports__[\"default\"] = (() => 'b');\n\n//# sourceURL=webpack:///./src/b.js?");

  }),

  "./src/index.js":
  (function(module, __webpack_exports__, __webpack_require__) {
 "use strict";
  eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _a_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./a.js */ \"./src/a.js\");\n/* harmony import */ var _b_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./b.js */ \"./src/b.js\");\n\n \n\n/* harmony default export */ __webpack_exports__[\"default\"] = (() => {\n console.log(Object(_a_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"])())\n});\n\n//# sourceURL=webpack:///./src/index.js?");
  })
})
复制代码

rollup打包生成:缓存

var test = (function () {
 'use strict';

  var a = () => 'a';

  var index = () => {
    console.log(a());
    
  };

  return index;

}());
复制代码

首先,webpack致力于复杂SPA的模块化构建,优点在于:性能优化

  1. 经过loader处理各类各样的资源依赖
  2. HMR模块热替换
  3. 按需加载
  4. 提取公共模块

rollup致力于打造性能出众的类库,有以下优点:bash

  1. 编译出来的代码可读性好
  2. rollup打包后生成的bundle内容十分干净,没有什么多余的代码,只是将各个模块按照依赖顺序拼接起来,全部模块构建在一个函数内(Scope Hoisting), 执行效率更高。相比webpack(webpack打包后会生成__webpack_require__等runtime代码),rollup拥有无可比拟的性能优点,这是由依赖处理方式决定的,编译时依赖处理(rollup)天然比运行时依赖处理(webpack)性能更好
  3. 对于ES模块依赖库,rollup会静态分析代码中的 import,并将排除任何未实际使用的代码(tree-shaking,下文会有具体解释)
  4. 支持程序流分析,能更加正确的判断项目自己的代码是否有反作用(配合tree-shaking)
  5. 支持导出es模块文件(webpack不支持导出es模块)

固然,rollup也有明显的缺点:前端框架

  1. 模块过于静态化,HMR很难实现
  2. 仅面向ES module,没法可靠的处理commonjs以及umd依赖

webpack和rollup的使用原则

经过以上的对比能够得出,构建App应用时,webpack比较合适,若是是类库(纯js项目),rollup更加适合。

webpack构建App的优点体如今如下几方面:

  1. 强大的插件生态,主流前端框架都有对应的loader
  2. 面向App的特性支持,好比以前提到的HMR按需加载公共模块提取等都是开发App应用必要的特性
  3. 简化Web开发各个环节,包括图片自动base64,资源缓存(chunkId),按路由作代码拆分,懒加载
  4. 可靠的依赖模块处理,不像rollup那样仅面向ES module,面临cjs的问题(webpack经过__webpack_require__实现各类类型的模块依赖问题)

rollup的优点在于构建高性能的bundle,这正是类库所须要的。

tree-shaking

tree-shaking能够理解为经过工具"摇"咱们的JS文件,将其中用不到的代码"摇"掉,属于性能优化的范畴。

tree-shaking较早由rollup实现,后来webpack2也借助于UglifyJS实现了tree-shaking的功能。

tree-shaking的本质是借助ES module的静态分析来消除无用的js代码,无用代码有如下几个特征:

  1. 代码不会被执行到
  2. 代码执行的结果不会被用到
  3. 代码只影响死变量

rollup打包时的tree-shaking案例:

// add.js:
export default (a, b) => {
  return a + b
}

// index.js:
import add from './add.js'
export default () => {
  add(1, 2)
}

// 打包后bundle.js:
var index = () => {
};
export default index;
复制代码

add(1, 2)的执行结果在index.js中没有被用到,所以在bundle.js中被'摇'掉了。

若是函数中存在反作用,那么tree-shaking会失效:

// add.js:
export default (a, b) => {
  window.a = 'a'
  return a + b
}

// ...

// bundle.js:
var add = (a, b) => {
  window.a = 'a';
  return a + b
};

var index = () => {
  add(1, 2);
};

export default index;
复制代码

因此咱们尽可能不要写带有反作用的代码。

rollup使用教程

首先在项目中安装rollup:

yarn add rollup -D
复制代码

package.json中加入构建脚本命令:

{
  "scripts": {
    "build": "rollup -c"
  }
}
复制代码

核心属性

rollup 支持命令行JS API两种调用方式,咱们重点来看下命令行配置文件中的几个核心属性:

// rollup.config.js
import resolve from 'rollup-plugin-node-resolve';
import commonjs from 'rollup-plugin-commonjs';
import babel from 'rollup-plugin-babel';

export default [{
  input: 'src/index.js',
  output: {
    file: 'dist/bundle.js',
    format: 'umd',
    name: 'test'
  },
  plugins: [
    resolve(),
    commonjs(),
    babel({
      exclude: 'node_modules/**'
    })
  ]
}]
复制代码

1. input: 填写打包的入口文件路径

2. output.format: 源码构建输出的格式

  1. iife: 自执行函数, 可经过 <script> 标签加载
  2. amd: 浏览器端的模块规范, 可经过 RequireJS 可加载
  3. cjs: Node 默认的模块规范, 可经过 Webpack 加载
  4. umd: 兼容 IIFE, AMD, CJS 三种模块规范
  5. es: ES module 规范, 可用 Webpack, Rollup 加载

rollup为了方便类库的使用者进行tree-shaking,提供了es的构建格式:

源文件:

// add.js:
export default (a, b) => a + b

// index.js:
import add from './add.js'
export default () => {
  var result = add(1, 2)
  console.log(result)
}
复制代码

rollup按照es的格式构建:

var add = (a, b) => {
  return a + b
};
var index = () => {
  var result = add(1, 2);
  console.log(result);
};
export default index;
复制代码

能够看出,咱们获得了一个基于ES module规范的bundle,此时读者可能会有这样的疑问:应用项目中一般会设置babel屏蔽node_module目录下的文件,若是将pkg.main指向当前ES module规范的bundle,项目最终打包后的bundle中会包含ES module代码。

为了解决上述问题,rollup最先提出了pkg.module,配置导出格式为es的文件的路径,打包工具在遇到pkg.module字段时,会优先使用。

综上所述,类库经过rollup能够设置打包出两份文件,一份umd(按照实际须要可选其余),一份es,将它们的路径分别设置为package.json中的mainmodule的值。这样就能方便类库的使用者进行tree-shaking

3. external + output.globals

rollup经过external + output.globals来标记外部依赖,以lodash为例:

// rollup.config.js
import resolve from 'rollup-plugin-node-resolve';
import commonjs from 'rollup-plugin-commonjs';
export default [
  {
    input: 'src/index.js',
    output: {
      file: 'dist/bundle.js',
      format: 'iife',
      name: 'test',
      globals: {
        'lodash': '_'
      }
    },
    external: [
      'lodash'
    ],
    plugins: [
      resolve(),
      commonjs()
    ]
  }
]

// index.js
import lodash from 'lodash'
export default () => {
  console.log(lodash)
}

// bundle.js
var test = (function (lodash) {
 'use strict';

  lodash = lodash && lodash.hasOwnProperty('default') ? lodash['default'] : lodash;

  var index = () => {
    console.log(lodash);
  };

  return index;

}(_));
复制代码

4. plugins

rollup同时提供了一些插件来解决压缩babel转换等问题,这里列举几个经常使用的插件:

  1. rollup-plugin-alias: 配置module的别名
  2. rollup-plugin-babel: 打包过程当中使用Babel进行转换, 须要安装和配置Babel
  3. rollup-plugin-eslint: 提供ESLint能力, 须要安装和配置ESLint
  4. rollup-plugin-node-resolve: 解析node_modules 中的模块
  5. rollup-plugin-commonjs: 转换 CJS -> ESM, 一般配合上面一个插件使用
  6. rollup-plugin-replace: 相似于webpack的DefinePlugin

其余配置见:rollup官网

最后感谢您花时间阅读这篇文章,但愿这篇文章能对您有所帮助。

参考文章: 1.www.zhihu.com/question/41… 2.juejin.im/post/5a4dc8… 3.www.ayqy.net/blog/rollup…


水滴前端团队招募伙伴,欢迎投递简历到邮箱:fed@shuidihuzhu.com

相关文章
相关标签/搜索