介绍
概览
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只包含最低所需,因此它打包的应用体积更小,更快速,并且使得应用和库之间解耦更松散。
因为这种方法是建立在import
和export
语句上,因此其效率极高,运行时自动缩减体积,探测打包文件中不需要的变量。
兼容性 Compatibility
导入CommodJS
rollup 能够通过插件导入CommondJS模块。
发布ES6模块
为了保证你的ES6模块能够马上被CommonJS的工具使用,例如在Node或者webpack中,你可以使用rollup来转化成UMD或者CommonJS格式风格。然后指定编译的版本在一个有mian
属性的package.json
文件中。如果你的package.json文件也有module
域(属性),es6敏感的工具,如rollup以及webpack2将会直接导入ES6模块版本。、
连接
step-by-step tutorial video series, with accompanying written walkthrough
miscellaneous issues in the
wiki
.
开始之前,你的电脑应该已经安装了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-pick
lodash模块的话,也许会用这个功能。在这个示例中,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
});
})
});