Rollup之入门笔记
Rollup 是一个 JavaScript 模块打包器,可以将小块代码编译成大块复杂的代码,例如 library 或应用程序。Rollup 对代码模块使用新的标准化格式,这些标准都包含在 JavaScript 的 ES6 版本中,而不是以前的特殊解决方案,如 CommonJS 和 AMD。ES6 模块可以使你自由、无缝地使用你最喜爱的 library 中那些最有用独立函数,而你的项目不必携带其他未使用的代码。ES6 模块最终还是要由浏览器原生实现,但当前 Rollup 可以使你提前体验。
为什么使用rollup
如果你将项目拆分成小的单独文件中,这样开发软件通常会很简单,因为这通常会消除无法预知的相互影响(remove unexpected interaction),以及显著降低了所要解决的问题的复杂度(complexity of the problem),并且可以在项目最初时,就简洁地编写小的项目。不幸的是,JavaScript 以往并没有将此功能作为语言的核心功能。
而rollup基于ES2015模块的,相比于webpack或Browserify所使用的CommonJS模块更加有效率,因为Rollup使用一种叫做
tree-shaking的特性来移除模块中未使用的代码,这也就是说当我们引用一个库的时候,我们只用到一个库的某一段的代码的时候,它不会把所有的代码打包进来,而仅仅打包使用到的代码。
它的优点有如下
- 能组合我们的脚本文件。
- 移除未使用的代码(仅仅使用ES6语法中)。
- 在浏览器中支持使用 Node modules。
- 压缩文件代码使文件大小尽可能最小化。
tree-shaking
除了使用 ES6 模块之外,Rollup 还静态分析代码中的 import,并将排除任何未实际使用的代码。这允许您架构于现有工具和模块之上,而不会增加额外的依赖或使项目的大小膨胀。
例如,在使用 CommonJS 时,必须导入(import)完整的工具(tool)或库(library)对象。
1 | // 使用 CommonJS 导入(import)完整的 utils 对象 |
但是在使用 ES6 模块时,无需导入整个 utils
对象,我们可以只导入(import)我们所需的 ajax
函数:
1 | // 使用 ES6 import 语句导入(import) ajax 函数 |
因为 Rollup 只引入最基本最精简代码,所以可以生成轻量、快速,以及低复杂度的 library 和应用程序。因为这种基于显式的 import
和 export
语句的方式,它远比「在编译后的输出代码中,简单地运行自动 minifier 检测未使用的变量」更有效。
注意:Rollup只会在ES6模块中支持tree-shaking特性。目前按照CommonJS模块编写的jquery不能被支持tree-shaking.
兼容性
导入 CommonJS
Rollup 可以通过@rollup/plugin-commonjs插件 导入现有的 CommonJS 模块。
发布 ES 模块
为了确保你的 ES6 模块可以直接与「运行在 CommonJS(例如 Node.js 和 webpack)中的工具(tool)」使用,你可以使用 Rollup 编译为 UMD 或 CommonJS 格式,然后在 package.json 文件的 main 属性中指向当前编译的版本。如果你的 package.json 也具有 module 字段,像 Rollup 和 webpack 2 这样的 ES6 感知工具(ES6-aware tools)将会直接 导入ES6 模块版本。
使用Rollup
安装
rollup一般有两种安装模式,全局与本地,建议本地安装,版本及协作时好维护。
全局安装
1 | npm install --global rollup |
这将使 Rollup 可用作全局命令行工具。
本地安装
在团队或分布式环境中工作时,将 Rollup 添加为本地依赖项是明智的。在本地安装 Rollup 可以防止要求多个贡献者单独安装 Rollup 作为额外的步骤,并确保所有贡献者都使用相同版本的 Rollup。
使用 NPM 在本地安装 Rollup:
1 | npm install rollup --save-dev |
或使用yarn:
1 | yarn -D add rollup |
rollup一般我们有两种使用方式,命令行和配置文件方式。
命令行使用
这些命令假定您的应用程序的入口点名为main.js,并且您希望所有导入都编译到一个名为bundle.js.
1 | -i, --input <filename> 要打包的文件(必须) |
例如:
对于浏览器:
1 | # compile to a <script> containing a self-executing function ('iife') |
对于 Node.js:
1 | # compile to a CommonJS module ('cjs') |
对于浏览器和 Node.js:
1 | # UMD format requires a bundle name |
配置文件(推荐)
我们一般在命令行中使用Rollup。也可以提供一份配置文件来简化命令行操作,同时还能启用Rollup的高级特性;
Rollup的配置文件是可选的,但是使用配置文件的作用很强大,而且很方便,因此我们推荐你使用
配置文件是一个ES6模块,它对外暴露一个对象,这个对象包含了一些Rollup需要的一些选项。通常,我们把这个配置文件叫做rollup.config.js
,它通常位于项目的根目录
仔细查阅这个包含大量选项的清单,你可以根据你自己的需要把它配置到你的配置文件中
1 | // rollup.config.js |
你必须使用配置文件才能执行以下操作:
- 把一个项目打包,然后输出多个文件
- 使用Rollup插件, 例如 @rollup/plugin-node-resolve 和 @rollup/plugin-commonjs 。这两个插件可以让你加载Node.js里面的CommonJS模块
如果你想使用Rollup的配置文件,记得在命令行里加上--config
或者-c
1 | # 默认使用rollup.config.js$ rollup --config |
例如可以在项目的根目录中运行:
1 | npx rollup --config或yarn rollup --config |
但是安装后,通常的做法是向package.json
添加单个构建脚本,为所有贡献者提供方便的命令。例如
1 | { |
基本使用
在项目的根目录下新建一个配置文件为 rollup.config.js,进行输入和输出配置:
1 | export default { |
下面再来了解一下format配置的含义:
- format: rollup支持的多种输出格式(有amd,cjs, es, iife 和 umd)
- sourcemap 可以进行 sourcemap 的配置,在调试代码时会提供很大的帮助,这个选项会在生成文件中添加 sourcemap,来让事情变得更加简单。
我们在package.json代码下配置运行命令,方便操作。
1 | "scripts": { |
因此我们只要在命令行中 输入命令:npm run build 即可完成打包;
示例:
src/js/es_1.js 代码如下:
1 | export function a(x) { |
src/js/es_2.js代码如下:
1 | function c(x,y) { |
src/main.js代码如下:
1 | import { a } from './js/es_1.js'; |
最终会在项目的根目录下生成文件 bundle.js, 代码如下:
1 | (function () { |
我们可以观察到我们没有使用的方法打包时没有打包进最终代码内。这就是rollup的有点之一。
除了基本功能,我们根据需求的扩展,还有很多的功能需要组合与配置,也有很多插件或者开源依赖帮我们完成更多的需求。
例如以上代码都是ES6的模块化规范(esm),然而现今,很多工具类及模块依旧使用的其它模块化规范,主要以node使用的CommonJS(csj)为主。而rollup默认只支持esm规范,如果要正常引入csj规范,需要借助@rollup/plugin-node-resolve和 @rollup/plugin-commonjs插件。并且以上代码打包后,可能一些语法,只有支持ES6的现代浏览器支持,所以为了兼容旧浏览器,我们经常还用到了babel来让旧浏览器也能正常使用。
JavaScript API使用
Rollup 提供 JavaScript 接口那样可以通过 Node.js 来使用。你可以很少使用,而且很可能使用命令行接口,除非你想扩展 Rollup 本身,或者用于一些难懂的任务,例如用代码把文件束生成出来。
rollup.rollup
The rollup.rollup
函数返回一个 Promise,它解析了一个 bundle
对象,此对象带有不同的属性及方法,如下:
1 | const rollup = require('rollup'); |
inputOptions
inputOptions
对象包含下列属性 (查看big list of options 以获得这些参数更详细的资料):
1 | const inputOptions = { |
复制
outputOptions
outputOptions
对象包括下列属性 (查看 big list of options 以获得这些参数更详细的资料):
1 | const outputOptions = { |
rollup.watch
我们在使用gulp或者webpack中,都经常需要针对文件修改进行监听,以便实时编译或者执行某些操作,rollup虽然偏向于打包sdk,但是也可以用于项目开发的,所以也可以借助插件做到文件监听。
老版本rollup需要导入rollup-watch
插件来实现监听。0.46版本后默认提供了 rollup.watch
函数,当它检测到磁盘上单个模块已经改变,它会重新构建你的文件束。 当你通过命令行运行 Rollup,并带上 --watch
标记时,此函数会被内部使用。
1 | const rollup = require('rollup'); |
watchOptions
watchOptions
参数是一个你会从一个配置文件中导出的配置 (或一个配置数据)。
1 | const watchOptions = { |
查看以上文档知道更多 inputOptions
和 outputOptions
的细节, 或查询 big list of options 关 chokidar
, include
和 exclude
的资料。
扩展插件
CommonJS规范兼容
一般会用到下面两个插件。
@rollup/plugin-commonjs 插件用于将CommonJS模块转换为ES6。
@rollup/plugin-node-resolve 用于告诉 Rollup 如何查找外部模块。
首先安装依赖:
1 | npm install @rollup/plugin-node-resolve @rollup/plugin-commonjs -D |
配置(引入csj规范导出esm规范):
1 | import resolve from '@rollup/plugin-node-resolve'; |
示例:
src/js/csj.js 代码如下:
1 | function add(data1, data2) { |
src/main.js代码如下:
1 | const add = require('./js/csj'); |
最终会在项目的根目录下生成文件 bundle.js, 代码如下:
1 | var main = {}; |
Babel转换(适配ES5)
Babel 是一个工具链,主要用于将采用 ECMAScript 2015+ 语法编写的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中。webpack,rollup等等都有对应的babel插件封装。具体Babel官方网站或者我的Babel学习笔记。
我们首先需要安装一些依赖项如下命令(目前最新版本babel7):
1 | npm install @rollup/plugin-babel @babel/core @babel/preset-env -D |
然后配置rollup,注意当commonjs与babel同时使用时,commonjs必须在前面。
1 | import resolve from '@rollup/plugin-node-resolve'; |
接下来需要在项目的根目录下创建 babel.config.json( v7.8.0+,v7.8.0以下版本babel.config.js)配置文件,并进行配置:
1 | // v7.8.0+ babel.config.json |
为了避免转译第三方脚本,我们需要设置一个 exclude 的配置选项来忽略掉 node_modules 目录下的所有文件。安装完成后,我们重新运行命令;
1 | // Rollup plugins |
示例:
src/js/babel.js 代码如下:
1 | const add = (data1, data2)=> { |
src/main.js 代码如下:
1 | const add = require('./js/babel'); |
然后打包后代码变成如下:
1 | var main = {}; |
我们对比下代码,可以看到箭头函数以及const解析成function与var了。
注意: Babel也提供了 babel-polyfill, 也可以让IE8之前的浏览器能够顺利执行。
Replace插件
我们通常有打包时全局替换目标字符串的需求,我们通常使用@rollup/plugin-replace插件就可以实现。可以用来替换全局模块的引入路径以及简化部分写法。
安装:
1 | npm install @rollup/plugin-replace --save-dev |
1 | import replace from '@rollup/plugin-replace'; |
我们node环境(例如rollup编译操作)中,可以根据执行的NODE变量定义不同的编译流程,但是我们需要编译的目标源码中不是node环境,拿不到我们的变量,如果我们需要根据不同的编译环境(比如生产和测试)执行一些不同的流程,那我们可以提前借助replace插件将环境提前写入到编译配置里,然后让源码根据定义的环境走不同的编译流程.详见下节。
替换环境变量与日志
因为只需要生产环境时进行代码压缩或者一些其它操作,开发环境部署不压缩等等。通常我们在node环境下使用process.env.NODE_ENV便可以获取到当前环境变量,但是我们可能需要在我们的打包代码中也使用这个环境变量,例如全局开关log信息等等。
NODE_ENV
NODE_ENV这个变量通常用来区分开发与生产环境,加载不同的配置。
node中有全局变量process表示当前node进程,process.env包含着关于系统环境的信息。但是process.env中并不存在NODE_ENV这个东西,NODE_ENV只是一个用户自定义的变量,当我们在服务启动时配置NODE_ENV,或在代码中给process.env.NODE_ENV赋值,js便能通过process.env.NODE_ENV获取信息。
在类unix系统下,运行
NODE_ENV=production xxx
就可以将变量带入进去在winodw环境下,运行
SET NODE_ENV=production xxx
,就可以将变量带入进去或者借助cross-env兼容两个系统环境,
cross-env NODE_ENV=production xxx
定义环境变量
配置
1 | import resolve from '@rollup/plugin-node-resolve'; |
Debug日志
安装:
1 | npm install --save debug |
使用:
1 | const add = require('./js/babel'); |
根据环境变量决定是否打开日志:
1 | const add = require('./js/babel'); |
压缩与混淆
我们经常需要对我们最终编译好的代码就行压缩和混淆,我们经常使用uglifyjs或者terser,rollup也有对应的插件rollup-plugin-uglify和rollup-plugin-terser,使我们更方便的在rollup中集成使用。
使用uglifyjs
安装插件:
1 | npm install --save-dev rollup-plugin-uglify |
配置:
1 | import { uglify } from "rollup-plugin-uglify"; |
编译结果:
1 | var main={},babel={};const add$1=(a,d)=>a+d;babel.default=add$1;const add=babel,addP=add(1,2);main.default=addP;export{main as default}; |
使用terser(推荐)
安装插件:
1 | npm install rollup-plugin-terser --save-dev |
配置:
1 | import { terser } from "rollup-plugin-terser"; |
编译结果:
1 | var a={},t={};t.default=function(a,t){return a+t};var e=t(1,2);a.default=e;export{a as default}; |
为什么使用terser
1.uglify-js不支持ES6 +。而针对es6+的库uglify-es不再维护
2.terser是uglify-es的分支,主要保留了uglify-es和uglify-js@3的API以及CLI的兼容性。