Vite-架构篇(Rollup)

快速开始

初始化

1
2
3
4
# 初始化项目
pnpm init -y
# 安装 rollup
pnpm install rollup

根目录下新建 rollup 的配置文件 rollup.config.js

1
2
3
4
5
6
7
8
// rollup.config.js
export default {
input: 'src/main.js',
output: {
file: 'dist/bundle.js',
format: 'cjs'
}
};

新建 src/main.js src/util.js

1
2
3
4
5
6
7
//  main.js
import { add } from "./util";

console.log(add(1, 2));
// util.js
export const add = (a, b) => a + b;
export const multi = (a, b) => a * b;

package.json 增加 build (部分代码省略),这里如果是 windows 环境,建议先安装 rimraf,将 rm -rf 替换成 rimraf ,我们在 build 时需要先将 dist 目录删掉重新生成

1
2
3
4
5
{
"scripts": {
"build": "rm -rf dist && rollup -c"
}
}

安装 rimraf 命令

1
pnpm install rimraf --save-dev

现在执行 pnpm run build 就能看到在 dist 目录下生成 bundle.js

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

打包后的文件可以看到代码已经放到一起了,并且我们在 util 中声明的方法 multi 并没又出现在打包后的文件中,因为 rollup 自带 Tree-Shaking 功能。
Tree-Shaking 也可以翻译为”摇树“,把一些不需要的东西抖落下来,本质就是在打包时,将一些没有用到的代码(也称 Dead Code)消除掉。

配置文件

详细文档:选项大列表

多产物配置

通过 format 指定不同的输出格式,可以同时打包出不同格式的产物,这里的 format 支持 amd、cjs、es、iife、umd、system

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
export default {
input: ['src/main.js', 'src/util.js'],
output: [
{
dir: "dist/es",
format: "es",
},
{
dir: "dist/cjs",
format: "cjs",
},
{
dir: "dist/amd",
format: "amd",
},
]
};

多入口配置 input

类型:string | string [] | { [entryName: string]: string }

如果有多个入口就需要用 数组/对象 的形式来定义了,需要注意的是,使用多入口时,产物需要定义 dir 指定输出目录

1
2
3
4
5
6
7
8
// rollup.config.js
export default {
input: ['src/main.js', 'src/util.js'],
output: {
dir: "dist/es",
format: "es",
},
};

如果想要定制每个入口的配置,可以通过导出数组的方式,来定义多个配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const mainBuild = {
input: ['src/main.js', 'src/util.js'],
output: [
{
dir: "dist/cjs",
format: "cjs",
},
]
}

const utilBuild = {
input: ['src/util.js'],
output: [
{
dir: "dist/amd",
format: "amd",
},
]
}


export default [mainBuild, utilBuild];

插件

插件有两种定义方式,第一种定义在 output 中,定义在这里的插件需要使用了 Output 阶段的钩子,第二种就是定义在 output 同级。

1
2
3
4
5
6
7
8
9
10
11
const mainBuild = {
input: ['src/main.js', 'src/util.js'],
output: [
{
dir: "dist/cjs",
format: "cjs",
plugins: [terser()]
},
],
plugins: [resolve(), commonjs()]
}

我们可以通过引入插件的方式来解决一些问题,例如使用到第三方依赖使用的是 CommonJs 格式的产物,rollup 有把 ESM 打包成 CommonJs 的能力,但他却不能反向处理,这时候就需要引入插件来处理了。

JavaScript API

当我们需要扩展 Rollup 本身,或者自己定制打包过程,需要使用 JavaScript API , rollup 提供了两个 API rollup.rollup 和 rollup.watch

rollup.rollup

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
const rollup = require('rollup');
// 输出配置项
const outputOptions = [
{
dir: "dist/cjs",
format: "cjs",
// 省略更多配置。。。
},
{
// ...
},
]
async function build() {
const bundle = await rollup.rollup({
input: ['./src/main.js','./src/util.js'],
});
try {
for (let output of outputOptions) {
const result = await bundle.generate(output);
await bundle.write(output);
}
} catch {
// 忽略错误处理
}
await bundle.close();
}
build();

首先通过 rollup.rollup 存储各个模块之前的内容依赖关系,完成模块图构建和 tree-shaking,返回一个 bundle 对象
然后遍历输出配置项,分别执行 bundle.generate 输出产物
最后通过 bundle.write 把产物写进磁盘

rollup.watch

当检测到磁盘单个模块发生变化,进行重构 bundle,我们来新建一个 watcher.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// watcher.js
const rollup = require('rollup');
// watch 的配置项
const watchOptions = {
input: ['./src/main.js','./src/util.js'],
output: [
{
dir: "dist/cjs",
format: "cjs",
},
{
dir: "dist/amd",
format: "amd",
},
],
watch: {
exclude: ["node_modules/**"],
include: ["src/**"],
},
};
const watcher = rollup.watch(watchOptions);

watcher.on('event', event => {
console.log(`watch 到了 ${event.code}`)
});

// 停止监听
// watcher.close();

watch 的配置项和我们写的输入输出配置项相同,只是多了一个 watch 项
通过 rollup.watch 返回监听对象,通过 watcher.on 进行监听。

event.code:
START — 监听器正在启动(重启)
BUNDLE_START — 构建单个 bundle
BUNDLE_END — 完成 bundle 构建
END — 完成所有bundle构建
ERROR — 构建时遇到错误

插件机制

构建流程

在前文的 JavaScript API 的 rollup.rollup 自定义构建中,能大概看出 rollup 经历了 Input => Build => Output,另外在真正的打包是在 Output 阶段,也就是执行 generate / write 的时候

Hooks

Build Hook:在build阶段执行的 hook,主要进行了代码转换,AST解析,模块依赖解析
Output Generation Hook:进行代码打包

执行方式

  1. Async:异步钩子函数
  2. Sync:同步钩子函数
  3. Parallel:并行钩子函数,底层使用 Promise.all 实现,处理没有依赖关系的插件
  4. Sequential:串行钩子函数,处理有依赖关系的插件
  5. First:当多个插件实现了这个 Hook ,会依次执行,直到返回值不为 null/undefined

Vite-架构篇(Rollup)
https://l1ushun.github.io/2023/08/12/vite-04/
作者
liu shun
发布于
2023年8月12日