概述
Rollup 是一个 JavaScript 模块打包器,Rollup 对代码模块使用新的标准化格式,这些标准都包含在 JavaScript 的 ES6 版本中,而不是以前的特殊解决方案,如 CommonJS 和 AMD。ES6 模块可以使你自由、无缝地使用你最喜爱的 library 中那些最有用独立函数,而你的项目不必携带其他未使用的代码。ES6 模块最终还是要由浏览器原生实现,但当前 Rollup 可以使你提前体验。
安装
可以使用全局安装rollup
:
1 | npm install --global rollup |
摇树优化(Tree-Shaking)
在使用CommonJS时,必须导入完整的库对象:
1 | var utils = require('utils'); |
但在使用ES6模块时,无需导入整个utils对象,我们只要导入我们所需的ajax即可:
1 | import { ajax } from 'utils'; |
因为 Rollup 只引入最基本最精简代码,所以可以生成轻量、快速,以及低复杂度的 library 和应用程序。因为这种基于显式的 import 和 export 语句的方式,它远比「在编译后的输出代码中,简单地运行自动 minifier 检测未使用的变量」更有效。
js各种模块分类:
- CommonJS:一个单独的文件就是一个模块,每个模块都是一个单独的作用域,在该模块内部定义的变量,无法被其他模块读取,除非定义为global对象的属性。模块只有一个出口,exports对象。
- CommonJS2:commonjs 规范只定义了exports,而 module.exports是nodejs对commonjs的实现,实现往往会在满足规范前提下作些扩展,我们这里把这种实现称为了commonjs2,所以CommonJS2只是添加了module.exports而已。
- AMD:需要用到对应的库函数,比如RequireJS。多个js文件可能有依赖关系,通过define定义模块,require加载模块
- CMD:推崇一个模块一个文件,使用define定义模块,一个文件一个模块,所以经常就用文件名作为模块id;推崇依赖就近,所以一般不在define的参数中写依赖,在factory中写
- ES:跟CommonJS类似,通过export命令显式指定输出的代码,再通过import命令输入,es module输出的是值的引用,而commonJS模块输出的是值的拷贝,不存在动态更新
- UMD:在执行UMD规范时,会优先判断是当前环境是否支持AMD环境,然后再检验是否支持CommonJS环境,否则认为当前环境为浏览器环境
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 > (function (root, factory) {
> if (typeof define === 'function' && define.amd) {
> // AMD
> define(['jquery'], factory);
> } else if (typeof exports === 'object') {
> // Node, CommonJS-like
> module.exports = factory(require('jquery'));
> } else {
> // Browser globals (root is window)
> root.returnExports = factory(root.jQuery);
> }
> }(this, function ($) {
> // methods
> function myFunc(){};
>
> // exposed public method
> return myFunc;
> }));
>
参考网址:
兼容性
导入CommJS
Rollup 可以通过rollup-plugin-commonjs导入已存在的 CommonJS 模块,它会将 CommonJS 转换成 ES2015 模块的。
发布 ES6 模块
为了确保你的 ES6 模块可以直接与「运行在 CommonJS(例如 Node.js 和 webpack)中的工具(tool)」使用,你可以使用 Rollup 编译为 UMD 或 CommonJS 格式,然后在 package.json 文件的 main 属性中指向当前编译的版本。如果你的 package.json 也具有 module 字段,像 Rollup 和 webpack 2 这样的 ES6 感知工具(ES6-aware tools)将会直接导入 ES6 模块版本。
Rollup可以通过
pkg.module
字段感知ES模块:
1
2
3
4
5
6
7 > {
> "name": "my-package",
> "version": "0.1.0",
> "main": "dist/my-package.umd.js",
> "module": "dist/my-package.esm.js"
> }
>
当打包工具遇到我们的模块时:
- 如果它已经支持 pkg.module 字段则会优先使用 ES6 模块规范的版本,这样可以启用 Tree Shaking 机制。
- 如果它还不识别 pkg.module 字段则会使用我们已经编译成 CommonJS 规范的版本,也不会阻碍打包流程。
参考网址:https://loveky.github.io/2018/02/26/tree-shaking-and-pkg.module/
常见问题
Rollup 是用来构建库还是应用程序?
Rollup 已被许多主流的 JavaScript 库使用,也可用于构建绝大多数应用程序。但是 Rollup 还不支持一些特定的高级功能,尤其是用在构建一些应用程序的时候,特别是代码拆分和运行时态的动态导入 dynamic imports at runtime. 如果你的项目中更需要这些功能,那使用 Webpack可能更符合你的需求。
针对app级别的应该使用Webpack,针对js库级别的应用应该使用Rollup
给npm包作者的建议:请使用pkg.module!
在很长一段时间,使用JavaScript库就意味着你要冒很大的风险,这是因为你和库作者使用的模块系统可能不一样,所以这就需要你和库作者在模块系统的选择上必须达成一致意见。举个例子,假设你使用的是Browserify打包工具,但是库作者更喜欢AMD模块系统,所以在你构建之前,你必须把库作者的模块系统替换成自己项目所使用的模块系统。虽说Universal Module Definition(UMD)模块系统可以稍稍解决上述问题,不过,由于UMD模块系统不强制要求你使用什么的模块系统,(这也就意味着),你永远不知道你下一步使用的模块系统是哪一种。
ES2015模块颠覆了这一切,这是因为import以及export命令已经变成JavaScript语言的一部分啦。在不久的将来,模块系统的选择将会变得更加明确,不再模棱两可啦,而且所有的JavaScript代码都能够无缝对接。不幸的是,由于浏览器(大多数)以及Node还不支持import以及export命令,所以我们仍然需要对js文件使用UMD模块系统(如果你构建的文件只是用于Node,或许可以考虑CommonJS)。
通过给你项目的package.json文件增加(针对模块)入口”module”: “dist/my-library.es.js”配置,让你的项目同时支持UMD以及ES2015模块系统(的想法)变成了可能。这一点很重要,这是因为,Webpack和Rollup都会使用pkg.module来尽可能构建出高效代码。在某些情况下,Webpack以及Rollup甚至都能利用tree-shake特性来剔除项目中未使用的代码。
创建第一个bundle
1 | // src/main.js |
现在可以创建bundle了:
1 | rollup src/main.js -f cjs |
-f 选项(–output.format 的缩写)指定了所创建 bundle 的类型——这里是 CommonJS(在 Node.js 中运行)。由于没有指定输出文件,所以会直接打印在 stdout 中:
1 | 'use strict'; |
也可以像下面一样将 bundle 保存为文件:
1 | rollup src/main.js -o bundle.js -f cjs |
(你也可以用 rollup src/main.js -f cjs > bundle.js,但是我们之后会提到,这种方法在生成 sourcemap 时灵活性不高。)
试着运行下面的代码:
1 | node |
恭喜,你已经用 Rollup 完成了第一个 bundle。
使用配置文件
类似webpack.config.js,rollup使用rollup.config.js
:
1 | // rollup.config.js |
我们用 –config 或 -c 来使用配置文件:
1 | rm bundle.js # so we can check the command works! |
同样的命令行选项将会覆盖配置文件中的选项:
1 | rollup -c -o bundle-2.js # `-o` is short for `--output.file` |
(注意 Rollup 本身会处理配置文件,所以可以使用 export default 语法——代码不会经过 Babel 等类似工具编译,所以只能使用所用 Node.js 版本支持的 ES2015 语法。)
如果愿意的话,也可以指定与默认 rollup.config.js 文件不同的配置文件:
1 | rollup --config rollup.config.dev.js |
配置项预览:
1 | // rollup.config.js |
你必须使用配置文件才能执行以下操作:
- 把一个项目打包,然后输出多个文件
- 使用Rollup插件, 例如 rollup-plugin-node-resolve 和 rollup-plugin-commonjs 。这两个插件可以让你加载Node.js里面的CommonJS模块
如果你想使用Rollup的配置文件,记得在命令行里加上–config或者-c @@2
使用插件
类似webpack插件,比如要支持读取JSON格式文件,增加插件支持:
1 | // rollup.config.js |
npm run build 执行 Rollup。结果如下:
1 | 'use strict'; |
使用命令行
命令行的参数:
1 | -i, --input 要打包的文件(必须) |
此外,还可以使用以下参数:1
-h/--help
打印帮助文档。1
-v/--version
打印已安装的Rollup版本号。1
-w/--watch
监听源文件是否有改动,如果有改动,重新打包1
--silent
不要将警告打印到控制台。
Javascript API
Rollup 提供 JavaScript 接口那样可以通过 Node.js 来使用。你可以很少使用,而且很可能使用命令行接口,除非你想扩展 Rollup 本身,或者用于一些难懂的任务,例如用代码把文件束生成出来。
rollup.rollup
The rollup.rollup 函数返回一个 Promise,它解析了一个 bundle 对象,此对象带有不同的属性及方法,如下:
1 | const rollup = require('rollup'); |
输入参数
inputOptions 对象包含下列属性 (查看big list of options 以获得这些参数更详细的资料):
1 | const inputOptions = { |
输出参数
outputOptions 对象包括下列属性 (查看 big list of options 以获得这些参数更详细的资料):
1 | const outputOptions = { |
rollup.watch
Rollup 也提供了 rollup.watch 函数,当它检测到磁盘上单个模块已经改变,它会重新构建你的文件束。 当你通过命令行运行 Rollup,并带上 –watch 标记时,此函数会被内部使用。
1 | const rollup = require('rollup'); |
监听参数
watchOptions 参数是一个你会从一个配置文件中导出的配置 (或一个配置数据)。
1 | const watchOptions = { |
查看以上文档知道更多 inputOptions 和 outputOptions 的细节, 或查询 big list of options 关 chokidar, include 和 exclude 的资料。
Rollup与其他工具的集成
npm依赖包
Rollup并不像webpack那样,它不知道如何打破常规去处理这些依赖,因此我们需要一些配置:
1 | npm install --save-dev rollup-plugin-node-resolve |
1 | // rollup.config.js |
Babel
1 | npm i -D rollup-plugin-babel |
1 | // rollup.config.js |
在Babel实际编译代码之前,需要进行配置。 创建一个新文件src/.babelrc:
1 | { |
这个设置有一些不寻常的地方。首先,我们设置”modules”: false,否则 Babel 会在 Rollup 有机会做处理之前,将我们的模块转成 CommonJS,导致 Rollup 的一些处理失败。
第三,我们将.babelrc文件放在src中,而不是根目录下。 这允许我们对于不同的任务有不同的.babelrc配置,比如像测试,如果我们以后需要的话 - 通常为单独的任务单独配置会更好。
ES模块语法
导入
导入的值不能重新分配,尽管导入的对象和数组可以被修改(导出模块,以及任何其他的导入,都将受到该修改的影响)。在这种情况下,它们的行为与const声明类似。
命名导入
1 | import { something } from './module.js'; |
1 | import { something as somethingElse } from './module.js'; |
命名空间导入
1 | import * as module from './module.js' |
默认导入
1 | import something from './module.js'; |
空导入
1 | import './module.js'; |
这对于polyfills是有用的,或者当导入的代码的主要目的是与原型有关的时候。
导出
命名导出
导出以前声明的值:
1 | var something = true; |
在导出时重命名:
1 | export { something as somethingElse }; |
声明后立即导出:
1 | // 这可以与 `var`, `let`, `const`, `class`, and `function` 配合使用 |
默认导出
1 | export default something; |
仅当源模块只有一个导出时,才建议使用此做法。
将默认和命名导出组合在同一模块中是不好的做法,尽管它是规范允许的。
绑定是如何工作的
ES模块导出实时绑定,而不是值,所以值可以在最初根据这个示例导入后更改:
1 | // incrementer.js |
1 | // main.js |
大选项列表(配置项)
核心功能
输入(input -i/–input)
String 这个包的入口点 (例如:你的 main.js 或者 app.js 或者 index.js)
文件(file -o/–output.file)
String 要写入的文件。也可用于生成 sourcemaps,如果适用
格式(format -f/–output.format)
String 生成包的格式。 下列之一:
- amd – 异步模块定义,用于像RequireJS这样的模块加载器
- cjs – CommonJS,适用于 Node 和 Browserify/Webpack
- es – 将软件包保存为ES模块文件
- iife – 一个自动执行的功能,适合作为