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,能够看到如下的命令行信息。
接下来,咱们关上 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。能够发现,所有入口的不同格局产物曾经胜利输入。
如果不同入口对应的打包配置不一样,咱们也能够应用默认的配置来导出一个配置数组,如下所示。
// 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 时,打包我的项目会呈现这样的报错:
所以,咱们须要引入额定的插件去解决这个问题,咱们须要装置两个外围的插件包。
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 命令,就能够发现产物曾经失常生成了,如下图所示。
在 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 打包过程进行定制,甚至是二次开发。