3

介绍

概览

rollup是一个js打包器,用来将很细碎的js编译打包成大的复杂的东西,像是一个库或者一个应用。其使用了ES6自带的新标准来格式化和打包js代码,而不是原先的Commonjs或者AMD这类解决方案。ES6模块能够使你轻松的无缝的组合你所喜欢库中的独立函数(静态函数)。这使得最后能够实现本地化,rollup如今实现了这些。

启动指南

使用npm install --global rollup命令下载安装。rollup既可以通过一个配置文件使用命令行接口来调用,也可以他自己的Javascript API使用。运行rollup --help查看可用的命令和参数。starter project template有一些常用设置的说明,如果需要更详细的说明,点击user guide

命令

这些名设定你的应用的入口是main.js,并且希望这些导入最后打包成一个bundle.js命名的文件。

浏览器环境

# compile to a <script> containing a self-executing function
# 编译到一个<script>元素包含的独立的函数。
$ rollup main.js --format iife --output bundle.js

Node.js环境

# compile to a CommonJS module
# 变异成一个CommonJS标准的模块
$ rollup main.js --format cjs --output bundle.js

浏览器和node.js兼容的环境

# UMD format requires a bundle name
$ rollup main.js --format umd --name "myBundle" --output bundle.js

为何如此

把项目分成各个小的部分来开发软件通常活容易些。因为经常需要去掉代码不期望的行为,也能够很大程度上降低解决问题的复杂程度,而且可以只在项目的第一个位置写一些小的项目而不是 isn't necessarily the answer。不幸的是,JavaScript本身的语言设计没有这类功能。

过滤树功能(Tree Shaking)

为了能够实现ES6模块功能,rollup会静态地分析你所引入的模块,然后去掉没有真正用到的部分。这会帮助你至引入那些需要的东西,并且减少项目的体积。

例如,如果使用CommonJS.整个的工具和库都会被导入。

// import the entire utils object with CommonJS
// 使用CommonJS整个的导入utils
var utils = require( 'utils' );
var query = 'rollup';
// use the ajax method of the utils object
// 仅使用utils中的ajax方法
utils.ajax( 'https://api.example.com?search=' + query ).then( handleResponse );

但是如果使用ES6模块系统。替代整个引入utils模块,而是仅仅引入我们所需要的ajax函数。

// import the ajax function with an ES6 import statement
//使用es6语句引入ajax函数
import { ajax } from 'utils';
var query = 'rollup';
// call the ajax function
// 调用ajax函数
ajax( 'https://api.example.com?search=' + query ).then( handleResponse );

因为rollup只包含最低所需,因此它打包的应用体积更小,更快速,并且使得应用和库之间解耦更松散。
因为这种方法是建立在importexport语句上,因此其效率极高,运行时自动缩减体积,探测打包文件中不需要的变量。


兼容性 Compatibility

导入CommodJS

rollup 能够通过插件导入CommondJS模块。

发布ES6模块

为了保证你的ES6模块能够马上被CommonJS的工具使用,例如在Node或者webpack中,你可以使用rollup来转化成UMD或者CommonJS格式风格。然后指定编译的版本在一个有mian属性的package.json文件中。如果你的package.json文件也有module域(属性),es6敏感的工具,如rollup以及webpack2将会直接导入ES6模块版本。、

连接


开始之前,你的电脑应该已经安装了node.js,因此你能够使用npm,此外你也需要知道怎么使用命令行工具。
使用rollup最简单的方法就是通过命令行接口。现在我们要全局安装rollup,在命令行输入如下命令。(稍后我们将学习如何本地安装到你的项目中,那么你的打包将更加爱便捷,暂时先不要管这么多)。

npm install rollup --global # or `npm i rollup -g` for short

现在你可以使用rollup,命令了。
因为没有传递参数,所以rollup只是打印出使用说明。这根rollup --help或者rollup -h的结果是一样的。

现在,来创建一个简单的项目。

mkdir -p my-rollup-project/src
cd my-rollup-project

