第 57 期 - Taro 插件机制原理与源码解析
摘要
文章以 Taro 4.0.2 版本为例,深入解析了其插件机制,包括插件的合并、注册、解析、调用以及相关函数的实现,还提及了一些工具函数的作用,并通过示例展示了插件机制的运行流程。
1. 前言
文章开篇介绍了 Taro 的版本情况,截至 2024 - 07 - 17,taro 正式版是 3.6.34,4.0 版本已经发布。作者计划写一个 taro 源码揭秘系列,本文是其中关于揭开整个架构的插件系统秘密的部分。并且列出了这个系列计划涵盖的文章主题,包括 Taro 架构入口 CLI、项目初始化背后原理、编译打包实现、发布订阅机制实现等。同时提到学习本文可以学到如何合并预设插件集合和插件、插件如何注册和调用等内容。还指出关于克隆项目、环境准备、调试代码等内容可参考系列的第一篇文章。
2. new Kernal 构造函数
- 首先定义了
IKernelOptions接口,Kernel类继承自EventEmitter。 - 在
Kernel类的构造函数中,根据传入的options参数初始化了一系列属性,如debugger、appPath、optsPresets等。并且初始化了一些数据结构,如hooks、methods、commands、platforms等,还调用了一些初始化方法,如initHelper、initConfig、initPaths、initRunnerUtils。 - 以
init命令为例,展示了CLI调用new Kernal的过程,包括如何注入插件等操作。
3. initPresetsAndPlugins 初始化预设插件集合和插件
- 这个方法主要做了几件事:
- 合并预设插件集合和插件:通过
mergePlugins函数合并CLI、用户项目和全局插件中的预设插件集合和插件。 - 转换全局配置里的插件集和插件为对象:使用
convertPluginsToObject函数进行转换。 - 在非测试环境下,使用
@swc/register编译ts等转换成commonjs,方便require读取文件。 - 解析预设插件集合和解析插件:分别调用
resolvePresets和resolvePlugins函数。
- 合并预设插件集合和插件:通过
3.1 工具函数 mergePlugins、convertPluginsToObject
isNpmPkg函数用于判断一个名称是否为npm包名。getPluginPath函数用于获取插件路径,要求插件和预设配置必须为绝对路径或者包名。convertPluginsToObject函数将插件项数组转换为对象,遍历数组,根据不同类型的项设置对象的属性。mergePlugins函数先将源插件和目标插件转换为对象,再进行合并。
4. resolvePresets 解析预设插件集合
- 这个方法主要做了两件事:
- 解析
cli和项目配置的预设插件集合,调用resolvePresetsOrPlugins函数得到resolvedCliAndProjectPresets,然后逐个初始化这些预设插件。 - 解析全局的预设插件集合,调用
resolvePresetsOrPlugins函数得到resolvedGlobalPresets,然后逐个初始化这些预设插件。
- 解析
4.1 resolvePresetsOrPlugins 解析插件集或者插件
- 主要功能是获取插件路径并处理插件加载错误。使用
resolve.sync获取路径,如果找不到插件依赖,根据不同情况处理,如使用备份路径、跳过错误(全局插件引入报错时)或抛出错误(非全局插件引入报错时)。最后组成包含插件信息的数组对象返回。
5. initPreset 初始化预设插件集合
- 主要做了以下几件事:
- 初始化插件
ctx:调用initPluginCtx函数。 - 执行插件,获得预设插件集合和插件:调用
apply方法。 - 注册插件:调用
registerPlugin函数。 - 如果预设插件集合是数组则继续递归调用
initPreset。 - 如果插件是数组,是全局插件就合并到全局额外的插件
globalExtraPlugins中,否则就合并到额外的插件extraPlugins中。
- 初始化插件
6. initPluginCtx 初始化插件 ctx
- 主要操作如下:
- 生成插件的
pluginCtx:创建Plugin实例。 - 注册内部方法:将
onReady、onStart等内部方法注册到ctx。 - 绑定
this指向:将this.methods数组中的方法绑定this指向到Kernal实例对象上,对kernelApis中的方法进行代理绑定this指向到Kernal实例对象上。
- 生成插件的
6.1 new Plugin({ id, path, ctx })
- 定义了
Plugin类,包含id、path、ctx等属性,构造函数用于初始化这些属性。 - 还包含以下几个注册方法:
register注册hook:判断hook的name和fn类型后存入hooks。registerCommand注册方法:将命令存入commandsMap 中,并调用register方法存入hooks。registerPlatform注册平台:将平台存入platforms中,并调用register方法存入hooks。registerMethod注册方法:处理不同传参形式,将方法存入methods中。addPluginOptsSchema添加插件的参数Schema:设置optsSchema属性。
7. registerPlugin 注册插件
- 将插件注册到
pluginsMap 中,如果插件已被注册则抛出错误。
8. resolvePlugins 解析插件
- 操作步骤如下:
- 合并预设插件集合中的插件、
CLI和项目中配置的插件得到resolvedCliAndProjectPlugins。 - 合并全局预设插件集合中的插件、全局配置的插件得到
resolvedGlobalPlugins。 - 遍历所有解析后的插件,一次调用
this.initPlugin初始化插件,最后清空extraPlugins和globalExtraPlugins。
- 合并预设插件集合中的插件、
9. initPlugin 初始化插件
- 主要做了以下几件事:
- 初始化插件的
ctx:调用initPluginCtx函数。 - 注册插件:调用
registerPlugin函数。 - 执行插件:调用
apply方法,传入pluginCtx和opts。 - 校验插件的参数是否符合要求:调用
checkPluginOpts函数。
- 初始化插件的
10. checkPluginOpts 校验插件的参数
- 使用
joi校验插件参数schema,如果pluginCtx.optsSchema不是函数则直接返回,否则根据joi的schema验证opts,不符合要求则抛出错误。
11. applyCliCommandPlugin 暴露 taro cli 内部命令插件
- 主要操作如下:
- 查找存在的
CLI命令插件文件路径,将其转换为插件对象。 - 使用
helper.createSwcRegister处理插件。 - 解析插件,然后逐个初始化插件。
- 查找存在的
12. 总结
- 回顾了本文学习的内容,包括如何合并预设插件集合和插件、插件的注册和调用等。
- 以
command/init.ts插件为例,展示了从插件的注册到最终调用的整个流程,涉及到createSwcRegister编译、插件初始化、register方法存入hooks,最后通过applyPlugins方法串联hooks中的函数依次执行。作者还建议读者自己调试源码以发现更多细节。
扩展阅读
Made by 捣鼓键盘的小麦 / © 2025 Front Talk 版权所有
