第二章:简单插件系统实现
在这一章中,我们将从零开始实现一个最基础的插件系统。通过实际编码,你将深入理解插件系统的核心机制。
设计目标
我们要实现的插件系统应该具备以下基本功能:
- 插件注册和管理
- 插件生命周期控制
- 简单的事件钩子系统
- 插件间的基本通信
核心架构设计
1. 插件接口定义
首先定义插件的基本接口:
javascript
// plugin-interface.js
class Plugin {
constructor(name, version = '1.0.0') {
this.name = name;
this.version = version;
this.enabled = false;
}
// 插件初始化方法,子类必须实现
init(context) {
throw new Error('Plugin must implement init method');
}
// 插件启用方法
enable() {
this.enabled = true;
this.onEnable();
}
// 插件禁用方法
disable() {
this.enabled = false;
this.onDisable();
}
// 生命周期钩子 - 启用时调用
onEnable() {
// 子类可以重写此方法
}
// 生命周期钩子 - 禁用时调用
onDisable() {
// 子类可以重写此方法
}
// 插件销毁方法
destroy() {
this.disable();
this.onDestroy();
}
// 生命周期钩子 - 销毁时调用
onDestroy() {
// 子类可以重写此方法
}
}
module.exports = Plugin;
2. 事件系统实现
实现一个简单的事件系统来支持插件间通信:
javascript
// event-emitter.js
class EventEmitter {
constructor() {
this.events = new Map();
}
// 注册事件监听器
on(event, listener) {
if (!this.events.has(event)) {
this.events.set(event, []);
}
this.events.get(event).push(listener);
// 返回取消监听的函数
return () => this.off(event, listener);
}
// 注册一次性事件监听器
once(event, listener) {
const onceWrapper = (...args) => {
listener(...args);
this.off(event, onceWrapper);
};
return this.on(event, onceWrapper);
}
// 移除事件监听器
off(event, listener) {
if (!this.events.has(event)) return;
const listeners = this.events.get(event);
const index = listeners.indexOf(listener);
if (index > -1) {
listeners.splice(index, 1);
}
// 如果没有监听器了,删除事件
if (listeners.length === 0) {
this.events.delete(event);
}
}
// 触发事件
emit(event, ...args) {
if (!this.events.has(event)) return false;
const listeners = this.events.get(event).slice(); // 复制数组避免修改问题
listeners.forEach(listener => {
try {
listener(...args);
} catch (error) {
console.error(`Error in event listener for ${event}:`, error);
}
});
return true;
}
// 获取事件的监听器数量
listenerCount(event) {
return this.events.has(event) ? this.events.get(event).length : 0;
}
// 获取所有事件名
eventNames() {
return Array.from(this.events.keys());
}
}
module.exports = EventEmitter;
3. 插件管理器实现
核心的插件管理器,负责插件的注册、加载和管理:
javascript
// plugin-manager.js
const EventEmitter = require('./event-emitter');
class PluginManager extends EventEmitter {
constructor() {
super();
this.plugins = new Map();
this.loadOrder = [];
this.context = this.createContext();
}
// 创建插件上下文
createContext() {
return {
// 事件系统
events: this,
// 日志方法
log: (...args) => console.log('[Plugin System]', ...args),
warn: (...args) => console.warn('[Plugin System]', ...args),
error: (...args) => console.error('[Plugin System]', ...args),
// 获取其他插件的引用
getPlugin: (name) => this.getPlugin(name),
// 获取所有插件
getAllPlugins: () => this.getAllPlugins()
};
}
// 注册插件
register(plugin) {
if (!plugin || !plugin.name) {
throw new Error('Plugin must have a name');
}
if (this.plugins.has(plugin.name)) {
throw new Error(`Plugin ${plugin.name} is already registered`);
}
// 存储插件
this.plugins.set(plugin.name, plugin);
this.loadOrder.push(plugin.name);
// 触发插件注册事件
this.emit('plugin:registered', plugin);
this.context.log(`Plugin ${plugin.name} registered`);
return this;
}
// 初始化插件
async initPlugin(name) {
const plugin = this.plugins.get(name);
if (!plugin) {
throw new Error(`Plugin ${name} not found`);
}
if (plugin.initialized) {
this.context.warn(`Plugin ${name} is already initialized`);
return;
}
try {
// 触发初始化前事件
this.emit('plugin:before-init', plugin);
// 调用插件的初始化方法
await plugin.init(this.context);
plugin.initialized = true;
// 触发初始化后事件
this.emit('plugin:after-init', plugin);
this.context.log(`Plugin ${name} initialized`);
} catch (error) {
this.context.error(`Failed to initialize plugin ${name}:`, error);
throw error;
}
}
// 启用插件
async enablePlugin(name) {
const plugin = this.plugins.get(name);
if (!plugin) {
throw new Error(`Plugin ${name} not found`);
}
if (!plugin.initialized) {
await this.initPlugin(name);
}
if (plugin.enabled) {
this.context.warn(`Plugin ${name} is already enabled`);
return;
}
try {
// 触发启用前事件
this.emit('plugin:before-enable', plugin);
plugin.enable();
// 触发启用后事件
this.emit('plugin:after-enable', plugin);
this.context.log(`Plugin ${name} enabled`);
} catch (error) {
this.context.error(`Failed to enable plugin ${name}:`, error);
throw error;
}
}
// 禁用插件
disablePlugin(name) {
const plugin = this.plugins.get(name);
if (!plugin) {
throw new Error(`Plugin ${name} not found`);
}
if (!plugin.enabled) {
this.context.warn(`Plugin ${name} is already disabled`);
return;
}
try {
// 触发禁用前事件
this.emit('plugin:before-disable', plugin);
plugin.disable();
// 触发禁用后事件
this.emit('plugin:after-disable', plugin);
this.context.log(`Plugin ${name} disabled`);
} catch (error) {
this.context.error(`Failed to disable plugin ${name}:`, error);
throw error;
}
}
// 卸载插件
unregister(name) {
const plugin = this.plugins.get(name);
if (!plugin) {
throw new Error(`Plugin ${name} not found`);
}
// 先禁用插件
if (plugin.enabled) {
this.disablePlugin(name);
}
try {
// 触发卸载前事件
this.emit('plugin:before-unregister', plugin);
// 销毁插件
plugin.destroy();
// 从管理器中移除
this.plugins.delete(name);
const index = this.loadOrder.indexOf(name);
if (index > -1) {
this.loadOrder.splice(index, 1);
}
// 触发卸载后事件
this.emit('plugin:after-unregister', plugin);
this.context.log(`Plugin ${name} unregistered`);
} catch (error) {
this.context.error(`Failed to unregister plugin ${name}:`, error);
throw error;
}
}
// 获取插件
getPlugin(name) {
return this.plugins.get(name);
}
// 获取所有插件
getAllPlugins() {
return Array.from(this.plugins.values());
}
// 获取已启用的插件
getEnabledPlugins() {
return this.getAllPlugins().filter(plugin => plugin.enabled);
}
// 初始化所有插件
async initAll() {
for (const name of this.loadOrder) {
await this.initPlugin(name);
}
}
// 启用所有插件
async enableAll() {
for (const name of this.loadOrder) {
await this.enablePlugin(name);
}
}
// 禁用所有插件
disableAll() {
// 按相反顺序禁用
for (let i = this.loadOrder.length - 1; i >= 0; i--) {
const name = this.loadOrder[i];
const plugin = this.plugins.get(name);
if (plugin && plugin.enabled) {
this.disablePlugin(name);
}
}
}
// 获取插件系统状态
getStatus() {
const plugins = this.getAllPlugins();
return {
total: plugins.length,
enabled: plugins.filter(p => p.enabled).length,
disabled: plugins.filter(p => !p.enabled).length,
plugins: plugins.map(p => ({
name: p.name,
version: p.version,
enabled: p.enabled,
initialized: p.initialized
}))
};
}
}
module.exports = PluginManager;
使用示例
让我们创建一些示例插件来测试我们的插件系统:
示例插件1:日志插件
javascript
// logger-plugin.js
const Plugin = require('./plugin-interface');
class LoggerPlugin extends Plugin {
constructor() {
super('logger', '1.0.0');
this.logs = [];
}
init(context) {
this.context = context;
// 监听所有插件事件并记录日志
context.events.on('plugin:registered', (plugin) => {
this.log(`Plugin registered: ${plugin.name}`);
});
context.events.on('plugin:after-enable', (plugin) => {
this.log(`Plugin enabled: ${plugin.name}`);
});
context.events.on('plugin:after-disable', (plugin) => {
this.log(`Plugin disabled: ${plugin.name}`);
});
}
onEnable() {
this.log('Logger plugin enabled');
}
onDisable() {
this.log('Logger plugin disabled');
}
log(message) {
const timestamp = new Date().toISOString();
const logEntry = `[${timestamp}] ${message}`;
this.logs.push(logEntry);
console.log(logEntry);
}
getLogs() {
return this.logs.slice(); // 返回副本
}
clearLogs() {
this.logs = [];
}
}
module.exports = LoggerPlugin;
示例插件2:计数器插件
javascript
// counter-plugin.js
const Plugin = require('./plugin-interface');
class CounterPlugin extends Plugin {
constructor() {
super('counter', '1.0.0');
this.count = 0;
}
init(context) {
this.context = context;
// 提供计数器API
context.events.on('counter:increment', () => {
this.increment();
});
context.events.on('counter:decrement', () => {
this.decrement();
});
context.events.on('counter:reset', () => {
this.reset();
});
}
onEnable() {
this.context.log('Counter plugin enabled, current count:', this.count);
}
increment() {
this.count++;
this.context.events.emit('counter:changed', this.count);
return this.count;
}
decrement() {
this.count--;
this.context.events.emit('counter:changed', this.count);
return this.count;
}
reset() {
const oldCount = this.count;
this.count = 0;
this.context.events.emit('counter:reset', oldCount);
this.context.events.emit('counter:changed', this.count);
return this.count;
}
getCount() {
return this.count;
}
}
module.exports = CounterPlugin;
完整使用示例
javascript
// example.js
const PluginManager = require('./plugin-manager');
const LoggerPlugin = require('./logger-plugin');
const CounterPlugin = require('./counter-plugin');
async function main() {
// 创建插件管理器
const pluginManager = new PluginManager();
// 创建插件实例
const loggerPlugin = new LoggerPlugin();
const counterPlugin = new CounterPlugin();
// 注册插件
pluginManager.register(loggerPlugin);
pluginManager.register(counterPlugin);
// 监听计数器变化事件
pluginManager.on('counter:changed', (count) => {
console.log(`Counter changed to: ${count}`);
});
// 初始化并启用所有插件
await pluginManager.initAll();
await pluginManager.enableAll();
// 测试插件功能
console.log('\n=== Testing Plugin System ===');
// 使用计数器插件
pluginManager.emit('counter:increment');
pluginManager.emit('counter:increment');
pluginManager.emit('counter:decrement');
// 获取计数器值
const counter = pluginManager.getPlugin('counter');
console.log('Current count:', counter.getCount());
// 重置计数器
pluginManager.emit('counter:reset');
// 查看系统状态
console.log('\n=== Plugin System Status ===');
console.log(pluginManager.getStatus());
// 查看日志
console.log('\n=== Logger Plugin Logs ===');
const logger = pluginManager.getPlugin('logger');
logger.getLogs().forEach(log => console.log(log));
// 禁用插件
pluginManager.disablePlugin('counter');
// 最终状态
console.log('\n=== Final Status ===');
console.log(pluginManager.getStatus());
}
main().catch(console.error);
运行结果
当你运行上面的示例代码时,你会看到类似这样的输出:
[Plugin System] Plugin logger registered
[Plugin System] Plugin counter registered
[Plugin System] Plugin logger initialized
[Plugin System] Plugin counter initialized
[Plugin System] Plugin logger enabled
[Plugin System] Plugin counter enabled
=== Testing Plugin System ===
Counter changed to: 1
Counter changed to: 2
Counter changed to: 1
Current count: 1
Counter changed to: 0
小结
在这一章中,我们实现了一个基础但功能完整的插件系统,包括:
- 插件接口定义:标准化的插件基类
- 事件系统:支持插件间通信的事件机制
- 插件管理器:负责插件生命周期管理的核心组件
- 实际示例:展示了如何创建和使用插件
这个基础插件系统已经具备了:
- 插件注册和管理
- 生命周期控制(初始化、启用、禁用、销毁)
- 事件驱动的通信机制
- 错误处理和日志记录
在下一章中,我们将进一步扩展这个系统,实现更强大的钩子系统,支持同步和异步钩子,让插件系统更加灵活和强大。
练习题
- 尝试创建一个配置管理插件,可以存储和读取配置信息
- 实现插件的依赖关系管理,确保依赖插件先于被依赖插件加载
- 添加插件的热重载功能,支持在运行时更新插件
下一章预告:我们将实现类似 Webpack/Vite 的钩子系统,支持更复杂的插件交互模式。