首先,我们需要一个入口点entry point,粘贴下面的代码到main.js文件中。

// src/main.js
import foo from './foo.js';
export default function () {
  console.log(foo);
}

然后在创建一个foo.js文件,就是我们导入点导入的文件。

// src/foo.js
export default 'hello world!';

现在我们来准备生成一个bundle。

rolup src/main.js --format cjs

--format选项制定我们要打包成什么格式。在这个例子中是CommonJS(能够在Node.js运行)。因为我们没有制定输出的文件,因此将会打印到命令行。(标准输出stdout)。

'use strict';

var foo = 'hello world!';

var main = function () {
  console.log(foo);
};

module.exports = main;

你可以使用如下方法保存编译出来的bundle。

rollup src/main.js --format cjs --output bundle.js
# or `rollup main.js -f cjs -o bundle.js`

(你也可以使用rollup src/main.js > bundle.js,但是下面你会明白,这种方式在你想要生成map文件时缺乏灵活性。)

运行这个代码

node
> var myBundle = require('./bundle.js');
> myBundle();
'hello world!'

祝贺,你已经使用rollup创建了第一个bundle。


使用配置文件

目前为止,都还不错,但是当我们开始添加更多的设置时,这就变成了令人讨厌的在命令行输入(很多)。为了保存我们自己常用的设置,我们可以创建一个配置文件,里面保存我们需要的设置。使用js写配置文件比使用命令行方便多了。
在根目录创建一个配置文件,命名rollup.config.js,然后添加如下代码。

//rollup.config.js
export default {
    entry:'src/main.js',
    format:'cjs',
    dest:'bundle.js'//等于--output
}

使用 --config or -c flag:来使用该文件。

rm bundle.js//然后检查该命令是否有效。
rollup -c

你可以使用相应的命令行命令来覆盖设置文件中的行为。

rollup -c -o bundle-2.js

(注意:rollup是自己运行配置文件,因此我们可以使用export default语法。语法没有被bable编译,所以你只能使用当前node版本所支持的es2015语法。)
当然,如果你喜欢你可以制定不用的设置文件。

rollup --config rollup.config.dev.js
rollup --confog rollup.config.prod.js

运行build

很多javaScript项目有一个惯例:在命令行执行npm run build就可执行创建——无论是什么平台的系统。这个很有用,因为如果有人想对你的项目有所贡献(即分支之类的),那么他就可以直接去关注源码即可而不用分心去了解用了什么依赖,怎么组合等(如此这样的有,rollup,webpack,gulp或者其他)。他们甚至都不用全局安装,就像我们在学习第一部分所做的那样。
设置你自己的npm run build是很好且简单的。

创建一个package.json文件

一个package.json文件保存着你的项目的一些重要的信息,包括名字,版本,授权以及以来。(事实上,如果没有package.json文件你不能把你的项目发表到npm库中——如果你是创建一个应用而非lib库,你仍需要一个package.json文件)。

创建package.json最简单的方法是在你项目所在的目录的命令行运行npm init名来,并跟着提示一步一步来便可。
打开你的package.json文件并且在scripts属性下添加build入口:

{
  ...
  "scripts":{
    "test":"echo \" Error:no test specified\"&& exit 1",
    "build":"rollup -c"
  }
}

(这个前提是假设你已经设置了rollup.config.js文件在你的项目目录中)

安装本地rollup

到目前为止我们一直使用的是全局安装rollup。使用本地安装rollup是一个更好的选择,因为任何复制了你项目的人制药运行了npm install指令,就会得到一个独立兼容(compatible version)版本。

npm install --save-dev rollup #or `npm i -D rollup`

之后注意package.json中的devDependencies属性将会存在。

{
  ...,
  "devDependencies": {
    "rollup": "^0.43.0"
  },
  ...
}

所有你的npm run将会寻找本地版本来运行,如果rollup在本地存在的话。
试下下面的命令。

npm run build

使用npm run dev实现监控试试编译

