rollup 打包工具
1. 概述
Rollup
是一款基于ES Module
的打包器,类似于webpack
可以将项目中散落的细小模块打包为整块的代码,不用点是Rollup
打包的模块可以更好的运行在浏览器或nodeJs
环境。
从作用上来看Rollup
与webpack
非常类似,相比于webpack
来说Rollup
要小巧很多。webpack
配合插件几乎可以完成前端工程化的绝大多数工作。Rollup
仅是ES Module
的打包器,并没有其他额外的功能。
webpack
有对于开发者十分友好的hmr
功能Rollup中
则无法支持。Rollup
诞生的目的并不是与webpack
工具竞争,他的初衷只是希望提供一个高效的ES Module
打包器,充分利用ES Module
的各项特性构建出结构比较扁平,性能比较出众的类库。
2. 使用
这里准备一个简单的示例,使用ES Module
方式组织代码模块化,示例的源代码包含三个文件,message.js
以默认导出的方式导出了一个对象。
export default {
hi: 'Hey Guys, I am yd~'
}
logger.js
中导出两个函数。
export const log = msg => {
console.log(msg);
}
export const error = msg => {
conole.log('------ ERROR ------')
console.log(msg);
}
index.js
导入这两个模块,并且使用他们。
import { log } from './logger';
import message from './message';
const msg = message.hi;
log(msg);
安装rollup
对示例应用打包。
yarn add rollup --dev
rollup
自带cli
程序,通过yarn rollup
运行程序。可以发现,在不传递任何参数的情况下rollup
会自动打印出帮助信息,帮助信息开始的位置就表示应该通过参数去指定一个打包入口文件。打包入口是src
下面的index.js
文件。
yarn rollup ./src/index.js
此时还应该指定一个代码的输出格式,可以使用--format
参数指定输出的格式比如iife
也自执行函数的格式。
yarn rollup ./src/index.js --format iife
还可以通过--file
指定文件的输出路径,比如这里指定dist
文件夹下的bundle.js
,这样打包结果就会输出到dist
文件当中。
yarn rollup ./src/index.js --format iife --file dist/bundle.js
rollup
打包结果惊人的简洁,基本上和手写的代码是一样的,相比于webpack
中大量的引导代码,这里的输出结果几乎没有任何的多余代码。
rollup
只是把打包过程中各个模块按照模块的依赖顺序先后的拼接到一起,而且仔细观察打包结果会发现,在输出结果中只会保留用到的部分,对于未引用的部分都没有输出。rollup
默认自动开启tree-shaking
优化输出的结果,tree-shaking
概念最早也是在rollup
工具中提出的。
(function () {
'use strict';
const log = msg => {
console.log(msg);
};
var message = {
hi: 'Hey Guys, I am yd~'
};
const msg = message.hi;
log(msg);
}());
3. 配置文件
rollup
支持以配置文件的方式去配置打包过程中的各项参数,可以在项目的跟目录下新建一个rollup.config.js
配置文件。这个文件是运行在node
环境中,不过rollup
自身会额外处理这个配置文件,所以可以直接使用ES Modules
。
在这个文件中需要导出一个配置对象,对象中通过input
属性指定打包的入口文件路径。通过output
指定输出的相关配置,output
属性要求是一个对象,在output
对象中可以使用file
属性指定输出的文件名。format
属性可以用来指定输出格式。
export default {
input: 'src/index.js',
output: {
file: 'dist/bundle.js',
format: 'iife'
}
}
需要通过--config
参数来表明使用项目中的配置文件,默认是不去读取配置文件的。
yarn rollup --config rollup.config.js
4. 使用插件
rollup
自身的功能就是ES
模块的合并打包,如果项目有更高级别的需求,例如想去加载其他类型的资源文件,或者是要在代码中导入CommonJS
模块,又或者是想要编译ECMAScript
的新特性。这些额外的需求,rollup
同样支持使用插件的方式扩展实现,而且插件是rollup
唯一的扩展方式,他不像webpack
中划分了loader
,plugin
和minimize
这三种扩展方式。
尝试使用一个可以在代码中导入JSON
文件的插件,这里使用的插件名字叫做rollup-plugin-json
需要先安装这个插件。
yarn add rollup-plugin-json --dev
安装完成过后打开配置文件,由于rollup
的配置文件可以直接使用ES Modules
所以使用import
的方式导入插件。导出的是一个函数,可以将函数的调用结果添加到配置对象的plugins
数组当中。
import json from 'rollup-plugin-json';
export default {
input: 'src/index.js',
output: {
file: 'dist/bundle.js',
format: 'iife'
},
plugins: [
json()
]
}
配置好插件后就可以在代码中通过import
方式导入json
文件了。
import { log } from './logger';
import message from './message';
import { name, version } from '../package.json';
const msg = message.hi;
log(msg);
log(name);
log(version);
yarn rollup --config rollup.config.js
输出的bundle.js
能看到json
中的name
和version
正常被打包进来了,而json
当中那些没有用到的属性也都会被tree-shaking
移除掉。
(function () {
'use strict';
const log = msg => {
console.log(msg);
};
var message = {
hi: 'Hey Guys, I am yd~'
};
var name = "rollup_p";
var version = "1.0.0";
const msg = message.hi;
log(msg);
log(name);
log(version);
}());
5. 加载NPM模块
rollup
默认只能按照文件路径的方式去加载本地的文件模块,对于node_modules
中的第三方的模块并不能够像webpack
一样直接去通过模块的名称导入对应的模块。为了抹平这个差异rollup
官方给出了rollup-plugin-node-resolve
插件。使用这个插件可以在代码当直接使用模块名称导入对应的模块。
yarn add rollup-plugin-node-resolve --dev
配置文件中需要先将这个模块导入进来,然后将插件配置到plugins
数组中。
import json from 'rollup-plugin-json';
import resolve from 'rollup-plugin-node-resolve'
export default {
input: 'src/index.js',
output: {
file: 'dist/bundle.js',
format: 'iife'
},
plugins: [
json(),
resolve()
]
}
完成以后就可以直接导入node_modules
中的第三方的npm
模块了。导入loadash-es
模块,这个模块是loadash
的ES Modules
版本。
import _ from 'loadash-es';
import { log } from './logger';
import message from './message';
import { name, version } from '../package.json';
const msg = message.hi;
log(msg);
log(name);
log(version);
log(_.camelCase('hello world'))
这里我们使用的loadsh ES Module
的版本而不是使用普通版本是因为rollup
默认只能取处理ES Module
模块,如果需要使用普通版本需要做额外的处理。
6. 加载CommonJS模块
为了兼容CommonJS
方式模块官网给出了rollup-plugin-commonjs
插件。
yarn add rollup-plugin-commonjs --dev
import json from 'rollup-plugin-json';
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'
},
plugins: [
json(),
resolve()
]
}
配置过后就可以使用commonjs
模块了,在src
下添加commonjs
模块cjs.module.js
文件。
module.exports = {
foo: 'bar'
}
commonjs
导出整体会作为默认导出。
import _ from 'loadash-es';
import { log } from './logger';
import message from './message';
import { name, version } from '../package.json';
import cjs from './cjs.module';
const msg = message.hi;
log(msg);
log(name);
log(version);
log(_.camelCase('hello world'));
log(cjs);
打包过后commonjs
被打包进bundle.js
里面了。
7. 代码拆分
rollup
最新的版本开始支持代码拆分了,可以使用符合ES Module
标准的动态导入的方式(Dynamic Imports
)实现模块的按需加载,rollup
内部会自动处理代码的拆分,也就是分包。
使用动态导入的方式导入logger
对应的模块,import
方法返回的是promise
对象,在promise
的then
方法里面可以拿到模块导入过后的对象。
// import { log } from './logger';
// import message from './message';
// const msg = message.hi;
// log(msg);
import('./logger').then(({ log }) => {
log('code splitting');
});
yarn rollup --config
使用代码拆分式打包要求format
不能是iife
形式。因为自执行函数会把所有的模块都放到同一个函数当中,他并没有像webpack
一样的引导代码,所以也就没有办法实现代码拆分。
要想使用代码拆分必须要使用amd
或commonjs
等其他标准,浏览器环境只能使用amd
标准。同样因为输出多个文件,这里就不能使用file
的这种配置方式,因为file
是执行一个单个文件输出的文件名。
如果需要输出多个文件可以使用dir
的参数将输出的目录设置为dist
。
export default {
input: 'src/index.js',
output: {
// file: 'dist/bundle.js',
// format: 'iife'
dir: 'dist',
format: 'amd'
},
}
yarn rollup --config
打包完成过后可以看到dist
目录中会根据动态导入生成入口的bundle.js
以及动态导入的bundle
。都是采用amd
的标准输出的。
8. 多入口打包
rollup
支持多入口打包,而且对于不同入口中公共的部分,会自动提取到单个文件当中,作为独立的bundle
。实例中有两个入口分别是index
和album
,共用fetch.js
和logger.js
两个模块。
index.js
import fetchApi from './fetch';
import { log } from './logger';
fetchApi('/posts').then(data => {
data.forEach(item => {
log(item);
})
})
album.js
import fetchApi from './fetch';
import { log } from './logger';
fetchApi('/photos?albumId=1').then(data => {
data.forEach(item => {
log(item);
})
})
fetch.js
export default endpoint => {
return fetch('xxxxx').then(response => response.json())
}
logger.js
export const log = msg => {
console.log(msg);
}
export const error = msg => {
conole.log('------ ERROR ------')
console.log(msg);
}
配置多入口打包非常简单,只需要将input
属性修改为一个数组就可以了,当然也可以使用与webpack
相同的对象配置方式,不过这里需要注意,因为多入口打包,内部会自动提取公共模块,也就是说内部会使用代码拆分。这里就不能使用iife
输出格式了,需要将输出格式修改为amd
。
export default {
// input: ['src/index.js', 'src/album.js'],
input: {
foo: 'src/index.js',
bar: 'src/album.js'
},
output: {
dir: 'dist',
// format: 'iife',
format: 'amd'
},
}
打包过后dist
目录下会多出三个js
文件,分别是两个不同打包入口的打包结果和公共提取出来的公共模块。
另外需要注意一点的是,对于amd
这种输出格式的js
文件,不能直接去引用到页面上,必须通过实现amd
标准的库去加载。
在dist
目录下手动创建一个html
文件。然后在html
中使用打包生成的bundle.js
, 采用requirejs
的库去加载amd
标准输出的bundle
。
require
可以通过data-main
参数来制定require
加载的模块的入口模块路径。
<body>
<script src="...require.js" data-main="foo.js"></script>
</body>
9. 选用原则
rollup
确实有他的优势,首先是他输出的结果会更加扁平一些,执行效率自然就会更高。其次是他会自动取移除那些未引用代码,也就是tree-shaking
。再一个就是他的打包结果基本上跟手写的代码一致,也就是打包结果对于开发者而言是可以正常阅读的。
但是他的缺点同样也很明显,首先就是加载一些非ES Module
的第三方模块就会比较复杂,需要配置一大堆插件。因为这些模块最终都被打包到一个函数当中了,所以没有办法像webpack
一样去实现HMR
这种模块热替换的这种开发体验。再一个就是在浏览器环境中,代码拆分必须要使用像requireJS
这样的amd
库,因为他的代码拆分必须要使用像amd
这样的输出格式。
综合以上的这些特点,如果正在开发一个应用程序,肯定要面临大量引入第三方模块这样的需求。同时又需要像HMR
这样的功能去提升开发体验。而且应用一旦大了以后还必须要去分包。这些需求rollu
p在满足上都会有一些欠缺。
如果正在开发的是一个js
框架或者类库,优点就特别有必要,而缺点基本可以忽略。拿加载第三方模块来说在开发类库的时候很少的在代码中去依赖一些第三方模块。所以像react
或者vue
之类的框架都是使用rollup
作为模块打包器,而并非是webpack
。
到目前为止,开源社区中大多数人还是希望这两个工具可以共同存在共同发展,并且能够相互支持和借鉴。原因很简单,就是希望能够让更专业的工具去做更专业的事情。
总结一下就是Webpack
是大而全Rollup
是小而美。
在对他们两者之前的选择上,基本的原则就是如果正在开放应用程序,建议使用Webpack
, 如果正在开发类库或者开发框架的话那建议选择Rollup
。
当然Rollup
同样可以去构建绝大多数的应用程序,Webpack
也同样可以去构建类库或者是框架。只不过相对来讲的话术业有专攻。
另外一点随着近几年Webpack
的发展Rollup
中的很多优势几乎已经被抹平了,例如Rollup
中扁平化输出在Webpack
中就可以使用concatenateModules
插件去完成。也可以实现类似的输出。
转载自:https://juejin.cn/post/6914165207183360013