Rollup 是一个非常流行和成熟的 JavaScript 的模块打包器,你可以利用它轻松打包 JavaScript 库或应用程序。
像 Vite、microbundle 这些新打包工具内部也都有基于 Rollup 做打包支持。不仅是因为 Rollup 的打包速度、生态系统,还因为有一套定义成熟的 API。
下面我们就来学习。
创建第一个捆绑包
我们从创建第一个捆绑包开始说起。首先创建项目:
bash
$ mkdir -p rollupjs-demos/src
$ cd rollupjs-demos
初始化并安装 rollup
依赖:
bash
$ pnpm init
$ pnpm install -D rollup
$npx rollup -v
rollup v3.28.1
创建 src/main.js
文件,内容如下:
javascript
// src/main.js
import foo from './foo.js';
export default function () {
console.log(foo);
}
src/foo.js
的内容就很简单了:
javascript
// src/foo.js
export default 'hello world!';
package.json
中增加 build
脚本:
json
{
"scripts": {
"build": "rollup src/main.js -f cjs"
},
}
执行 build
脚本:
bash
$ npm run build
> rollupjs-demos@1.0.0 build
> rollup src/main.js -f cjs
src/main.js → stdout...
'use strict';
var foo = 'hello world!';
function main () {
console.log(foo);
}
module.exports = main;
-f
选项(--format
的缩写)指定了我们要创建的捆绑包输出格式,在本例中是 CommonJS(将在 Node.js 中运行)。由于我们没有指定输出文件,因此将直接打印到标准输出(stdout)。
可以通过指定 -o
选项(等同于 --file
或 --output
)指定将结果输出到某个文件中。
json
{
"scripts": {
"build": "rollup src/main.js -o bundle.js -f cjs"
},
}
再次执行:
json
$ npm run build
> rollupjs-demos@1.0.0 build
> rollup src/main.js -o bundle.js -f cjs
src/main.js → bundle.js...
created bundle.js in 25ms
成功输出 bundle.js
文件,查看下文件内容:
javascript
'use strict';
var foo = 'hello world!';
function main () {
console.log(foo);
}
module.exports = main;
使用配置文件
当然,Rollup 也支持配置文件。rollup src/main.js -o bundle.js -f cjs
等同于以下配置内容:
javascript
// rollup.config.mjs
/**
* @type {import('rollup').RollupOptions}
*/
export default {
input: 'src/main.js',
output: {
file: 'bundle.js',
format: 'cjs'
}
};
使用 -c
选项启用配置文件打包模式:
bash
$ npx rollup -c
src/main.js → bundle.js...
created bundle.js in 24ms
-c
flag 是 --config
的简写,表示 Rollup 打包时使用配置文件(默认 rollup.config.{mjs,cjs,js}
,优先级 .mjs > .cjs > .js
)
或者修改 build
脚本:
diff
{
"scripts": {
- "build": "rollup src/main.js -o bundle.js -f cjs"
+ "build": "rollup -c"
},
}
执行:
bash
$ npm run build
> rollupjs-demos@1.0.0 build
> rollup -c
src/main.js → bundle.js...
created bundle.js in 24ms
使用插件
到目前为止,我们已经从一个入口点和一个通过相对路径导入的模块创建了一个简单的捆绑包。当你创建更复杂的捆绑包时,往往需要 Rollup 的插件来满足需求,比如使用 Babel 编译代码、或是导入 JSON 文件等等。
为此,我们使用插件来改变 Rollup 在捆绑过程中关键点(key points)的行为。Rollup Awesome List repo 上维护了一份优秀插件列表。
在本教程中,我们将使用 @rollup/plugin-json,它支持 Rollup 导入 JSON 文件数据。
首先安装依赖(作为开发依赖,因为生产环境不需要):
bash
$ pnpm install --save-dev @rollup/plugin-json
src/main.js
改成以下内容:
javascript
// src/main.js
import { version } from '../package.json';
export default function () {
console.log('version ' + version);
}
配置文件中增加读取 json
文件的支持:
javascript
// rollup.config.mjs
import json from '@rollup/plugin-json';
export default {
input: 'src/main.js',
output: {
file: 'bundle.js',
format: 'cjs'
},
plugins: [json()]
};
Rollup 是通过配置文件中的 plugins
字段添加插件的:
bash
$ npm run build
> rollupjs-demos@1.0.0 build
> rollup -c
src/main.js → bundle.js...
created bundle.js in 26ms
查看打包出来的文件 bundle.js
的内容:
javascript
'use strict';
var version = "1.0.0";
function main () {
console.log('version ' + version);
}
module.exports = main;
使用输出阶段的插件
有些插件还可以专门应用于 Rollup 打包的输出阶段。关于输出插件的技术细节,可以参阅插件钩子一章的文档。简而言之,这些插件只能在 Rollup 的主要分析(main analysis)完成后才能修改代码。如果将不兼容的插件作为特定输出插件使用,Rollup 会发出警告。其中一个可能的用例是对要在浏览器中使用的捆绑包进行最小化。
我们扩展下前面的示例,分别提供最小化构建和默认构建(没有压缩)。为此,我们需要安装 @rollup/plugin-terser
:
bash
$ pnpm install --save-dev @rollup/plugin-terser
编辑 rollup.config.mjs
文件,将 output
字段改成数组类型,添加一个最小化输出,格式方面,我们选择 iife
。
diff
import json from '@rollup/plugin-json'
/**
* @type {import('rollup').RollupOptions}
*/
export default {
input: 'src/main.js',
- output: {
- file: 'bundle.js',
- format: 'cjs'
- },
+ output: [
+ {
+ file: 'bundle.js',
+ format: 'cjs'
+ },
+ {
+ file: 'bundle.min.js',
+ format: 'iife',
+ name: 'version',
+ plugins: [terser()]
+ }
+ ],
plugins: [json()]
}
iife
格式对会代码进行封装,可以直接通过浏览器中的脚本标签使用,同时避免了与其他代码之间不必要的交互。我们的代码有一个输出,因此需要提供一个全局变量名,这个变量将由 Rollup 在打包时创建,以便其他代码可以通过这个变量访问我们的代码。
就能发现,每个 output
成员还单独支持使用 plugins
字段使用自定义插件。另外,iife
格式下的name
字段,用于指定当前包导出时使用的全局变量(version
)。
bash
$ npm run build
> rollupjs-demos@1.0.0 build
> rollup -c
src/main.js → bundle.js, bundle.min.js...
created bundle.js, bundle.min.js in 160ms
除了 bundle.js
之外,Rollup 现在还会创建第二个文件 bundle.min.js
:
javascript
var version=function(){"use strict";return function(){console.log("version 1.0.0")}}();
代码分割
代码分割(Code splitting)是将代码划分为可以按需/同时加载的多个 bundle(又叫 chunk 文件) 或组件。随着应用程序日趋复杂,代码分割避通过将脚本拆分为多个较小的文件的方式避免下载巨大的文件,换得代码执行性能的提升。
Rollup 会对动态加载或多个入口点的地方自动使用代码分割,将代码拆分成块。要使用代码分割功能实现惰性动态加载,我们返回到原始示例,并修改 src/main.js
:
javascript
// src/main.js
export default function () {
import('./foo.js').then(({ default: foo }) => console.log(foo));
}
修改 build
脚本:
json
{
"scripts": {
"build": "rollup src/main.js -f cjs -d dist"
},
}
-d
选项是 --dir
的简写形式,指定打包文件的输出目录。
bash
$ npm run build
> rollupjs-demos@1.0.0 build
> rollup src/main.js -f cjs -d dist
src/main.js → dist...
created dist in 24ms
这将创建一个名为 dist
的文件夹,其中包含两个文件,main.js
和 chunk-[hash].js
, 其中 [hash]
是基于内容的哈希字符串。
javascript
//→ main.js:
'use strict';
function main() {
Promise.resolve(require('./chunk-b8774ea3.js')).then(({ default: foo }) =>
console.log(foo)
);
}
module.exports = main;
//→ chunk-b8774ea3.js:
('use strict');
var foo = 'hello world!';
exports.default = foo;
自定义分块文件的命名方式
Rollup 默认会基于内容的哈希字符串作为分块文件名。你可以通过指定 output.chunkFileNames
(指定 chunk 文件名,默认值 "[name]-[hash].js
") 和 output.entryFileNames
(指定入口文件名,默认值"[name].js
") 选项来提供自己的命名模式。
需要注意的是,代码拆分构建不支持 UMD 和 IIFE 输出格式!
基于 Rollup 写简易版本的打包 CLI
以上我们都是用 rollup
指令方式使用的 Rollup。当然,Rollup 还支持以编程的方式使用。
Rollup 有两个核心 API:rollup.rollup
、rollup.watch
------前者是构建模式,后者是监听模式。构建模式用于打包生产包;监听模式则支持我们可以一边修改代码、一边查看输测试出,用于开发阶段。
javascript
import { rollup, watch } from 'rollup';
// 构建模式
const bundle = await rollup(inputOptions);
const { output } = await bundle.write(outputOptions);
// 监听模式
const watchOptions = {...};
const watcher = watch(watchOptions);
构建模式案例
先写一个简单的 Rollup 构建模式的封装。
javascript
#!/usr/bin/env node
import { rollup } from 'rollup';
const inputOptions = { input: 'src/main.js' }
const outputOptions = { dir: 'dist', format: 'cjs' }
async function bundle() {
try {
const bundle = await rollup(inputOptions);
await bundle.write(outputOptions);
console.log('Bundling completed successfully!');
} catch (error) {
console.error('An error occurred while bundling:', error);
}
}
bundle();
注意:直接执行代
#!/usr/bin/env node
头部脚本的文件需要在 Bash 环境终端才能生效(比如 Git Bash)。
执行查看结果:
bash
$ ./microbundle.mjs
Bundling completed successfully!
成功,发现输出了 dist/main.js
文件。
监听模式案例
再一个简单的 Rollup 监听模式的封装。
javascript
#!/usr/bin/env node
import { watch } from 'rollup';
const inputOptions = { input: 'src/main.js' }
const outputOptions = { dir: 'dist', format: 'cjs' }
async function bundle() {
try {
const watcher = watch({
...inputOptions,
output: [outputOptions],
watch: {
include: 'src/**', // Specify the directory or files to watch
},
});
watcher.on('event', (event) => {
if (event.code === 'START') {
console.log('Bundling started...');
} else if (event.code === 'END') {
console.log('Bundling completed successfully!');
} else if (event.code === 'ERROR') {
console.error('An error occurred while bundling:', event.error);
}
});
} catch (error) {
console.error('An error occurred while setting up the watcher:', error);
}
}
bundle();
监听 src/
目录下的文件改动。每次改动都会触发文件打包,我们可以通过 watch()
方法的返回对象 watcher
上的事件监听到打包的不同阶段。
执行查看结果:
bash
$ ./microbundle.mjs
Bundling started...
Bundling completed successfully!
成功。
总结
这个文章我们简单地介绍了下现代打包工具 Rollup 的使用。设计插件、代码分隔等核心概念。并采用编程的方式基于 Rollup 写了 2 个简易版本的打包 CLI。
更进一步的学习,大家可以参考官方中文站点(cn.rollupjs.org/)。