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

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

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

Vite[1]microbundle[2] 这些新打包工具内部也都有用 Rollup 做打包支持。不仅是因为 Rollup 的打包速度、生态系统,还因为有一套定义成熟的 API。

下面我们就来学习。

创建第一个捆绑包

我们从创建第一个捆绑包开始说起。首先创建项目

$ mkdir -p rollupjs-demos/src
cd rollupjs-demos 

初始化并安装 rollup 依赖:

$ pnpm init
$ pnpm install -D rollup

$npx rollup -v

rollup v3.28.1

创建 src/main.js 文件,内容如下:

// src/main.js
import foo from './foo.js';
export default function ({
  console.log(foo);
}

src/foo.js 的内容就很简单了:

// src/foo.js
export default 'hello world!';

package.json 中增加 build 脚本:

{
  "scripts": {
    "build""rollup src/main.js -f cjs"
  },
}

执行 build 脚本:

$ 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)指定将结果输出到某个文件中。

{
  "scripts": {
    "build""rollup src/main.js -o bundle.js -f cjs"
  },
}

再次执行:

$ 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 文件,查看下文件内容:

'use strict';

var foo = 'hello world!';

function main ({
  console.log(foo);
}

module.exports = main;

使用配置文件

当然,Rollup 也支持配置文件。rollup src/main.js -o bundle.js -f cjs 等同于以下配置内容:

// rollup.config.mjs

/**
 * @type {import('rollup').RollupOptions}
 */

export default {
 input'src/main.js',
 output: {
  file'bundle.js',
  format'cjs'
 }
};

使用 -c 选项启用配置文件打包模式:

$ 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 脚本:

{
  "scripts": {
-    "build": "rollup src/main.js -o bundle.js -f cjs"
+    "build": "rollup -c" 
  },
}

执行:

$ 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[3]  repo 上维护了一份优秀插件列表。

在本教程中,我们将使用 @rollup/plugin-json[4],它支持 Rollup 导入 JSON 文件数据。

首先安装依赖(作为开发依赖,因为生产环境不需要):

$ pnpm install --save-dev @rollup/plugin-json

src/main.js 改成以下内容:

// src/main.js
import { version } from '../package.json';

export default function ({
 console.log('version ' + version);
}

配置文件中增加读取 json 文件的支持:

// 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 字段添加插件的:

$ npm run build

> rollupjs-demos@1.0.0 build
> rollup -c


src/main.js → bundle.js...
created bundle.js in 26ms

查看打包出来的文件 bundle.js  的内容:

'use strict';

var version = "1.0.0";

function main ({
  console.log('version ' + version);
}

module.exports = main;

使用输出阶段的插件

有些插件还可以专门应用于 Rollup  打包的输出阶段。关于输出插件的技术细节,可以参阅插件钩子[5]一章的文档。简而言之,这些插件只能在 Rollup 的主要分析(main analysis)完成后才能修改代码。如果将不兼容的插件作为特定输出插件使用,Rollup 会发出警告。其中一个可能的用例是对要在浏览器中使用的捆绑包进行最小化。

我们扩展下前面的示例,分别提供最小化构建和默认构建(没有压缩)。为此,我们需要安装 @rollup/plugin-terser

$ pnpm install --save-dev @rollup/plugin-terser

编辑 rollup.config.mjs 文件,将 output 字段改成数组类型,添加一个最小化输出,格式方面,我们选择 iife

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)。

$ 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

var version=function(){"use strict";return function(){console.log("version 1.0.0")}}();

代码分割

代码分割(Code splitting)是将代码划分为可以按需/同时加载的多个 bundle(又叫 chunk 文件) 或组件。随着应用程序日趋复杂,代码分割避通过将脚本拆分为多个较小的文件的方式避免下载巨大的文件,换得代码执行性能的提升。

Rollup 会对动态加载或多个入口点的地方自动使用代码分割,将代码拆分成块。要使用代码分割功能实现惰性动态加载,我们返回到原始示例,并修改 src/main.js

// src/main.js
export default function ({
 import('./foo.js').then((default: foo }) => console.log(foo));
}

修改 build 脚本:

{
  "scripts": {
    "build""rollup src/main.js -f cjs -d dist"
  },
}

-d 选项是 --dir 的简写形式,指定打包文件的输出目录。

$ 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] 是基于内容的哈希字符串。

//→ 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——前者是构建模式,后者是监听模式。构建模式用于打包生产包;监听模式则支持我们可以一边修改代码、一边查看输测试出,用于开发阶段。

import { rollup, watch } from 'rollup';

// 构建模式
const bundle = await rollup(inputOptions);
const { output } = await bundle.write(outputOptions);

// 监听模式
const watchOptions = {...};
const watcher = watch(watchOptions);

构建模式案例

先写一个简单的 Rollup 构建模式的封装。

#!/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)。

执行查看结果:

$ ./microbundle.mjs 
Bundling completed successfully!

成功,发现输出了 dist/main.js 文件。

监听模式案例

再一个简单的 Rollup 监听模式的封装。

#!/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 上的事件监听到打包的不同阶段。

执行查看结果:

$ ./microbundle.mjs 
Bundling started...
Bundling completed successfully!

成功。

总结

这个文章我们简单地介绍了下现代打包工具 Rollup 的使用。设计插件、代码分隔等核心概念。并采用编程的方式基于 Rollup 写了 2 个简易版本的打包 CLI。

更进一步的学习,大家可以参考官方中文站点(https://cn.rollupjs.org/)。

参考链接

  • https://cn.rollupjs.org/tutorial/
  • https://developer.mozilla.org/zh-CN/docs/Glossary/Code_splitting
  • https://cn.rollupjs.org/javascript-api/

References

[1]

Vite: https://vitejs.dev/

[2]

microbundle: https://www.npmjs.com/package/microbundle

[3]

Rollup Awesome List: https://github.com/rollup/awesome

[4]

@rollup/plugin-json: https://github.com/rollup/plugins/tree/master/packages/json

[5]

插件钩子: https://rollupjs.org/plugin-development/#build-hooks


原文始发于微信公众号(写代码的宝哥):Rollup:一个高效的现代 JavaScript 模块捆绑程序

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/243845.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!