聊聊unplugin
先上一个 github 地址 https://github.com/unjs/unplugin
前言
前端工具链生态是日新月异,很多人 webpack 都还没玩熟悉呢,新的 vite 都要出到 3.0 了,再说 vite 是基于 rollup 和 esbuild 的吧,这些个工具都各有优劣,都是要学习的东西。但在我看来,这些工具都有一个共同的特点:都是打包工具。打包工具需要做的事情就很简单,接收输入的文件,输出成我们想要的东西,这其中还包含了可以通过不同的插件实现对输入文件的处理,以实现混淆、注入等功能;同时打包工具大多都提供了许多钩子(hook),贯穿整个打包流程,也方便了我们对打包过程的关注和额外处理。百变不离其宗,webpack 有 loader 和 plugin,vite 扩展了设计出色的 Rollup 接口,还带了一些 vite 独特的配置项,因此有人就有了写一次代码,适配多个 bundler 的想法,unplugin 就是这样一个存在。
unplugin
由于 contributors 大多是 vite 团队出身,因此 unplugin 也是继承了 rollup 的 plugin api作为基础,通过在它提供的 hook 上实现具体的代码逻辑,unplugin 会帮你做一层转换以适配各个 bundler,本质上我理解还是一个适配器模式的思想。
来个官方的 README 中的表格,具体支持的 hook 如下。
| Hook | Rollup | Vite | Webpack 4 | Webpack 5 | esbuild |
|---|---|---|---|---|---|
buildStart | ✅ | ✅ | ✅ | ✅ | ✅ |
buildEnd | ✅ | ✅ | ✅ | ✅ | ✅ |
transformInclude1 | ✅ | ✅ | ✅ | ✅ | ✅ |
transform | ✅ | ✅ | ✅ | ✅ | ✅ 3 |
enforce | ❌ 2 | ✅ | ✅ | ✅ | ❌ 2 |
resolveId | ✅ | ✅ | ✅ | ✅ | ✅ |
load | ✅ | ✅ | ✅ | ✅ | ✅ 3 |
watchChange | ✅ | ✅ | ✅ | ✅ | ✅ |
基于这么一个框架,方便了许多人去写 bundler 的二次开发,有许多新的插件基于 unplugin 实现,我觉得这是一个十分方便的东西,并且也没有特别复杂,所以可以来看看它的具体实现。
具体实现
unplugin 提供了一个 createUnplugin 的工厂函数,这个工厂函数接收一个入参为 options 的方法:
export function createUnplugin<UserOptions = {}>(
factory: UnpluginFactory<UserOptions>
): UnpluginInstance<UserOptions> {
return {
get esbuild() {
return getEsbuildPlugin(factory)
},
get rollup() {
return getRollupPlugin(factory)
},
get vite() {
return getVitePlugin(factory)
},
get webpack() {
return getWebpackPlugin(factory)
},
get raw() {
return factory
},
}
}我们以熟悉的 vite 为例,定位到 getVitePlugin 函数内,发现这个函数实现的功能很简单,就只是将 factory 函数返回的东西传给 toRollupPlugin,而 toRollupPlugin 也仅仅是将 transform 这个 hook 调用了一遍,同时合并了一些配置,这其实也很容易理解,因为本质上 vite 是对 rollup 的上层封装,而正好 unplugin 也是基于 rollup 的 api 往外提供公共的 hook,因此这其中不用做什么转换,直接调用就好了。
那这样说的话,感觉 webpack 处理会比较复杂,可以接下来看看 webpack 的处理方案。
