Rollup 基本概念及使用

news2024/11/25 15:24:56

Rollup是一款基于ESModule模块规范实现的JavaScript打包工具,在前端社区中赫赫有名,同时也在Vite的架构体系中发挥着重要作用。不仅是Vite生产环境下的打包工具,其插件机制也被Vite所兼容,可以说是Vite的构建基石。

接下来,我们将围绕Rollup的基本概念和核心特性展开,学习完本小节内容,你不仅能知道Rollup是如何打包项目的,还能学会Rollup更高阶的使用方式,甚至能够通过JavaScriptAPI二次开发Rollup。

一、快速上手

首先,创建一个空的文件夹,然后使用npm init -y新建一个项目,此时,打开项目会发现多了一个package.json文件。

{
  "name": "demo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo "Error: no test specified" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

接着,继续安装 rollup 依赖,命令如下。

npm i rollup

新增 src/index.js 和 src/util.js 和rollup.config.js 三个文件,目录结构如下所示。

.
├── package.json
├── pnpm-lock.yaml
├── rollup.config.js
└── src
    ├── index.js
    └── util.js

其中,index.js 和 util.js 和rollup.config.js 文件的内容分别如下。

// src/index.js
import { add } from "./util";
console.log(add(1, 2));


// src/util.js
export const add = (a, b) => a + b;


export const multi = (a, b) => a * b;
// rollup.config.js
// 以下注释是为了能使用 VSCode 的类型提示
/**
 * @type { import('rollup').RollupOptions }
 */
const buildOptions = {
  input: ["src/index.js"],
  output: {
    // 产物输出目录
    dir: "dist/es",
    // 产物格式
    format: "esm",
  },
};
export default buildOptions;

然后,在package.json中加入如下的构建脚本。

{
  // rollup 打包命令,`-c` 表示使用配置文件中的配置
  "build": "rollup -c"
}

接着,在终端执行一下npm run build,可以看到如下的命令行信息。

image.png

接下来,我们打开dist/es 目录查看一下产物的内容。

const add = (a, b) => a + b;
console.log(add(1, 2));

可以发现,util.js中的multi方法并没有被打包到产物中,这是因为 Rollup 具有天然的 Tree Shaking 功能,可以分析出未使用到的模块并自动擦除。

所谓 Tree Shaking(摇树),也是计算机编译原理中DCE(Dead Code Elimination,即消除无用代码) 技术的一种实现。由于 ES 模块依赖关系是确定的,和运行时状态无关。因此 Rollup 可以在编译阶段分析出依赖关系,对 AST 语法树中没有使用到的节点进行删除,从而实现 Tree Shaking。

二、常用配置解读

2.1 多产物配置

在打包 JavaScript 类库的场景中,我们通常需要对外暴露出不同格式的产物供他人使用,不仅包括 ESM,也需要包括诸如CommonJS、UMD等格式,保证良好的兼容性。那么,同一份入口文件,如何让 Rollup 给我们打包出不一样格式的产物呢?为了实现这一需求,我们基于上述的配置文件来进行如下修改。

// rollup.config.js
/**
 * @type { import('rollup').RollupOptions }
 */
const buildOptions = {
  input: ["src/index.js"],
  // 将 output 改造成一个数组
  output: [
    {
      dir: "dist/es",
      format: "esm",
    },
    {
      dir: "dist/cjs",
      format: "cjs",
    },
  ],
};


export default buildOptions;

从代码中可以看到,我们将output属性配置成一个数组,数组中每个元素都是一个描述对象,决定了不同产物的输出行为。

2.2 多入口配置

除了多产物配置,Rollup 中也支持多入口配置,而且通常情况下两者会被结合起来使用。接下来,就让我们继续改造之前的配置文件,将 input 设置为一个数组或者一个对象,如下所示。

{
  input: ["src/index.js", "src/util.js"]
}
// 或者
{
  input: {
    index: "src/index.js",
    util: "src/util.js",
  },
}

然后,再次执行npm run build。可以发现,所有入口的不同格式产物已经成功输出。

image.png

如果不同入口对应的打包配置不一样,我们也可以使用默认的配置来导出一个配置数组,如下所示。

// rollup.config.js
/**
 * @type { import('rollup').RollupOptions }
 */
const buildIndexOptions = {
  input: ["src/index.js"],
  output: [
    // 省略 output 配置
  ],
};


/**
 * @type { import('rollup').RollupOptions }
 */
const buildUtilOptions = {
  input: ["src/util.js"],
  output: [
    // 省略 output 配置
  ],
};


export default [buildIndexOptions, buildUtilOptions];

如果是比较复杂的打包场景(如 Vite 源码本身的打包),我们需要将项目的代码分成几个部分,用不同的 Rollup 配置分别打包。

2.3 自定义output配置

前面我们提到了input的使用,主要用来声明入口,可以配置成字符串、数组或者对象,使用比较简单。而output与之相对,用来配置输出的相关信息,常用的配置项如下。

output: {
  // 产物输出目录
  dir: path.resolve(__dirname, 'dist'),
  // 以下三个配置项都可以使用这些占位符:
  // 1. [name]: 去除文件后缀后的文件名
  // 2. [hash]: 根据文件名和文件内容生成的 hash 值
  // 3. [format]: 产物模块格式,如 es、cjs
  // 4. [extname]: 产物后缀名(带`.`)
  // 入口模块的输出文件名
  entryFileNames: `[name].js`,
  // 非入口模块(如动态 import)的输出文件名
  chunkFileNames: 'chunk-[hash].js',
  // 静态资源文件输出文件名
  assetFileNames: 'assets/[name]-[hash][extname]',
  // 产物输出格式,包括`amd`、`cjs`、`es`、`iife`、`umd`、`system`
  format: 'cjs',
  // 是否生成 sourcemap 文件
  sourcemap: true,
  // 如果是打包出 iife/umd 格式,需要对外暴露出一个全局变量,通过 name 配置变量名
  name: 'MyBundle',
  // 全局变量声明
  globals: {
    // 项目中可以直接用`$`代替`jquery`
    jquery: '$'
  }
}

2.4 依赖 external

对于某些第三方包,有时候我们不想让 Rollup 进行打包,也可以通过 external 进行外部化,配置如下。

{
  external: ['react', 'react-dom']
}

在 SSR 构建或者使用 ESM CDN 的场景中,这个配置将非常有用

2.5 接入插件

在Rollup的日常使用中,我们难免会遇到一些Rollup本身不支持的场景,比如兼容CommonJS打包、注入环境变量、配置路径别名、压缩产物代码等等。这个时候就需要我们引入相应的Rollup插件了。接下来以一个具体的场景为例带大家熟悉一下Rollup插件的使用。

虽然Rollup能够打包输出出CommonJS格式的产物,但对于输入给Rollup的代码并不支持CommonJS,仅仅支持ESM。你可能会说,那我们直接在项目中统一使用ESM规范就可以了啊,这有什么问题呢?需要注意的是,我们不光要考虑项目本身的代码,还要考虑第三方依赖。目前为止,还是有不少第三方依赖只有CommonJS格式产物而并未提供ESM产物,比如项目中用到lodash时,打包项目会出现这样的报错:

image.png

所以,我们需要引入额外的插件去解决这个问题,我们需要安装两个核心的插件包。

pnpm i @rollup/plugin-node-resolve @rollup/plugin-commonjs 

关于这两个插件包的说明如下:

  • @rollup/plugin-node-resolve是为了允许我们加载第三方依赖,否则像import React from ‘react’ 的依赖导入语句将不会被 Rollup 识别。
  • @rollup/plugin-commonjs 的作用是将 CommonJS 格式的代码转换为 ESM 格式

然后,我们在配置文件中导入这些插件,相关的配置如下:

// rollup.config.js
import resolve from "@rollup/plugin-node-resolve";
import commonjs from "@rollup/plugin-commonjs";


/**
 * @type { import('rollup').RollupOptions }
 */
export default {
  input: ["src/index.js"],
  output: [
    {
      dir: "dist/es",
      format: "esm",
    },
    {
      dir: "dist/cjs",
      format: "cjs",
    },
  ],
  // 通过 plugins 参数添加插件
  plugins: [resolve(), commonjs()],
};

现在,我们以lodash这个只有 CommonJS 产物的第三方包为例测试一下。

npm i lodash

然后,在src/index.js 加入如下的代码。

import { merge } from "lodash";
console.log(merge);

然后,执行 npm run build命令,就可以发现产物已经正常生成了,如下图所示。

image.png

在Rollup配置文件中,plugins除了可以与output配置在同一级,也可以配置在output参数里面。

// rollup.config.js
import { terser } from 'rollup-plugin-terser'
import resolve from "@rollup/plugin-node-resolve";
import commonjs from "@rollup/plugin-commonjs";


export default {
  output: {
    // 加入 terser 插件,用来压缩代码
    plugins: [terser()]
  },
  plugins: [resolve(), commonjs()]
}

需要注意的是,output.plugins中配置的插件是有一定限制的,只有使用Output 阶段相关钩子的插件才能够放到这个配置中,大家可以去这个站点查看 Rollup 的 Output 插件列表。这里也给大家分享其它一些比较常用的 Rollup 插件库:

  • @rollup/plugin-json: 支持.json的加载,并配合rollup的Tree Shaking机制去掉未使用的部分,进行按需打包。
  • @rollup/plugin-babel:在 Rollup 中使用 Babel 进行 JS 代码的语法转译。
  • @rollup/plugin-typescript: 支持使用 TypeScript 开发。
  • @rollup/plugin-alias:支持别名配置。
  • @rollup/plugin-replace:在 Rollup 进行变量字符串的替换。
  • rollup-plugin-visualizer: 对 Rollup 打包产物进行分析,自动生成产物体积可视化分析图。

三、JavaScript API

我们通过Rollup的配置文件结合rollup -c完成了 Rollup 的打包过程,但有些场景下我们需要基于 Rollup 定制一些打包过程,配置文件就不够灵活了,这时候我们需要用到对应 JavaScript API 来调用 Rollup,主要分为rollup.rollup和rollup.watch两个 API,接下来我们以具体的例子来学习一下。

首先,是 rollup.rollup,用来一次性地进行 Rollup 打包,可以新建一个build.js文件,内容如下。

// build.js
const rollup = require("rollup");


// 常用 inputOptions 配置
const inputOptions = {
  input: "./src/index.js",
  external: [],
  plugins:[]
};


const outputOptionsList = [
  // 常用 outputOptions 配置
  {
    dir: 'dist/es',
    entryFileNames: `[name].[hash].js`,
    chunkFileNames: 'chunk-[hash].js',
    assetFileNames: 'assets/[name]-[hash][extname]',
    format: 'es',
    sourcemap: true,
    globals: {
      lodash: '_'
    }
  }
  // 省略其它的输出配置
];


async function build() {
  let bundle;
  let buildFailed = false;
  try {
    // 1. 调用 rollup.rollup 生成 bundle 对象
    bundle = await rollup.rollup(inputOptions);
    for (const outputOptions of outputOptionsList) {
      // 2. 拿到 bundle 对象,根据每一份输出配置,调用 generate 和 write 方法分别生成和写入产物
      const { output } = await bundle.generate(outputOptions);
      await bundle.write(outputOptions);
    }
  } catch (error) {
    buildFailed = true;
    console.error(error);
  }
  if (bundle) {
    // 最后调用 bundle.close 方法结束打包
    await bundle.close();
  }
  process.exit(buildFailed ? 1 : 0);
}


build();

让我们来解释一下上面的代码:

  • 通过 rollup.rollup方法,传入 inputOptions,生成 bundle 对象;
  • 调用 bundle 对象的 generate 和 write 方法,传入outputOptions,分别完成产物和生成和磁盘写入。
  • 调用 bundle 对象的 close 方法来结束打包。

接着,执行node build.js命令。这样,我们就可以完成了以编程的方式来调用 Rollup 打包的过程。除了通过rollup.rollup完成一次性打包,我们也可以通过rollup.watch来完成watch模式下的打包,即每次源文件变动后自动进行重新打包。你可以新建watch.js文件,配置如下。

// watch.js
const rollup = require("rollup");


const watcher = rollup.watch({
  // 和 rollup 配置文件中的属性基本一致,只不过多了`watch`配置
  input: "./src/index.js",
  output: [
    {
      dir: "dist/es",
      format: "esm",
    },
    {
      dir: "dist/cjs",
      format: "cjs",
    },
  ],
  watch: {
    exclude: ["node_modules/**"],
    include: ["src/**"],
  },
});


// 监听 watch 各种事件
watcher.on("restart", () => {
  console.log("重新构建...");
});


watcher.on("change", (id) => {
  console.log("发生变动的模块id: ", id);
});


watcher.on("event", (e) => {
  if (e.code === "BUNDLE_END") {
    console.log("打包信息:", e);
  }
});

现在,我们可以通过执行node watch.js开启 Rollup 的 watch 打包模式,当你改动一个文件后可以看到如下的日志,说明 Rollup 自动进行了重新打包,并触发相应的事件回调函数。

发生生变动的模块id: /xxx/src/index.js
重新构建...
打包信息: {
  code: 'BUNDLE_END',
  duration: 10,
  input: './src/index.js',
  output: [
    // 输出产物路径
  ],
  result: {
    cache: { /* 产物具体信息 */ },
    close: [AsyncFunction: close],
    closed: false,
    generate: [AsyncFunction: generate],
    watchFiles: [
      // 监听文件列表
    ],
    write: [AsyncFunction: write]
  }
}

基于如上的两个 JavaScript API 我们可以很方便地在代码中调用 Rollup 的打包流程,相比于配置文件有了更多的操作空间,你可以在代码中通过这些 API 对 Rollup 打包过程进行定制,甚至是二次开发。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/552011.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

cesium entity与时间轴关联(添加运动轨迹)

1、先添加一个entity实体 用model加载一个飞机实体 let entity viewer.entities.add({position: Cesium.Cartesian3.fromDegrees(120, 21),model: {uri: "/static/Cesium_Air.glb",minimumPixelSize: 128,maximumScale: 20000,},}) viewer.trackedEntity entity;…

LIN-报文结构

文章目录 协议规范一、字节场二、报文头(HEADER FIELDS)同步间隔(synchronisation break)同步场(SYNCH FIELD)标识符场(IDENTIFIER FIELD) 三、数据场(DATE FIELDS)四、校…

【负载均衡式在线OJ】 数据库

文章目录 41.使用Postman进行综合调试42.了解-前端预备52. 添加oj用户到MySQL53. 使用MySQL_Workbench创建表结构54. 测试录题功能55.重新设计oj_model56.编写oj_model具体代码57.MySQL综合测试58.结项与项目扩展思路 41.使用Postman进行综合调试 完善判题功能 先编译再测试 …

SpingBoot简介—helloworld

目录 一、使用idea创建第一个SpringBoot项目 二、Hello World 三、整体项目结构 一、使用idea创建第一个SpringBoot项目 在进行这一步时,可能会遇到问题,如下图所示: 在这里解决方法有以下四种: (1)方法1&…

国内AI大模型 VS chatgpt (20)-- H265/H264有何不同,h265有什么优势?

H265/H264有何不同,h265有什么优势? 文心一言: H.264和H.265是两种不同的视频编码标准,它们在压缩质量和带宽需求方面有所不同。 H.264是一种高效的视频压缩标准,它的编码单位是宏块(macroblock)&#…

XML配置方式整合SSM框架实现用户登录

文章目录 一,创建数据库与表(一)创建数据库(二)创建用户表(三)在用户表里插入记录 二,创建Maven项目三,项目添加相关依赖四,创建日志属性文件五,创…

Aspose.Tasks for Java 23crack

Aspose.Tasks for Java 23crack 新属性允许您控制“分配成本”和“剩余成本”的自动计算。 Aspose.Tasks for Java是一个项目管理API,可帮助您操作Microsoft project文档。开发人员可以定义项目的主要属性和默认属性以及日历信息。他们还可以指定工作日和日历例外情…

【5.21】六、自动化测试—持续集成测试

目录 6.4 持续集成测试 6.4.1 持续集成的概念 6.4.2 持续集成测试框架设计 6.4 持续集成测试 持续集成(Continuous Integration,CI)是软件开发DevOps(DevelopmentOperations)中的一个概念,它强调的是软…

【第十一届泰迪杯数据挖掘挑战赛】A 题:新冠疫情防控数据的分析 思路+代码(持续更新)

【第十一届泰迪杯数据挖掘挑战赛】A 题:新冠疫情防控数据的分析 思路代码(持续更新) 问题背景解决问题代码下载数据分析Task1Task2Task3Task4 问题背景 自 2019 年底至今,全国各地陆续出现不同程度的新冠病毒感染疫情,…

目标检测复盘 --4. Faster RCNN

Fast RCNN的性能得到了很大的提升,但是还是有很大一部分开销在候选框的生成模块,也就是SS算法,Faster RCNN使用一个网络专门干这个事,从而加快整体检测速度,能达到5帧每秒。所以这里的FasterRCNN也就是RPNFastRCNN RPN…

opencv_c++学习(十六)

一、线性滤波 均值滤波: blur(InputArray src, utputArray dst,Size ksize, Point anchor Point(-i,-1), int borderType BoRDER_DEFAULT)src:待均值滤波的图像,图像的数据类型必须是CV_8U、CV_16U、CV_16S、CV_32F和CV_64F这五种数据类型之一。 ds…

详解RGB和XYZ色彩空间转换之上

前言 首先需要指明本文中描述的R,G,B并非通常的sRGB中的三个分量R,G,B,而是波长分别为700nm,546.1nm,435.8nm的单色红光,单色绿光,单色蓝光。sRGB中的RGB中的红色、绿色、蓝色已经不是单色光了。虽然习惯上大家都叫RGB…

Java配置类整合SSM框架实现用户登录

文章目录 一,创建数据库与表(一)创建数据库(二)创建用户表(三)在用户表里插入记录 二,创建Maven项目三,添加相关依赖四,创建日志属性文件五,创建数…

自动化测试与手工测试的区别是什么?

目录 什么是自动化测试? 自动化测试与手工测试的区别 自动化测试的困境 什么是自动化测试? 自动化测试是指利用软件测试工具自动实现全部或部分测试,它是软件测试的一个重要组成 部分,能完成许多手工测试无法实现或难以实现的测试。能够正确、合理地…

DAY 62 mysql的高级语句

表连接查询 MYSQL数据库中的三种连接: inner join(内连接):只返回两个表中联结字段相等的行(有交集的值)left join(左连接):返回包括左表中的所有记录和右表中联结字段相等的记录right join(右连接):返回…

【P28】JMeter 测试活动(Flow Control Action)

文章目录 一、测试活动(Flow Control Action)参数说明二、测试计划设计2.1、Pause 2.2、Break Current Loop2.3、Stop 一、测试活动(Flow Control Action)参数说明 控制取样器流程 选择线程组右键 >>> 添加 >>&g…

chatgpt赋能Python-python8_3

Python 8%3 - 了解Python中的求余运算 Python是一种通用编程语言,具有广泛的应用领域,如Web开发、数据分析、机器学习等。在Python中,求余运算是一种重要且常用的运算。本文将介绍Python中的求余运算,并且重点讲解Python中的8%3的…

【二叉树】(一)

🖊作者 : D. Star. 📘专栏 : 数据结构 😆今日分享 : 为什么阴干的衣服会有味道? 衣服上超过三分之二的污垢来自我们身体外部的分泌物,即汗水和皮脂。如果在洗衣服的过程中皮脂没有洗干净的话,它就可以成为衣服&#x…

计算机基础数据结构和算法动态可视化展示网站收录

计算机中有很多数据结构、算法对于小白来说非常难理解,交互式动画一步步展示整个过程可以帮助我们快速准确地理解这些算法。我们整理了12个可动态交互和展示常见数据结构和排序、图算法等网站,总计12个 收录到 webhub123​www.webhub123.com/#/home/de…

C 学习笔记 —— 内存操作函数

文章目录 内存操作函数memcpy实例 memmove内存重叠问题 memcmp实例 memset不能任意赋值 参考资料 内存操作函数 内存操作函数也是在<string.h>头文件中的 四个内存函数&#xff1a; memcpy(内存拷贝&#xff09; memmove&#xff08;内存移动&#xff09; memcmp&#…