217e18fa31793eaee344c30ae744d19e_r.png

插件化系统的原理:面向接口编程 遵守开放封闭原则(OCP),系统对扩展开放,对修改封闭

一个插件系统的核心是制定一套消息通信机制,同时将系统运行时的上下文进行封装,按照不同的场景需求暴露给插件。通过消息通信机制将系统和插件隔离,保证插件不会侵入原系统,通过暴露封装后的上下文内容,安全可靠的将系统资源提供给插件调用

使用插件的优点:

  1. 允许第三方开发者扩展应用功能(webpack的大量插件)
  2. 拔插式的设计方便开发者添加、删减插件
  3. 通过插件设计,大大减小原系统的代码体积

    实现一个插件系统

    jojo.png

  4. 控制插件的加载

  5. 对插件暴露合适范围的上下文,并对不同场景的上下文做隔离
  6. 有一套可拔插的消息通信机制,订阅&监听

    Hooks 声明

  • Hooks 相当于插件的管理中心,每一个生命周期钩子基本对应框架的不同场景

    通过不同的 Hook 来对应框架不同的事件、生命周期节点,以便在需要时调用相应的 Hook

  1. // hooks.js,通过这个hook建立一个hash map,相当于一个插件注册中心。每个key代表一个类型的钩子
  2. class Hooks {
  3. constructor() {
  4. this.hooks = new Map();
  5. }
  6. add(name, fn) {
  7. const hooks = this.get(name);
  8. hooks.add(fn);
  9. this.hooks.set(name, hooks);
  10. }
  11. get(name) {
  12. return this.hooks.get(name) || new Set();
  13. }
  14. invoke(name, ...args) {
  15. for (const hook of this.get(name)) {
  16. hook(...args);
  17. }
  18. }
  19. async invokePromise(name, ...args) {
  20. for (const hook of this.get(name)) {
  21. await hook(...args);
  22. }
  23. }
  24. }
  25. module.exports = new Hooks();

Hook 的调用、注册

针对不同场景的具体插件,会注册特定的钩子(大于等于一个钩子)。插件组合使用不同特性的钩子,往其中插入具体业务代码。框架加载插件后,即会自动注册钩子。当运行到指定钩子的时候,相应的也会执行具体插件钩入的业务代码

  1. // index.js
  2. #!/usr/bin/env node
  3. const fs = require('fs');
  4. const hookBus = require('./hooks');
  5. function onCreate() {
  6. console.log('onCreate');
  7. hookBus.invoke('onCreate',{a: 1,b: 2}); // 这里增加了主生命周期钩子的注册,可以将主流程中的上下文变量传过去
  8. }
  9. async function onStart() {
  10. console.log('onStart');
  11. await hookBus.invokePromise('onStart', {a: 3, b: 4}); // 这里是一个主生命周期异步钩子的注册
  12. }
  13. // 这个方法传给plugin,提供给插件来调用钩子
  14. function hook(name, fn) {
  15. hookBus.add(name, fn);
  16. }
  17. function loadPlugin() {
  18. fs.readdirSync(__dirname)
  19. .filter(item => /^plugin/.test(item))
  20. .forEach(file =>
  21. require(require.resolve(`${__dirname}/${file}`)).apply(hook) // 这里统一向钩子暴露了apply方法,作为插件主入口
  22. );
  23. }
  24. function main() {
  25. loadPlugin();
  26. onCreate();
  27. onStart();
  28. }
  29. main();
  1. // plugin-1.js
  2. console.log('plugin-1 loaded');
  3. function apply(hook) {
  4. hook('onCreate', function(ctx) {
  5. console.log('plugin-1 onCreate');
  6. console.log(ctx);
  7. });
  8. hook('onStart', function(ctx) {
  9. console.log('plugin-1 onStart');
  10. console.log(ctx);
  11. });
  12. }
  13. module.exports = {
  14. apply
  15. };
  16. // plugin-2.js
  17. console.log('plugin-2 loaded');
  18. function apply(hook) {
  19. hook('onCreate', function(ctx) {
  20. console.log('plugin-2 onCreate');
  21. console.log(ctx);
  22. });
  23. }
  24. module.exports = {
  25. apply
  26. };

资料

  1. 插件系统的设计
  2. javascript plugin architecture
  3. 谈谈如何设计一个插件(Plugin)体系
  4. 前端插件系统设计
  5. 如何编写神奇的「插件机制」,优化基于 Antd Table 封装表格的混乱代码