通过安装rollup watch你可以创建一个命令,在源码出现变化是实时编译。

npm install --save-dev rollup-watch

package.json

{
  ...,
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "rollup -c",
    "dev": "rollup -c -w"
  },
  ...
}

命令rollup -c -w(rollup --config --watch缩写)会以监视模式运行rollup。



开始使用插件

目前为止,我们通过入口文件创建了个一简单的bundle,并且使用相对路径导入了一个模块。随着需要打包更复杂的bundle,你经常需要一些灵活的特性——导入从npm下载的模块,通过babel编译模块,使用json等。

为了应对这些我们使用插件,这些插件会改变rollup在编译时的一些行为。在the rollup wiki中可以找到目前维护的一些插件。

使用插件

在这个教程里我们将使用rollup-plugin-json插件,它能够使rollup导入json文件里的数据。
安装rollup-plugin-json作为开发时依赖(development dependency)。

npm install --save-dev rollup-plugin-json

(我们使用--save-der而不是--save是因为我的代码在运行时不是真正的以来这个插件——只是在我们编译bundle时依赖而已。)
修改你的src/main.js文件,让他导入package.json而不是src/foo.js

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

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

运行npm run build,结果回事如下的样子:

'use strict';

var version = "1.0.0";

var main = function () {
  console.log('version ' + version);
};

module.exports = main;

(注意:只有我们真正所需要的数据才会被导入——version,其他的name,devDependencies等package.json中的属性将会被忽略,这就是tree-shaking的作用。)


结合npm库使用rollup

在某种情况下,你的项目需要下载npm的第三方模块到你的node_modules文件夹中。跟其他的如webpack,Browserfy不同,rollup不知道out of box,怎么处理这些依赖,我们需要添加一些设置。
下载一个the answer依赖,它导出life,universe,everything问题的答案,

npm install --save the-answer # or `npm i -S the-answer`

注意:此处我们使用的是--save,因此它被保存到package.json的dependencies属性中。
如果我们更新src/main.js文件。。。

import answer from 'the-answer';

export default function () {
  console.log('the answer is ' + answer);
}

然后运行rollup

npm run build

我们会看到如下的警告

⚠️ 'the-answer' is imported by src/main.js, but could not be resolved – treating it as an external dependency.

导出来的bundle.js仍然能够在Node.js下运行,因为import声明被转化成CommonJS风格的require语句,但是the-answer没有放到bundle中,因此我们需要一个插件。

rollup-plugin-node-resolve插件

rollup-plugin-node-resolve插件教会rollup怎么去找到扩展的模块。安装。

npm install --save-dev rollup-plugin-node-resolve

这时运行下npm run build将不会有错误抛出,bundle包含导入的组件。

rollup-plugin-commonjs插件

有些库导出的是es6模块,所以你可import——the-answer就是这种。然而npm的大多数第三方库是CommonJS风格的模块。在其发生改变之前,我们需要转换CommonJS为ES2015的模块,然后再用rollup处理。

这正是rollup-plugin-commonjs的功能所在。

注意rollup-plugin-commonjs必须在其他的插件转化你的模块之前运行——这是为了防止其他插件打断对CommonJS的探测。

同级依赖peer dependencies

假如你正在创建一个库并且有一个同级依赖(即你的库所需要的依赖而不是开发依赖),例如React或者Loadash等。如果你像我们上面所讲的那样引入其中,你的bundle将会包含他们全部。

import answer from 'the-answer';
import _ from 'lodash';

你可以优雅地处理引入的模块以及bundle。这个李子中,我们把lodash作为模块,但是the-answer不是。

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

export default {
  entry: 'src/main.js',
  format: 'cjs',
  plugins: [resolve({
    // pass custom options to the resolve plugin
    customResolveOptions: {
      moduleDirectory: 'node_modules'
    }
  })],
  // indicate which modules should be treated as external
  external: ['lodash'],
  dest: 'bundle.js'
};

