refactor(editor): 统一配置管理、完善插件卸载和热更新同步 (#298)

主要变更:

1. 统一配置管理 (EditorConfig)
   - 新增 EditorConfig 集中管理路径、文件名、全局变量等配置
   - 添加 SDK 模块配置系统 (ISDKModuleConfig)
   - 重构 PluginSDKRegistry 使用配置而非硬编码

2. 完善插件卸载机制
   - 扩展 PluginRegisteredResources 追踪运行时资源
   - 实现完整的 deactivatePluginRuntime 清理流程
   - ComponentRegistry 添加 unregister/getRegisteredComponents 方法

3. 热更新同步机制
   - 新增 HotReloadCoordinator 协调热更新过程
   - 热更新期间暂停 ECS 循环避免竞态条件
   - 支持超时保护和失败恢复
This commit is contained in:
YHH
2025-12-09 09:06:29 +08:00
committed by GitHub
parent 40a38b8b88
commit 6c99b811ec
12 changed files with 1270 additions and 172 deletions

View File

@@ -0,0 +1,328 @@
/**
* Hot Reload Coordinator
* 热更新协调器
*
* Coordinates the hot reload process to ensure safe code updates
* without causing race conditions or inconsistent state.
*
* 协调热更新过程,确保代码更新安全,不会导致竞态条件或状态不一致。
*
* @example
* ```typescript
* const coordinator = new HotReloadCoordinator();
* await coordinator.performHotReload(async () => {
* // Recompile and reload user code
* await userCodeService.compile(options);
* await userCodeService.load(outputPath, target);
* });
* ```
*/
import { createLogger } from '@esengine/ecs-framework';
import type { HotReloadEvent } from './IUserCodeService';
const logger = createLogger('HotReloadCoordinator');
/**
* Hot reload phase enumeration
* 热更新阶段枚举
*/
export const enum EHotReloadPhase {
/** Idle state, no hot reload in progress | 空闲状态,没有热更新进行中 */
Idle = 'idle',
/** Preparing for hot reload, pausing systems | 准备热更新,暂停系统 */
Preparing = 'preparing',
/** Compiling user code | 编译用户代码 */
Compiling = 'compiling',
/** Loading new modules | 加载新模块 */
Loading = 'loading',
/** Updating component instances | 更新组件实例 */
UpdatingInstances = 'updating-instances',
/** Updating systems | 更新系统 */
UpdatingSystems = 'updating-systems',
/** Resuming systems | 恢复系统 */
Resuming = 'resuming',
/** Hot reload complete | 热更新完成 */
Complete = 'complete',
/** Hot reload failed | 热更新失败 */
Failed = 'failed'
}
/**
* Hot reload status interface
* 热更新状态接口
*/
export interface IHotReloadStatus {
/** Current phase | 当前阶段 */
phase: EHotReloadPhase;
/** Error message if failed | 失败时的错误信息 */
error?: string;
/** Timestamp when current phase started | 当前阶段开始时间戳 */
startTime: number;
/** Number of updated instances | 更新的实例数量 */
updatedInstances?: number;
/** Number of updated systems | 更新的系统数量 */
updatedSystems?: number;
}
/**
* Hot reload options
* 热更新选项
*/
export interface IHotReloadOptions {
/**
* Timeout for hot reload process in milliseconds.
* 热更新过程的超时时间(毫秒)。
*
* @default 30000
*/
timeout?: number;
/**
* Whether to restore previous state on failure.
* 失败时是否恢复到之前的状态。
*
* @default true
*/
restoreOnFailure?: boolean;
/**
* Callback for phase changes.
* 阶段变化回调。
*/
onPhaseChange?: (phase: EHotReloadPhase) => void;
}
/**
* Hot Reload Coordinator
* 热更新协调器
*
* Manages the hot reload process lifecycle:
* 1. Pause ECS update loop
* 2. Execute hot reload tasks (compile, load, update)
* 3. Resume ECS update loop
*
* 管理热更新过程生命周期:
* 1. 暂停 ECS 更新循环
* 2. 执行热更新任务(编译、加载、更新)
* 3. 恢复 ECS 更新循环
*/
export class HotReloadCoordinator {
private _status: IHotReloadStatus = {
phase: EHotReloadPhase.Idle,
startTime: 0
};
private _coreReference: any = null;
private _previousPausedState: boolean = false;
private _hotReloadPromise: Promise<void> | null = null;
private _onPhaseChange?: (phase: EHotReloadPhase) => void;
/**
* Get current hot reload status.
* 获取当前热更新状态。
*/
public get status(): Readonly<IHotReloadStatus> {
return { ...this._status };
}
/**
* Check if hot reload is in progress.
* 检查热更新是否进行中。
*/
public get isInProgress(): boolean {
return this._status.phase !== EHotReloadPhase.Idle &&
this._status.phase !== EHotReloadPhase.Complete &&
this._status.phase !== EHotReloadPhase.Failed;
}
/**
* Initialize coordinator with Core reference.
* 使用 Core 引用初始化协调器。
*
* @param coreModule - ECS Framework Core module | ECS 框架 Core 模块
*/
public initialize(coreModule: any): void {
this._coreReference = coreModule;
logger.info('HotReloadCoordinator initialized');
}
/**
* Perform a coordinated hot reload.
* 执行协调的热更新。
*
* This method ensures the ECS loop is paused during hot reload
* and properly resumed afterward, even if an error occurs.
*
* 此方法确保 ECS 循环在热更新期间暂停,并在之后正确恢复,即使发生错误。
*
* @param reloadTask - Async function that performs the actual reload | 执行实际重载的异步函数
* @param options - Hot reload options | 热更新选项
* @returns Promise that resolves when hot reload is complete | 热更新完成时解析的 Promise
*/
public async performHotReload(
reloadTask: () => Promise<HotReloadEvent | void>,
options: IHotReloadOptions = {}
): Promise<HotReloadEvent | void> {
// Prevent concurrent hot reloads | 防止并发热更新
if (this._hotReloadPromise) {
logger.warn('Hot reload already in progress, waiting for completion | 热更新已在进行中,等待完成');
await this._hotReloadPromise;
}
const {
timeout = 30000,
restoreOnFailure = true,
onPhaseChange
} = options;
this._onPhaseChange = onPhaseChange;
this._status = {
phase: EHotReloadPhase.Idle,
startTime: Date.now()
};
let result: HotReloadEvent | void = undefined;
this._hotReloadPromise = (async () => {
try {
// Phase 1: Prepare - Pause ECS | 阶段 1准备 - 暂停 ECS
this._setPhase(EHotReloadPhase.Preparing);
this._pauseECS();
// Create timeout promise | 创建超时 Promise
const timeoutPromise = new Promise<never>((_, reject) => {
setTimeout(() => {
reject(new Error(`Hot reload timed out after ${timeout}ms | 热更新超时 ${timeout}ms`));
}, timeout);
});
// Phase 2-5: Execute reload task with timeout | 阶段 2-5带超时执行重载任务
this._setPhase(EHotReloadPhase.Compiling);
result = await Promise.race([
reloadTask(),
timeoutPromise
]);
// Phase 6: Resume ECS | 阶段 6恢复 ECS
this._setPhase(EHotReloadPhase.Resuming);
this._resumeECS();
// Phase 7: Complete | 阶段 7完成
this._setPhase(EHotReloadPhase.Complete);
logger.info('Hot reload completed successfully | 热更新成功完成', {
duration: Date.now() - this._status.startTime
});
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
this._status.error = errorMessage;
this._setPhase(EHotReloadPhase.Failed);
logger.error('Hot reload failed | 热更新失败:', error);
// Always resume ECS on failure | 失败时始终恢复 ECS
if (restoreOnFailure) {
this._resumeECS();
}
throw error;
} finally {
this._hotReloadPromise = null;
}
})();
await this._hotReloadPromise;
return result;
}
/**
* Update hot reload status with instance count.
* 更新热更新状态的实例数量。
*
* @param instanceCount - Number of updated instances | 更新的实例数量
*/
public reportInstanceUpdate(instanceCount: number): void {
this._status.updatedInstances = instanceCount;
this._setPhase(EHotReloadPhase.UpdatingInstances);
}
/**
* Update hot reload status with system count.
* 更新热更新状态的系统数量。
*
* @param systemCount - Number of updated systems | 更新的系统数量
*/
public reportSystemUpdate(systemCount: number): void {
this._status.updatedSystems = systemCount;
this._setPhase(EHotReloadPhase.UpdatingSystems);
}
/**
* Pause ECS update loop.
* 暂停 ECS 更新循环。
*/
private _pauseECS(): void {
if (!this._coreReference) {
logger.warn('Core reference not set, cannot pause ECS | Core 引用未设置,无法暂停 ECS');
return;
}
// Store previous paused state to restore later | 存储之前的暂停状态以便后续恢复
this._previousPausedState = this._coreReference.paused ?? false;
// Pause ECS | 暂停 ECS
this._coreReference.paused = true;
logger.debug('ECS paused for hot reload | ECS 已暂停以进行热更新');
}
/**
* Resume ECS update loop.
* 恢复 ECS 更新循环。
*/
private _resumeECS(): void {
if (!this._coreReference) {
logger.warn('Core reference not set, cannot resume ECS | Core 引用未设置,无法恢复 ECS');
return;
}
// Restore previous paused state | 恢复之前的暂停状态
this._coreReference.paused = this._previousPausedState;
logger.debug('ECS resumed after hot reload | 热更新后 ECS 已恢复', {
paused: this._coreReference.paused
});
}
/**
* Set current phase and notify listener.
* 设置当前阶段并通知监听器。
*/
private _setPhase(phase: EHotReloadPhase): void {
this._status.phase = phase;
if (this._onPhaseChange) {
try {
this._onPhaseChange(phase);
} catch (error) {
logger.warn('Error in phase change callback | 阶段变化回调错误:', error);
}
}
logger.debug(`Hot reload phase: ${phase} | 热更新阶段: ${phase}`);
}
/**
* Reset coordinator state.
* 重置协调器状态。
*/
public reset(): void {
this._status = {
phase: EHotReloadPhase.Idle,
startTime: 0
};
this._hotReloadPromise = null;
this._onPhaseChange = undefined;
}
}

View File

@@ -11,6 +11,8 @@
* - scripts/editor/ -> Editor-only code (inspectors, gizmos, panels)
*/
import type { IHotReloadOptions } from './HotReloadCoordinator';
/**
* User code target environment.
* 用户代码目标环境。
@@ -280,8 +282,13 @@ export interface IUserCodeService {
*
* @param projectPath - Project root path | 项目根路径
* @param onReload - Callback when code is reloaded | 代码重新加载时的回调
* @param options - Hot reload options | 热更新选项
*/
watch(projectPath: string, onReload: (event: HotReloadEvent) => void): Promise<void>;
watch(
projectPath: string,
onReload: (event: HotReloadEvent) => void,
options?: IHotReloadOptions
): Promise<void>;
/**
* Stop watching for file changes.
@@ -296,20 +303,36 @@ export interface IUserCodeService {
isWatching(): boolean;
}
import { EditorConfig } from '../../Config';
/**
* Default scripts directory name.
* 默认脚本目录名称。
*
* @deprecated Use EditorConfig.paths.scripts instead
*/
export const SCRIPTS_DIR = 'scripts';
export const SCRIPTS_DIR = EditorConfig.paths.scripts;
/**
* Editor scripts subdirectory name.
* 编辑器脚本子目录名称。
*
* @deprecated Use EditorConfig.paths.editorScripts instead
*/
export const EDITOR_SCRIPTS_DIR = 'editor';
export const EDITOR_SCRIPTS_DIR = EditorConfig.paths.editorScripts;
/**
* Default output directory for compiled user code.
* 编译后用户代码的默认输出目录。
*
* @deprecated Use EditorConfig.paths.compiled instead
*/
export const USER_CODE_OUTPUT_DIR = '.esengine/compiled';
export const USER_CODE_OUTPUT_DIR = EditorConfig.paths.compiled;
// Re-export hot reload coordinator types
// 重新导出热更新协调器类型
export {
EHotReloadPhase,
type IHotReloadStatus,
type IHotReloadOptions
} from './HotReloadCoordinator';

View File

@@ -7,7 +7,14 @@
*/
import type { IService } from '@esengine/ecs-framework';
import { Injectable, createLogger, PlatformDetector, ComponentRegistry as CoreComponentRegistry } from '@esengine/ecs-framework';
import {
Injectable,
createLogger,
PlatformDetector,
ComponentRegistry as CoreComponentRegistry,
COMPONENT_TYPE_NAME,
SYSTEM_TYPE_NAME
} from '@esengine/ecs-framework';
import type {
IUserCodeService,
UserScriptInfo,
@@ -15,7 +22,8 @@ import type {
UserCodeCompileResult,
CompileError,
UserCodeModule,
HotReloadEvent
HotReloadEvent,
IHotReloadOptions
} from './IUserCodeService';
import {
UserCodeTarget,
@@ -23,6 +31,8 @@ import {
EDITOR_SCRIPTS_DIR,
USER_CODE_OUTPUT_DIR
} from './IUserCodeService';
import { HotReloadCoordinator, EHotReloadPhase } from './HotReloadCoordinator';
import { EditorConfig } from '../../Config';
import type { IFileSystem, FileEntry } from '../IFileSystem';
import type { ComponentInspectorRegistry, IComponentInspector } from '../ComponentInspectorRegistry';
import { GizmoRegistry } from '../../Gizmos/GizmoRegistry';
@@ -64,8 +74,15 @@ export class UserCodeService implements IService, IUserCodeService {
*/
private _registeredGizmoTypes: any[] = [];
/**
* 热更新协调器
* Hot reload coordinator
*/
private _hotReloadCoordinator: HotReloadCoordinator;
constructor(fileSystem: IFileSystem) {
this._fileSystem = fileSystem;
this._hotReloadCoordinator = new HotReloadCoordinator();
}
/**
@@ -160,8 +177,8 @@ export class UserCodeService implements IService, IUserCodeService {
// Determine output file name | 确定输出文件名
const outputFileName = options.target === UserCodeTarget.Runtime
? 'user-runtime.js'
: 'user-editor.js';
? EditorConfig.output.runtimeBundle
: EditorConfig.output.editorBundle;
const outputPath = `${outputDir}${sep}${outputFileName}`;
// Build entry point content | 构建入口点内容
@@ -176,8 +193,8 @@ export class UserCodeService implements IService, IUserCodeService {
// Determine global name for IIFE output | 确定 IIFE 输出的全局名称
const globalName = options.target === UserCodeTarget.Runtime
? '__USER_RUNTIME_EXPORTS__'
: '__USER_EDITOR_EXPORTS__';
? EditorConfig.globals.userRuntimeExports
: EditorConfig.globals.userEditorExports;
// Build alias map for framework dependencies | 构建框架依赖的别名映射
const shimPath = `${outputDir}${sep}_shim_ecs_framework.js`.replace(/\\/g, '/');
@@ -421,7 +438,8 @@ export class UserCodeService implements IService, IUserCodeService {
// Access scene through Core.scene
// 通过 Core.scene 访问场景
const Core = (window as any).__ESENGINE__?.ecsFramework?.Core;
const sdkGlobal = (window as any)[EditorConfig.globals.sdk];
const Core = sdkGlobal?.ecsFramework?.Core;
const scene = Core?.scene;
if (!scene || !scene.entities) {
logger.warn('No active scene for hot reload | 没有活动场景用于热更新');
@@ -526,9 +544,10 @@ export class UserCodeService implements IService, IUserCodeService {
systemInstance.enabled = enabled;
}
// 标记为用户系统,便于后续识别和移除 | Mark as user system for later identification and removal
systemInstance.__isUserSystem__ = true;
systemInstance.__userSystemName__ = name;
// 标记为用户系统,便于后续识别和移除
// Mark as user system for later identification and removal
systemInstance[EditorConfig.typeMarkers.userSystem] = true;
systemInstance[EditorConfig.typeMarkers.userSystemName] = name;
// 添加到场景 | Add to scene
scene.addSystem(systemInstance);
@@ -565,9 +584,11 @@ export class UserCodeService implements IService, IUserCodeService {
for (const system of this._registeredSystems) {
try {
scene.removeSystem(system);
logger.debug(`Removed user system: ${system.__userSystemName__} | 移除用户系统: ${system.__userSystemName__}`);
const systemName = system[EditorConfig.typeMarkers.userSystemName];
logger.debug(`Removed user system: ${systemName} | 移除用户系统: ${systemName}`);
} catch (err) {
logger.warn(`Failed to remove system ${system.__userSystemName__}:`, err);
const systemName = system[EditorConfig.typeMarkers.userSystemName];
logger.warn(`Failed to remove system ${systemName}:`, err);
}
}
@@ -692,8 +713,13 @@ export class UserCodeService implements IService, IUserCodeService {
*
* @param projectPath - Project root path | 项目根路径
* @param onReload - Callback when code is reloaded | 代码重新加载时的回调
* @param options - Hot reload options | 热更新选项
*/
async watch(projectPath: string, onReload: (event: HotReloadEvent) => void): Promise<void> {
async watch(
projectPath: string,
onReload: (event: HotReloadEvent) => void,
options?: IHotReloadOptions
): Promise<void> {
if (this._watching) {
this._watchCallbacks.push(onReload);
return;
@@ -701,6 +727,16 @@ export class UserCodeService implements IService, IUserCodeService {
this._currentProjectPath = projectPath;
// Initialize hot reload coordinator with Core reference
// 使用 Core 引用初始化热更新协调器
const sdkGlobal = (window as any)[EditorConfig.globals.sdk];
const Core = sdkGlobal?.ecsFramework?.Core;
if (Core) {
this._hotReloadCoordinator.initialize(Core);
} else {
logger.warn('Core not available for HotReloadCoordinator | Core 不可用于热更新协调器');
}
try {
// Check if we're in Tauri environment | 检查是否在 Tauri 环境
if (PlatformDetector.isTauriEnvironment()) {
@@ -731,27 +767,44 @@ export class UserCodeService implements IService, IUserCodeService {
// Get previous module | 获取之前的模块
const previousModule = this.getModule(target);
// Recompile the affected target | 重新编译受影响的目标
const compileResult = await this.compile({
projectPath,
target
});
// Use coordinator for synchronized hot reload
// 使用协调器进行同步热更新
try {
const hotReloadEvent = await this._hotReloadCoordinator.performHotReload(
async () => {
// Recompile the affected target | 重新编译受影响的目标
const compileResult = await this.compile({
projectPath,
target
});
if (compileResult.success && compileResult.outputPath) {
// Reload the module | 重新加载模块
const newModule = await this.load(compileResult.outputPath, target);
if (!compileResult.success || !compileResult.outputPath) {
throw new Error(
`Hot reload compilation failed: ${compileResult.errors.map(e => e.message).join(', ')}`
);
}
// Create hot reload event | 创建热更新事件
const hotReloadEvent: HotReloadEvent = {
target,
changedFiles: paths,
previousModule,
newModule
};
// Reload the module | 重新加载模块
const newModule = await this.load(compileResult.outputPath, target);
this._notifyHotReload(hotReloadEvent);
} else {
logger.error('Hot reload compilation failed | 热更新编译失败', compileResult.errors);
// Create hot reload event | 创建热更新事件
const reloadEvent: HotReloadEvent = {
target,
changedFiles: paths,
previousModule,
newModule
};
return reloadEvent;
},
options
) as HotReloadEvent;
if (hotReloadEvent) {
this._notifyHotReload(hotReloadEvent);
}
} catch (error) {
logger.error('Hot reload failed | 热更新失败:', error);
}
});
@@ -774,6 +827,16 @@ export class UserCodeService implements IService, IUserCodeService {
}
}
/**
* Get the hot reload coordinator.
* 获取热更新协调器。
*
* @returns Hot reload coordinator instance | 热更新协调器实例
*/
getHotReloadCoordinator(): HotReloadCoordinator {
return this._hotReloadCoordinator;
}
/**
* Stop watching for file changes.
* 停止监视文件变更。
@@ -971,12 +1034,13 @@ export class UserCodeService implements IService, IUserCodeService {
const shimPaths: string[] = [];
// Create shim for @esengine/ecs-framework | 为 @esengine/ecs-framework 创建 shim
// This uses window.__ESENGINE__.ecsFramework set by PluginSDKRegistry
// 这使用 PluginSDKRegistry 设置的 window.__ESENGINE__.ecsFramework
// This uses window[EditorConfig.globals.sdk].ecsFramework set by PluginSDKRegistry
// 这使用 PluginSDKRegistry 设置的 window[EditorConfig.globals.sdk].ecsFramework
const ecsShimPath = `${outputDir}${sep}_shim_ecs_framework.js`;
const sdkGlobalName = EditorConfig.globals.sdk;
const ecsShimContent = `// Shim for @esengine/ecs-framework
// Maps to window.__ESENGINE__.ecsFramework set by PluginSDKRegistry
module.exports = (typeof window !== 'undefined' && window.__ESENGINE__ && window.__ESENGINE__.ecsFramework) || {};
// Maps to window.${sdkGlobalName}.ecsFramework set by PluginSDKRegistry
module.exports = (typeof window !== 'undefined' && window.${sdkGlobalName} && window.${sdkGlobalName}.ecsFramework) || {};
`;
await this._fileSystem.writeFile(ecsShimPath, ecsShimContent);
shimPaths.push(ecsShimPath);
@@ -1101,8 +1165,8 @@ module.exports = (typeof window !== 'undefined' && window.__ESENGINE__ && window
): Promise<Record<string, any>> {
// Determine global name based on target | 根据目标确定全局名称
const globalName = target === UserCodeTarget.Runtime
? '__USER_RUNTIME_EXPORTS__'
: '__USER_EDITOR_EXPORTS__';
? EditorConfig.globals.userRuntimeExports
: EditorConfig.globals.userEditorExports;
// Clear any previous exports | 清除之前的导出
(window as any)[globalName] = undefined;
@@ -1150,47 +1214,45 @@ module.exports = (typeof window !== 'undefined' && window.__ESENGINE__ && window
}
/**
* Check if a class extends Component.
* 检查类是否继承自 Component。
* Check if a class is decorated with @ECSComponent.
* 检查类是否使用了 @ECSComponent 装饰器
*
* Uses the actual Component class from the global framework to check inheritance.
* 使用全局框架中的实际 Component 类来检查继承关系。
* 用户组件必须使用 @ECSComponent 装饰器才能被识别。
* User components must use @ECSComponent decorator to be recognized.
*
* @example
* ```typescript
* @ECSComponent('MyComponent')
* class MyComponent extends Component {
* // ...
* }
* ```
*/
private _isComponentClass(cls: any): boolean {
// Get Component class from global framework | 从全局框架获取 Component 类
const framework = (window as any).__ESENGINE__?.ecsFramework;
if (!framework?.Component) {
return false;
}
// Use instanceof or prototype chain check | 使用 instanceof 或原型链检查
try {
const ComponentClass = framework.Component;
// Check if cls.prototype is an instance of Component
// 检查 cls.prototype 是否是 Component 的实例
return cls.prototype instanceof ComponentClass ||
ComponentClass.prototype.isPrototypeOf(cls.prototype);
} catch {
return false;
}
// 检查是否有 @ECSComponent 装饰器(通过 Symbol 键)
// Check if class has @ECSComponent decorator (via Symbol key)
return cls?.[COMPONENT_TYPE_NAME] !== undefined;
}
/**
* Check if a class extends System.
* 检查类是否继承自 System。
* Check if a class is decorated with @ECSSystem.
* 检查类是否使用了 @ECSSystem 装饰器
*
* 用户系统必须使用 @ECSSystem 装饰器才能被识别。
* User systems must use @ECSSystem decorator to be recognized.
*
* @example
* ```typescript
* @ECSSystem('MySystem')
* class MySystem extends EntitySystem {
* // ...
* }
* ```
*/
private _isSystemClass(cls: any): boolean {
let proto = cls.prototype;
while (proto) {
const name = proto.constructor?.name;
if (name === 'System' || name === 'EntityProcessingSystem') {
return true;
}
proto = Object.getPrototypeOf(proto);
}
return false;
// 检查是否有 @ECSSystem 装饰器(通过 Symbol 键)
// Check if class has @ECSSystem decorator (via Symbol key)
return cls?.[SYSTEM_TYPE_NAME] !== undefined;
}
/**

View File

@@ -102,14 +102,19 @@ export type {
UserCodeCompileResult,
CompileError,
UserCodeModule,
HotReloadEvent
HotReloadEvent,
IHotReloadStatus,
IHotReloadOptions
} from './IUserCodeService';
export {
UserCodeTarget,
SCRIPTS_DIR,
EDITOR_SCRIPTS_DIR,
USER_CODE_OUTPUT_DIR
USER_CODE_OUTPUT_DIR,
EHotReloadPhase
} from './IUserCodeService';
export { UserCodeService } from './UserCodeService';
export { HotReloadCoordinator } from './HotReloadCoordinator';