runtime-registry
v1.0.0
Published
A runtime capability registry for micro-frontend architectures
Maintainers
Readme
RuntimeRegistry
一个轻量级、类型安全的运行时能力注册中心。提供命名空间隔离的能力注册和发现,并带有事件通知系统。
✨ 核心特性
- 🎯 命名空间隔离:不同模块/插件/租户之间完全隔离,互不干扰
- 🚀 高性能:基于原生 Map 的 O(1) 时间复杂度操作
- 📦 类型安全:完整的 TypeScript 支持和泛型推导
- 🔔 事件系统:外部 EventEmitter 集成,实时监听所有变更
- 🪶 轻量级:最小依赖,简洁的 API 设计
- 🔍 可观察性:内置调试和审查工具
🎯 适用场景
RuntimeRegistry 特别适合需要以下特性的场景:
核心优势
✅ 隔离性 - 不同模块/插件/租户需要完全隔离
✅ 可观察性 - 需要监听所有变更(审计、日志、调试)
✅ 动态性 - 运行时动态注册/注销能力
✅ 轻量级 - 不想引入重量级的 IoC 容器或状态管理库
✅ 类型安全 - TypeScript 项目需要完整的类型推导
典型应用
- 🏗️ 微前端架构 - 不同应用间的能力共享和隔离
- 🔌 插件系统 - 插件注册、发现和管理
- 🎯 服务定位器 - 轻量级依赖注入和服务管理
- 🏢 多租户系统 - 租户级别的配置和资源隔离
- 🚦 功能开关 - 分环境、分用户组的特性管理
- 📦 模块化配置 - 分模块的配置管理和热更新
- 🎨 主题系统 - 多主题/皮肤的资源管理
- 🧪 测试隔离 - 测试用例间的独立上下文
- 💾 缓存管理 - 带命名空间和事件通知的内存缓存
Runtime 的含义
RuntimeRegistry 的 "Runtime" 强调在程序运行期间动态发现和管理能力。
- ✅ 绑定可以是静态的(初始化时注册)
- ✅ 也可以是动态的(运行时注册)
- ✅ 发现总是运行时的(动态查找)
与 ESM/CommonJS 的关系
它是JS模块化开发在运行时的能力补充
| 特性 | ESM/CommonJS | RuntimeRegistry | |-----|--------------|-----------------| | 层次 | 模块系统层 | 应用层 | | 时机 | 加载时 | 运行时 | | 解决 | 代码组织 | 实例管理 | | 粒度 | 文件级 | 实例级 | | 绑定 | 静态路径 | 动态键值 |
典型组合使用:
// 用 ESM 组织代码
import { UserService } from './services/user';
// 用 RuntimeRegistry 管理实例
const services = registry.namespace('services');
services.set('userService', new UserService());
// 跨模块/跨应用访问(无需 import)
const userService = services.get('userService');相比其他方案的优势
| 特性 | RuntimeRegistry | Redux | EventEmitter | 全局对象 | |-----|----------------|-------|--------------|---------| | 命名空间隔离 | ✅ | ❌ | ❌ | ❌ | | 类型安全 | ✅ | ✅ | ❌ | ❌ | | 事件通知 | ✅ | ✅ | ✅ | ❌ | | 性能 | ⚡ O(1) | 🐌 O(n) | ⚡ O(1) | ⚡ O(1) | | 包体积 | 🪶 极小 | 📦 较大 | 🪶 极小 | - | | 学习成本 | 📖 简单 | 📚 复杂 | 📖 简单 | 📖 简单 |
Installation
npm install runtime-registry eventemitter3Quick Start
import EventEmitter from 'eventemitter3';
import { RuntimeRegistry, REGISTRY_EVENTS } from 'runtime-registry';
// Initialize with an EventEmitter
const emitter = new EventEmitter();
const registry = new RuntimeRegistry({ emitter });
// Listen to events
emitter.on(REGISTRY_EVENTS.REGISTER, (payload) => {
console.log(`Registered: ${payload.namespace}/${payload.key}`);
});
// Create a namespace for your app
const app1 = registry.namespace('app1');
// Register capabilities
app1.set('userService', userService);
app1.set('authService', authService);
// Get capabilities
const service = app1.get('userService');
// Get all capabilities
const allServices = app1.get();
// { userService: {...}, authService: {...} }📚 核心概念
命名空间隔离
RuntimeRegistry 使用命名空间提供完全的隔离机制。每个命名空间拥有独立的键值存储空间。
const mainApp = registry.namespace('main-app');
const pluginA = registry.namespace('plugin-a');
const pluginB = registry.namespace('plugin-b');
// 相同的 key,不同的值 - 完全不冲突!
mainApp.set('userService', mainAppUserService);
pluginA.set('userService', pluginAUserService);
pluginB.set('userService', pluginBUserService);事件系统
RuntimeRegistry 集成外部 EventEmitter 实例(来自 eventemitter3 或 Node.js events),实时通知所有变更。
事件类型:
registry:register- 注册新能力registry:update- 更新已有能力registry:unregister- 删除能力registry:clear- 清空命名空间
import EventEmitter from 'eventemitter3';
import { RuntimeRegistry, REGISTRY_EVENTS } from 'runtime-registry';
const emitter = new EventEmitter();
const registry = new RuntimeRegistry({ emitter });
// Listen to all events
emitter.on(REGISTRY_EVENTS.REGISTER, (payload) => {
console.log('Register:', payload);
// { namespace, key, value, timestamp }
});
emitter.on(REGISTRY_EVENTS.UPDATE, (payload) => {
console.log('Update:', payload);
// { namespace, key, value, oldValue, timestamp }
});
emitter.on(REGISTRY_EVENTS.UNREGISTER, (payload) => {
console.log('Unregister:', payload);
// { namespace, key, oldValue, timestamp }
});
emitter.on(REGISTRY_EVENTS.CLEAR, (payload) => {
console.log('Clear:', payload);
// { namespace, count, timestamp }
});📖 API Reference
RuntimeRegistry
constructor(options: RuntimeRegistryOptions)
创建新的 RuntimeRegistry 实例。
const registry = new RuntimeRegistry({
emitter: new EventEmitter()
});namespace<T>(name: string): IScopedRegistry<T>
获取或创建命名空间的作用域注册表。重复调用返回相同实例。
const app1 = registry.namespace('app1');
const app1Again = registry.namespace('app1');
console.log(app1 === app1Again); // true使用 TypeScript 泛型:
interface MyServices {
userService: UserService;
authService: AuthService;
}
const app = registry.namespace<MyServices>('app');
app.set('userService', userService); // 类型检查!
const user = app.get('userService'); // 类型:UserService | undefinedinspect(): Record<string, Record<string, any>>
获取所有命名空间及其内容,用于调试。
const result = registry.inspect();
// {
// app1: { userService: {...}, authService: {...} },
// app2: { userService: {...} }
// }snapshot(): Map<string, Map<string, any>>
创建所有命名空间的快照。
const snapshot = registry.snapshot();
for (const [namespace, store] of snapshot) {
console.log(`${namespace}:`, Array.from(store.entries()));
}destroy(): void
销毁所有命名空间并清理,会为每个命名空间触发 clear 事件。
registry.destroy();ScopedRegistry
set(key: string, value: any): void
注册或更新能力。首次注册触发 register 事件,更新时触发 update 事件。
app.set('userService', userService); // 触发 'register'
app.set('userService', newUserService); // 触发 'update'get(key?: string): any
通过键获取能力,或省略键获取所有能力。
// 获取单个值
const service = app.get('userService');
// 获取所有值
const all = app.get();
// { userService: {...}, authService: {...} }has(key: string): boolean
检查能力是否存在。
if (app.has('userService')) {
// ...
}delete(key: string): boolean
删除能力。如果删除成功返回 true,键不存在返回 false。删除时触发 unregister 事件。
const deleted = app.delete('userService');clear(): void
清空此命名空间中的所有能力,触发 clear 事件并携带数量。
app.clear();keys(): string[]
获取此命名空间中的所有键。
const keys = app.keys();
// ['userService', 'authService']values(): any[]
获取此命名空间中的所有值。
const values = app.values();entries(): Array<[string, any]>
获取所有条目的键值对数组。
const entries = app.entries();
// [['userService', {...}], ['authService', {...}]]💡 使用示例
场景 1:插件系统
import EventEmitter from 'eventemitter3';
import { RuntimeRegistry, REGISTRY_EVENTS } from 'runtime-registry';
const emitter = new EventEmitter();
const pluginRegistry = new RuntimeRegistry({ emitter });
// 插件 A 注册
const markdownPlugin = pluginRegistry.namespace('plugin-markdown');
markdownPlugin.set('render', (text) => marked(text));
markdownPlugin.set('config', { supportGFM: true });
// 插件 B 注册
const highlightPlugin = pluginRegistry.namespace('plugin-highlight');
highlightPlugin.set('highlight', (code, lang) => hljs.highlight(code, { language: lang }));
// 主应用发现和使用插件
const markdown = pluginRegistry.namespace('plugin-markdown');
if (markdown.has('render')) {
const renderer = markdown.get('render');
const html = renderer('# Hello World');
}场景 2:服务定位器 / 依赖注入
// 服务层
const services = registry.namespace('services');
services.set('logger', loggerService);
services.set('http', httpClient);
services.set('database', dbConnection);
// 仓储层
const repositories = registry.namespace('repositories');
repositories.set('userRepo', new UserRepository());
repositories.set('orderRepo', new OrderRepository());
// 在任何地方获取依赖
function createUserController() {
const logger = services.get('logger');
const userRepo = repositories.get('userRepo');
return new UserController(logger, userRepo);
}场景 3:多租户配置管理
// 租户 A
const tenantA = registry.namespace('tenant-company-a');
tenantA.set('theme', { primaryColor: '#ff0000' });
tenantA.set('features', ['chat', 'video', 'files']);
tenantA.set('quota', { storage: '100GB', users: 50 });
// 租户 B
const tenantB = registry.namespace('tenant-company-b');
tenantB.set('theme', { primaryColor: '#0000ff' });
tenantB.set('features', ['chat', 'files']); // 不包括 video
tenantB.set('quota', { storage: '500GB', users: 200 });
// 根据租户获取配置
function getTenantConfig(tenantId) {
const tenant = registry.namespace(`tenant-${tenantId}`);
return tenant.get();
}场景 4:功能开关管理
// 开发环境
const devFlags = registry.namespace('flags-dev');
devFlags.set('newUI', true);
devFlags.set('experimentalAPI', true);
devFlags.set('debugMode', true);
// 生产环境
const prodFlags = registry.namespace('flags-prod');
prodFlags.set('newUI', false);
prodFlags.set('experimentalAPI', false);
// 监听功能开关变化
emitter.on(REGISTRY_EVENTS.UPDATE, (payload) => {
if (payload.namespace.startsWith('flags-')) {
console.log(`功能 ${payload.key} 从 ${payload.oldValue} 改为 ${payload.value}`);
reloadFeature(payload.key);
}
});场景 5:微前端架构
import EventEmitter from 'eventemitter3';
import { RuntimeRegistry, REGISTRY_EVENTS } from 'runtime-registry';
// Create a global registry
const emitter = new EventEmitter();
const globalRegistry = new RuntimeRegistry({ emitter });
// Main application registers shared services
const mainApp = globalRegistry.namespace('main-app');
mainApp.set('router', router);
mainApp.set('store', store);
mainApp.set('eventBus', eventBus);
// Module A registers its services
const moduleA = globalRegistry.namespace('module-a');
moduleA.set('userService', {
getUser: async (id) => { /* ... */ },
updateUser: async (id, data) => { /* ... */ },
});
// Module B can have its own userService without conflicts
const moduleB = globalRegistry.namespace('module-b');
moduleB.set('userService', {
getUserProfile: async (id) => { /* ... */ },
});
// Module C can discover and use services from other modules
const moduleC = globalRegistry.namespace('module-c');
// Get main app's router
const router = mainApp.get('router');
// Get Module A's user service
const userService = moduleA.get('userService');场景 6:测试隔离
describe('User Tests', () => {
let testContext;
beforeEach(() => {
const testId = `test-${Date.now()}`;
testContext = registry.namespace(testId);
// 为每个测试创建独立的 mock
testContext.set('mockDB', createMockDB());
testContext.set('mockAPI', createMockAPI());
testContext.set('testData', generateTestData());
});
afterEach(() => {
testContext.clear(); // 清理测试上下文
});
it('should create user', () => {
const mockDB = testContext.get('mockDB');
const testData = testContext.get('testData');
// 测试逻辑...
});
});场景 7:事件监听和调试
// Log all registry changes in development
if (process.env.NODE_ENV === 'development') {
emitter.on(REGISTRY_EVENTS.REGISTER, (payload) => {
console.log(`[Registry] ✅ Registered: ${payload.namespace}/${payload.key}`);
});
emitter.on(REGISTRY_EVENTS.UPDATE, (payload) => {
console.log(`[Registry] 🔄 Updated: ${payload.namespace}/${payload.key}`);
});
emitter.on(REGISTRY_EVENTS.UNREGISTER, (payload) => {
console.log(`[Registry] ❌ Unregistered: ${payload.namespace}/${payload.key}`);
});
}场景 8:Module Federation 集成
// In your Module Federation setup
import { RuntimeRegistry } from 'runtime-registry';
import EventEmitter from 'eventemitter3';
// Create global registry
const emitter = new EventEmitter();
window.__RUNTIME_REGISTRY__ = new RuntimeRegistry({ emitter });
// In each remote module
const registry = window.__RUNTIME_REGISTRY__;
const myModule = registry.namespace('my-module');
// Register module capabilities
myModule.set('init', () => {
console.log('Module initialized');
});
myModule.set('routes', [
{ path: '/users', component: UsersPage },
{ path: '/profile', component: ProfilePage },
]);
// In host application
const myModule = registry.namespace('my-module');
if (myModule.has('init')) {
myModule.get('init')();
}
const routes = myModule.get('routes');场景 9:类型安全的服务注册表
// Define your service types
interface AppServices {
userService: {
getUser(id: string): Promise<User>;
updateUser(id: string, data: Partial<User>): Promise<void>;
};
authService: {
login(credentials: Credentials): Promise<Token>;
logout(): void;
};
configService: {
get(key: string): any;
set(key: string, value: any): void;
};
}
// Create typed registry
const app = registry.namespace<AppServices>('app');
// TypeScript will enforce types
app.set('userService', {
getUser: async (id) => { /* ... */ },
updateUser: async (id, data) => { /* ... */ },
});
// Type-safe retrieval
const userService = app.get('userService');
// Type: AppServices['userService'] | undefined
if (userService) {
const user = await userService.getUser('123'); // Type-safe!
}场景 10:动态模块加载
// Track loaded modules
const loadedModules = new Set<string>();
emitter.on(REGISTRY_EVENTS.REGISTER, (payload) => {
if (payload.key === 'init') {
loadedModules.add(payload.namespace);
console.log(`Module ${payload.namespace} loaded`);
}
});
// Load modules dynamically
async function loadModule(name: string, url: string) {
const module = await import(url);
const moduleRegistry = registry.namespace(name);
// Module registers itself
moduleRegistry.set('init', module.init);
moduleRegistry.set('routes', module.routes);
moduleRegistry.set('components', module.components);
return moduleRegistry;
}
// Check if module is loaded
function isModuleLoaded(name: string): boolean {
return loadedModules.has(name);
}🎨 最佳实践
1. 命名空间命名规范
使用描述性的、层次化的命名:
// ✅ 推荐
registry.namespace('main-app');
registry.namespace('plugin-user-management');
registry.namespace('tenant-company-a');
registry.namespace('cache-users');
// ❌ 避免
registry.namespace('app1');
registry.namespace('m1');
registry.namespace('x');2. 服务注册模式
在模块初始化时注册所有服务:
// module-init.ts
export function initModule(registry: RuntimeRegistry) {
const moduleRegistry = registry.namespace('my-module');
// 一次性注册所有服务
moduleRegistry.set('userService', createUserService());
moduleRegistry.set('authService', createAuthService());
moduleRegistry.set('config', moduleConfig);
return moduleRegistry;
}3. 事件监听器清理
模块卸载时清理事件监听器:
const listeners: Array<() => void> = [];
// 添加监听器
const removeListener = emitter.on(REGISTRY_EVENTS.REGISTER, handler);
listeners.push(() => emitter.off(REGISTRY_EVENTS.REGISTER, handler));
// 卸载时清理
function cleanup() {
listeners.forEach(remove => remove());
}4. 错误处理
始终处理可能的 undefined 值:
const service = app.get('userService');
if (!service) {
throw new Error('UserService not registered');
}
// 或使用可选链
const user = await app.get('userService')?.getUser('123');5. 调试和审查
使用 inspect() 进行调试:
// 添加到浏览器控制台用于调试
if (typeof window !== 'undefined') {
window.__DEBUG_REGISTRY__ = () => {
console.table(registry.inspect());
};
}
// 在控制台执行:__DEBUG_REGISTRY__()⚡ 性能
- Set 操作: O(1)
- Get 操作: O(1)
- Has 操作: O(1)
- Delete 操作: O(1)
- Clear 操作: O(n),其中 n 是命名空间中的键数量
- Namespace 创建: O(1)
所有操作都使用原生 JavaScript Map,确保最优性能。
🔷 TypeScript 支持
RuntimeRegistry 使用 TypeScript 编写,提供完整的类型定义。使用泛型实现类型安全:
interface Services {
api: ApiClient;
auth: AuthService;
}
const app = registry.namespace<Services>('app');
// 类型检查生效!
app.set('api', apiClient);
app.set('auth', authService);
const api = app.get('api'); // 类型:ApiClient | undefined🤝 贡献
欢迎贡献代码!请随时提交 Pull Request。
📄 许可证
MIT
💬 支持
如有问题或疑问,请在 GitHub 仓库中提交 issue。
