Rollup:一个高效的现代 JavaScript 模块捆绑程序

Rollup 是一个非常流行和成熟的 JavaScript 的模块打包器,你可以利用它轻松打包 JavaScript 库或应用程序。

Vitemicrobundle 这些新打包工具内部也都有基于 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.jschunk-[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.rolluprollup.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/)。

参考链接

相关推荐
尸僵打怪兽1 小时前
后台数据管理系统 - 项目架构设计-Vue3+axios+Element-plus(0920)
前端·javascript·vue.js·elementui·axios·博客·后台管理系统
ggome1 小时前
Uniapp低版本的安卓不能用解决办法
前端·javascript·uni-app
Ylucius1 小时前
JavaScript 与 Java 的继承有何区别?-----原型继承,单继承有何联系?
java·开发语言·前端·javascript·后端·学习
前端初见1 小时前
双token无感刷新
前端·javascript
bin91532 小时前
前端JavaScript导出excel,并用excel分析数据,使用SheetJS导出excel
前端·javascript·excel
.生产的驴2 小时前
SpringBoot 消息队列RabbitMQ 消息确认机制确保消息发送成功和失败 生产者确认
java·javascript·spring boot·后端·rabbitmq·负载均衡·java-rabbitmq
打野赵怀真2 小时前
你有看过vue的nextTick源码吗?
前端·javascript
书中自有妍如玉3 小时前
layui时间选择器选择周 日月季度年
前端·javascript·layui
Riesenzahn3 小时前
canvas生成图片有没有跨域问题?如果有如何解决?
前端·javascript
f8979070703 小时前
layui 可以使点击图片放大
前端·javascript·layui