前言
之前一直用 webpack 来打包前端项目,虽说配置复杂,但用多了也沉淀出了自己的配置,能够应对多数的打包和构建场景。然而最近在折腾一些自己的小项目时,需要打包一些类库发布到 npm 。虽说 webpack 也可以构建 library ,但是打包出的产物体积过大,而且代码也不是那么的“干净”,并且当想通过 webpack 一次打包出不同的版本,比如 esm 、 CommonJS 、 umd 等,webpack 就显得更加难用,于是就把目光锁定到了 rollup 。
什么是 rollup
rollup 和 webpack 一样,都是 JavaScript 模块打包器,用于打包和构建 JavaScript 应用程序和 library 。而 rollup 则更适合打包 library 且自身更为小巧和简单,所以有些开发应用时需要的功能,rollup 反而不支持,比如模块热更新(HMR)。我们熟知的 Vue 、 React 都是通过 rollup 打包。并且 rollup 进入大多是开发者的视野也要得益于 React 。2017年4月初,Facebook 将一个巨大的 pull 请求 合并到了 React 主分支中,将其现有的构建流程替换为基于 rollup ,这一举动让 rollup 得到了更多开发者的关注。
快速上手
首先创建项目,我们将会实现一个计算器类库,用于解决 js 在计算加减乘除时产生的精度丢失问题。项目目录如下:
仓库地址: https://github.com/onlymisaky/calculator
| calculator | 
index.js 文件中包含 加减乘除 四个方法,并通过匿名导出向外部暴露这些方法:
| export default { | 
utils.js 中则是一些辅助方法,无需多言。
由于只是一个简单的计算功能,并不设计和平台有关的功能或 API ,所以我们要实现的这个 library 可以在任意的 js 环境下使用,比如浏览器、 node 、 Electron 等等。所以我们也需要构建出不同版本的包。
接下来就开始正式进入 rollup 的正式使用。首先就是安装 rollup :
| npm i rollup -D | 
和 webpack 一样, rollup 可以通过命令的方式直接使用(需要将 rollup 全局安装):
| rollup src/index.js -f umd -o dist/index.js | 
上面的命令表示将 src/index.js 以 umd 形式打包,输出到 dist/index.js 。
很显然这种方式及其不灵活,所以只提一下不做详细的参数介绍。
最常用的方法还是使用配置文件的方式,不必担心, rollup 的配置文件比 webpack 简单多了,甚至比 gulp 的还简单。
核心概念
在介绍如何编写 rollup 的配置文件前,我么需要先了解几个它的核心概念,这有助于我们更好使用。
- input: 入口文件,对标- webpack的- entry,指明了库文件入口位置
- output: 输出文件,对标- webpack的- output,指明了打包后输出文件的位置、包名、格式等等
- plugins: 插件,在构建过程中,需要一些辅助功能,都通过插件实现,比如语法转换、别名解析
- external: 当我们的库是基于另一个库开发时,就需要用到它,比如开发一个基于- Vue的指令,为了不将 Vue 打包到我们的库中,就需要将 Vue 写在 external 中。
以上就是 rollup 的一些核心概念,相较于 webpack 确实简化了许多,在了解核心概念后,编写 rollup 配置文件就简单多了。
编写和使用配置文件
在项目根目录创建 rollup.config.js 文件,代码内容如下:
| import pkg from './package.json'; | 
上面的配置代码中,只涉及到了 input 和 output 两个概念,需要额外解释的是 output 这个选项。
output 允许传入一个对象会数组,当传入数组时,会依次输出多个文件。
- output.file: 表示输出的文件路径
- output.format: 表示输出文件的格式,可选项有- umd、- commonjs、- esm等。
- output.name: 当- format值为- umd时,需要设置- output.name,在浏览器环境下就可通过- name使用。
- output.name: 导出方式,可选值有- default、- named、- none、- auto,我们是匿名导出,就填写- default。
- output.banner: 文件头部添加的内容,当然也有对应的- output.footer选项用于文件末尾添加的内容 。
接下来使用 rollup -c 命令便可以打包,就可以在 dist 文件夹输出 index.js 文件。为了方便,将 rollup -c  加入 npm scripts 中,运行 npm run build,得到的输出文件内容大致如下:
| 
 | 
这是一个标准的 umd 格式的包,我们可以在 浏览器环境下通过 Calculator 直接使用,也可以在 node 环境下通过 requrie 调用。
babel 插件
大致浏览打包出的代码后,可以发现有些较新的语法被没有转换,比如 模板字符串 、 箭头函数 、 rest参数 等等,这些代码在 es5 环境下是无法运行的。为什么会这样呢?因为 rollup 是不会转换这些的,那么就需要用到 babel 插件,来转换这些新的语法。
| npm i @babel/core @babel/preset-env @rollup/plugin-babel -D | 
rollup.config.js 文件中添加 plugin 配置项: 
| import babel from '@rollup/plugin-babel'; | 
创建 babel.config.js 配置文件:
| /** @type {import('@babel/core').TransformOptions} */ | 
配置好 babel 之后,再次打包,我们以发现一些高级的语法和 API (这里笔者偷懒了,实际上在没有安装和配置 @babel/runtime 等相关插件前,babel 只能转换语法,不能转化 API ,比如代码中的 includes 就是新的 API 但是并没有被转换,等笔者写完 babel 相关文章后再来更新) 已经被转换可以在低版本环境中运行了。
resolve 插件
在上述场景中,我们并没有引用其他的包,但在实际开发中引用第三方包辅助快速开发是非常常见的场景。
在默认情况下,如果我们直接导入 node_modules 中的包,打包完成之后,node_modules 中的包并不会和我们编写的库合并。为了举例说明,我们将安装一个用于迭代字符串的包 repeat-string ,然后在 index.js 导入并使用:
| import repeat from 'repeat-string'; | 
在运行 npm run build 之后可以发现,虽然打包成功了,但是控制台确有一些警告提示: 
| (!) Unresolved dependencies | 
打包后的代码中也可以看到,repeat-string 默认是以参数的形式注入其中,而不是与我们的代码合并。
为了解决这个问题,就要用到 @rollup/plugin-node-resolve 插件,他可以帮助我们解析 node_modules 中的第三方包。
| npm i @rollup/plugin-node-resolve -D | 
rollup.config.js :
| import babel from '@rollup/plugin-babel'; | 
再次运行 npm run build 之后,你会惊讶的发现,直接报错了: 
| src/index.js → dist/index.js... | 
这是因为 repeat-string 是 commonjs 格式的包,而我们是以 ESModule 的方式引入的,所以就报错了。所以需要继续借助插件将 commonjs 转换为 ESM ,就是接下来要介绍的 commonjs 插件。
commonjs 插件
安装和配置:
| npm i @rollup/plugin-commonjs -D | 
| import babel from '@rollup/plugin-babel'; | 
npm run build 之后,可以发现不仅正确的解析了 repeat-string ,而且也将代码和我们的库合并了。
其他常用插件
除了上述的三个插件之外,还有一些常见的插件:
- @rollup/plugin-json: 解析编译源码中的- json文件,并且配合- rollup的- Tree Shaking可只打包- .json文件中我们真正用到的部分。
- @rollup/plugin-typescript: 解析和转换- typescript
- @rollup/plugin-eslint: eslint 插件
- rollup-plugin-terser: 压缩代码
其它更多插件可以到官方仓库中查找: https://github.com/rollup/plugins
external 属性
当配置了 @rollup/plugin-node-resolve 和 @rollup/plugin-commonjs 之后, 所有从 node_modules 中导入的包都会合并到我们的库中,有时候我们并不希望如此,比如 Vuex 和 VueRouter 都基于 Vue 开发,但打包出的代码中若包含了 Vue 的源码,那显然不合适,所以需要将 Vue 设置为外部项,也就是通过 external 属性来设置。如果回到我们的案例中,我们想将 repeat-string 也设置为外部项,只需做如下修改:
| import resolve from '@rollup/plugin-node-resolve'; | 