现在看下,lodash会被视作外部,而不是打包到你的lib里面。
external关键字接受一个模块的数组或者一个函数,这个函数的参数是模块的名字,如果这个模块应该不被打包到其中,那么返回true。

export default {
  // ...
  external: id => /lodash/.test(id)
}

如果你是用babel-plugin-lodash插件来cherry-picklodash模块的话,也许会用这个功能。在这个示例中,Babel将会覆盖你的import语句。

import _merge from 'lodash/merge'

external数组不会处理通配符,因此import将仅仅作为不打包而已。

结合Babel使用rollup

很多开发者会在他们的项目中使用Babel,因此他们可以使用一些超前的es6特性,这样能够在浏览器和node环境中使用。
同时使用rollup和Babel的最简单的方法是使用rollup-plugin-babel插件。安装:

npm i -D rollup-plugin-babel

rollup.config.js中配置。

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

export default {
  entry: 'src/main.js',
  format: 'cjs',
  plugins: [
    resolve(),
    babel({
      exclude: 'node_modules/**' // only transpile our source code
    })
  ],
  dest: 'bundle.js'
};

在babel能够真正的编译源码之前,你需要一些设置,创建一个新文件src/.babelrc

{
  "presets": [
    ["latest", {
      "es2015": {
        "modules": false
      }
    }]
  ],
  "plugins": ["external-helpers"]
}

在这一步有一些东西跟往常不太一样。首先我们设置modules:false,否则Babel将会在rollup转化前转化我们的模块为CommonJS风格,这样就无法实现rollup的目的(tree shaking)。

其次,我们使用了external-helpers插件,它使rollup在bundle的头部添加一次'helper',而不是在每个使用模块的地方包含他们(这是默认行为)。

第三我们把.babelrc放在了src文件夹里,而不是项目的跟目录,如果我们售后需要它,这个允许我们有不同的.babelrc文件对应不同的需求,例如测试。(针对不同的需求设置不同的配置是个好的方法)

现在,在运行rollup之前,我们需要安装latest预设以及external-helers插件。

npm i -D babel-preset-latest babel-plugin-external-helpers

现在运行rollup生成不打了,此时可以使用es2015特性了。首先更新下src/main.js的内容。

import answer from 'the-answer';

export default () => {
  console.log(`the answer is ${answer}`);
}

使用npm run build运行rollup,然后查看bundle。

'use strict';

var index = 42;

var main = (function () {
  console.log('the answer is ' + index);
});

映射 Sourcemaps

映射可以通过添加--sourcemap命令行flag实现,也可以通过在配置文件中设置sourceMap:true属性实现。

问答

什么是‘tree shaking’?
Tree-shaking是活的代码放入——code inclusion,是一种只填加那些使用了的代码的处理,类似于无用代码剔除,然后提高效率。阅读了解更多:Tree-shaking vs Dead-Code-Elimination

为什么原生的ES2015的模块系统更优于AMD和CommonJS模块标准?
ES2015是官方标准,很快会被浏览器以及Node.js所实现。它们允许静态分析实现类似tree-shaking这样的效果,并且拥有更高级的特性,如循环引用,实时绑定(live-binding)等。

谁设计了rollup logo?看起来可耐。
Julian Lioyd

与其他工具相比

coming soon...

结合Gulp使用rollup

rollup 返回的是一个Promises,所以gulp可以很容易的集成。

语法跟配置文件很想,但是属性分散到两个不同的设置里。
构造bundle,然后导出到目标output。

var gulp = require('gulp')
    rollup = require('rollup')
    rollupTypescript = require('rollup-plugin-typescript')
;
gulp.task('build',function(){
  return rollup.rollup({
    entry:"./src/main.js",
    plugins:[rollupTypescript()],
  }).then(function(bundle){
    bundle.write({
      format:'umd',
      moduleName:'library',
      dest:'./dest/library.js',
      sourceMap:true
    });
  })
});

farmerz
1.4k 声望93 粉丝

可可西里,可可西里,我只是想去看一看。