插件系统 其实见的并不少了,Chrome, Visual Studio Code等等都有自己的一套插件系统,这不是自己也在写一个博客系统嘛,想着实现插件系统会好玩点,本文主要是通过阅读各路大佬的文章,最终总结出来的一些东西
为什么要做一个插件系统?
作为一个系统开发者,开发系统考虑的事情有所局限性,其实现的「features」或者对于一些人来说并不够,他们需要更多的额外功能,但是由于整一个系统的项目结构较复杂,其他人来维护的成本相对来讲会较高。那这个时候就需要使用一个插件来实现那些额外的功能。
当然插件也可以将系统功能拆分为松耦合的子模块,分而治之。
设计插件&系统?
插件有什么
如 VSCode 和 Chrome 的插件,他们内部都自带一个核心配置引导文件:package.json 或 manifest.json
Chrome
比如在 Chrome 中的插件主要有两个配置字段:background, content_scripts
这个是 配置文件
在 Chrome 中 它提供了一个全局变量,上面有很多的生命周期钩子,比如说 runTime, onMount, onInstall
总结一下,background中提供的功能有:
- 提供一个暴露了插件API的运行环境
- 通过事件钩子异步的通知消息
- 在事件的回调中实现数据通信
所以,我们发现一个插件系统的核心是制定一套消息通信机制,同时将系统运行时的上下文进行封装,按照不同的场景需求暴露给插件。通过消息通信机制将系统和插件隔离,保证插件不会侵入原系统,通过暴露封装后的上下文内容,安全可靠的将系统资源提供给插件调用。
Zhihu.潜语.插件系统的设计 https://zhuanlan.zhihu.com/p/106183037
当一个软件系统有很多子功能模块的时候,插件系统设计中还需要做到权限区分&分模块的资源隔离,因此才有了 content_scripts 这个东西,但用 background 仅能做到运行时环境的权限管控,无法做到细粒度控制
| Type | Accessible API | DOM Access | JS Access |
|---|---|---|---|
| Background | 除 devtools | 不能直接访问 | 不可以 |
| Content_script | 只能访问 extension, runtime | 可以 | 不可以 |
| Popup | 除 devtools | 不能直接访问 | 不可以 |
| DevTools | devtools, extension, runtime 中的一部分 | 可以 | 可以 |
VSCode
VSCode的技术栈和我的技术栈十分的接近(其实也就只是同 TypeScript 啦)因为有nodejs的运行时环境,所以相对应的,提供了一套更加复杂的插件系统。
通过 Yeoman 初始化插件项目,你在package.json中可以得到以下内容:
main 定义了主入口
contributes声明了想要去拓展的vscode的功能(详细的contributes可以看 https://code.visualstudio.com/api/references/contribution-points )
activationEvents则是告诉vscode什么时候去运行这个插件(详细的activationEvents列表可以看 https://code.visualstudio.com/api/references/activation-events )
看看插件实现:
- package.json 的
contributes字段中声明要使用commands拓展一个名为 extension.sayHello 的命令插件 - 插件active的时候在vscode.commond上注册并push到订阅器subscriptions中
- package.json 的activationEvents中声明extension.sayHello 的 调用时机为 onCommand 看到这里,各位看官应该就会发现,vscode除了上面提到的消息通信机制和提供上下文外,还解耦了事件监听和插件加载两个环节,从而可以提供更好的插件运行机制。
最为简单的方法 实现一个插件系统
根据 Chrome 和 VSCode 的插件系统,所需要做到的插件系统就需要做到:
- 控制插件的加载
- 对插件暴露合适范围的上下文,并对不同场景的上下文做隔离
- 有一套可插拔的消息通信机制,订阅&监听
首先先在index.js模拟插件生命周期
之后可以在同级目录下 创建两个文件作为插件:plugin-*.js 里面就最简单地写个 console.log() 吧
之后 我们在 index.js 中要将插件注入
通过运行它,你就可以收到返回了
实现了插件的加载后,下面就需要实现最核心的部分了,主文件和插件的通信
在这个地方我们需要有一个插件注册中心(hash map)每个key代表一个类型的钩子,我们新建一个 hook.js 来实现
通过上面简单的几步,我们已经实现了一个简易的插件系统
总结
一个插件系统的核心有以下几点:
- 控制插件的加载
- 对插件暴露合适范围的上下文,并对不同场景的上下文做隔离
- 有一套可插拔的消息通信机制,订阅&监听
实现一个插件系统的步骤:
- 制定一套加载插件的机制和规则(配置 or 约定 or 注册 等等)
- 提供一个存放插件的仓库
- 统一插件入口,暴露上下文,通过回调等手段实现消息通信
Reference
Zhihu.潜语.插件系统的设计 https://zhuanlan.zhihu.com/p/106183037
Juejin.末日沙兔.设计一个node.js插件式系统 https://juejin.cn/post/7077202852694720525