rollup -c
简要流程
插件系统相关模块
- Graph: 全局唯一的图,包含入口以及各种依赖的相互关系,操作方法,缓存等。是 rollup 的核心
- PluginDriver: 插件驱动器,调用插件和提供插件环境上下文等
插件机制分析
概述
一个 Rollup 插件是由一个或多个属性、构建钩子函数、输出钩子函数组成的对象,插件还需要符合一些官方的约定。一个插件应该作为一个包来发布,这个包导出一个可以用插件特定的选项来调用的函数,并且该函数返回一个对象。
插件允许你自定义 Rollup 的行为,比如,打包之前转换代码或者在你的 node_modules
文件夹中查找第三方包。
官方插件维护在 rollup/plugins 仓库,社区精选插件维护在 rollup/awesome。如果你想给某个插件提建议,请提交一个 pr。
一个简单的例子
下面的插件可以在不访问文件系统的前提下拦截任何 virtual-module
的导入。例如,如果你想在浏览器中使用 Rollup,这是必要的。它甚至可以用来替换入口点,如例子中所示。
1 | // index.js |
约定
- 插件应该有一个清晰的名字,并且必须带上
rollup-plugin-
前缀。 - 在
package.json
中包含rollup-plugin
关键字。 - 插件应该被测试,我们推荐 mocha 或者 ava 这类开箱支持 promises 的库。
- 尽可能使用异步方法。
- 使用英文编写插件文档
- 如果合适的话,确保你的插件输出正确的 sourcemap
- 如果你的插件使用 ‘virtual modules’(比如帮助函数),给模块名加上
\0
前缀。这可以阻止其他插件执行它。
钩子函数
rollup 插件的核心是钩子函数,rollup 钩子函数分为两类:
构建钩子函数
为了与构建过程交互,你的插件对象需要包含一些构建钩子函数。构建钩子是构建的各个阶段调用的函数。构建钩子函数可以影响构建执行方式、提供构建的信息或者在构建完成后修改构建。rollup 中有不同的构建钩子函数:
async
:这类 hook 也可以返回一个解析为相同类型值的 promise;否则,hook 将被标记为sync
。first
:如果有多个插件实现了这个 hook,hook 将依次运行,直到钩子返回一个非null
或非undefined
的值。sequential
:如果有多个插件实现了这个 hook,所有的插件都将按照指定的插件顺序运行。如果一个 hook 是异步的,这种类型的后续 hook 将一直等待,直到当前 hook 被解析。parallel
:如果有多个插件实现了这个 hook,所有的插件都将按照指定的插件顺序运行。如果一个 hook 是异步的,这种类型的后续 hook 将并行运行,而不等待当前钩子。
构建钩子函数在构建阶段执行,它们被 rollup.rollup(inputOptions)
触发。它们主要关注在 Rollup 处理输入文件之前定位、提供和转换输入文件。构建阶段的第一个钩子是 options
,最后一个钩子总是 buildEnd
,除非有一个构建错误,在这种情况下 closeBundle
将在这之后被调用。
此外,在观察模式下,watchChange
钩子可以在任何时候被触发,以通知新的运行将在当前运行产生其输出后被触发。另外,当 watcher 关闭时,closeWatcher 钩子函数将被触发。
输出生成钩子函数
输出生成钩子函数可以提供关于生成的包的信息并在构建完成后立马执行。它们和构建钩子函数拥有一样的工作原理和相同的类型,但是不同的是它们分别被 ·bundle.generate(output)
或 bundle.write(outputOptions)
调用。只使用输出生成钩子的插件也可以通过输出选项传入,因为只对某些输出运行。
输出生成阶段的第一个钩子函数是 outputOptions,如果输出通过 bundle.generate(…) 成功生成则第一个钩子函数是 generateBundle,如果输出通过 bundle.write(...)
生成则最后一个钩子函数是 writeBundle
,另外如果输出生成阶段发生了错误的话,最后一个钩子函数则是 renderError。
另外,closeBundle 可以作为最后一个钩子被调用,但用户有责任手动调用 bundle.close()
来触发它。CLI 将始终确保这种情况发生。
钩子函数加载实现
PluginDriver
中有 9 个 hook 加载函数。主要是因为每种类别的 hook 都有同步和异步的版本。
1.hookFirst:
加载
first
类型的钩子函数,场景有resolveId
、resolveAssetUrl
等
1 | function hookFirst<H extends keyof PluginHooks, R = ReturnType<PluginHooks[H]>>( |
2.hookFirstSync:
hookFirst 的同步版本,使用场景有
resolveFileUrl
、resolveImportMeta
等
1 | function hookFirstSync<H extends keyof PluginHooks, R = ReturnType<PluginHooks[H]>>( |
3.hookSeq
加载
sequential
类型的钩子函数,和 hookFirst 的区别就是不能中断,使用场景有onwrite
、generateBundle
等
1 | async function hookSeq<H extends keyof PluginHooks>( |
4.hookSeqSync
hookSeq 同步版本,不需要构造 promise,而是直接使用
runHookSync
执行钩子函数。使用场景有closeWatcher
、watchChange
等。
1 | hookSeqSync<H extends SyncPluginHooks & SequentialPluginHooks>( |
5.hookReduceArg0
对 arg 第一项进行 reduce 操作。使用场景:
options
、renderChunk
等
1 | function hookReduceArg0<H extends keyof PluginHooks, V, R = ReturnType<PluginHooks[H]>>( |
6.hookReduceArg0Sync
hookReduceArg0
同步版本,使用场景 transform
、generateBundle
等
7.hookParallel
并行执行 hook,不会等待当前 hook 完成。使用场景
buildEnd
、buildStart
、moduleParsed
等。
1 | hookParallel<H extends AsyncPluginHooks & ParallelPluginHooks>( |
runHook
上面的钩子函数加载函数,内部都调用了执行钩子函数的方法 runHook
或 runHookSync
,我们以 runHook
为例分析一下源码:
1 | function runHook<T>( |
核心依赖
- yargs-parser:yargs 使用的强大的选项解析插件
- source-map-support:这个模块通过 V8 堆栈追踪 API 支持 堆栈 sourcemap 支持
总结
Rollup 的插件和其他大型框架大同小异,都是提供统一的接口并贯彻了约定优于配置的思想。9 种 hook 加载函数使 rollup 的插件开发非常灵活,同时也带来了学习成本。
和 webpack 相比,rollup 的插件系统自称一派且没有区分 plugin 和 loader。
Rollup 插件机制的核心是构建阶段和输出生成阶段的各种钩子函数。内部通过基于 Promise 实现异步 hook 的调度。
rollup 的源码全都糅杂在一个库中,阅读起来着实头大,模块、工具函数管理的看起来很随意。而且我们无法直接移植它的任何工具到我们的项目中,相比起来,webpack 的插件系统封装成了一个插件 tapable 就很利于我们学习和使用。