feat(editor): 完善用户代码热更新和环境检测 (#275)

* fix: 更新 bundle-runtime 脚本使用正确的 platform-web 输出文件

原脚本引用的 runtime.browser.js 不存在,改为使用 index.mjs

* feat(editor): 完善用户代码热更新和环境检测

## 热更新改进
- 添加 hotReloadInstances() 方法,通过更新原型链实现真正的热更新
- 组件实例保留数据,仅更新方法
- ComponentRegistry 支持热更新时替换同名组件类

## 环境检测
- 启动时检测 esbuild 可用性
- 在启动页面底部显示环境状态指示器
- 添加 check_environment Rust 命令和前端 API

## esbuild 打包
- 将 esbuild 二进制文件打包到应用中
- 用户无需全局安装 esbuild
- 支持 Windows/macOS/Linux 平台

## 文件监视优化
- 添加 300ms 防抖,避免重复编译
- 修复路径分隔符混合问题

## 资源打包修复
- 修复 Tauri 资源配置,保留 engine 目录结构
- 添加 src-tauri/bin/ 和 src-tauri/engine/ 到 gitignore

* fix: 将热更新模式改为可选,修复测试失败

- ComponentRegistry 添加 hotReloadEnabled 标志,默认禁用
- 只有启用热更新模式时才会替换同名组件类
- 编辑器环境自动启用热更新模式
- reset() 方法中重置热更新标志

* test: 添加热更新模式的测试用例
This commit is contained in:
YHH
2025-12-05 14:24:09 +08:00
committed by GitHub
parent d7454e3ca4
commit 6702f0bfad
12 changed files with 755 additions and 57 deletions

View File

@@ -1,8 +1,9 @@
import { useState, useEffect, useRef } from 'react';
import { getVersion } from '@tauri-apps/api/app';
import { Globe, ChevronDown, Download, X, Loader2, Trash2 } from 'lucide-react';
import { Globe, ChevronDown, Download, X, Loader2, Trash2, CheckCircle, AlertCircle } from 'lucide-react';
import { checkForUpdatesOnStartup, installUpdate, type UpdateCheckResult } from '../utils/updater';
import { StartupLogo } from './StartupLogo';
import { TauriAPI, type EnvironmentCheckResult } from '../api/tauri';
import '../styles/StartupPage.css';
type Locale = 'en' | 'zh';
@@ -33,6 +34,8 @@ export function StartupPage({ onOpenProject, onCreateProject, onOpenRecentProjec
const [updateInfo, setUpdateInfo] = useState<UpdateCheckResult | null>(null);
const [showUpdateBanner, setShowUpdateBanner] = useState(false);
const [isInstalling, setIsInstalling] = useState(false);
const [envCheck, setEnvCheck] = useState<EnvironmentCheckResult | null>(null);
const [showEnvStatus, setShowEnvStatus] = useState(false);
const langMenuRef = useRef<HTMLDivElement>(null);
useEffect(() => {
@@ -59,6 +62,24 @@ export function StartupPage({ onOpenProject, onCreateProject, onOpenRecentProjec
});
}, []);
// 启动时检测开发环境
useEffect(() => {
TauriAPI.checkEnvironment().then((result) => {
setEnvCheck(result);
// 如果环境就绪,在控制台显示信息
if (result.ready) {
console.log('[Environment] Ready ✓');
console.log(`[Environment] esbuild: ${result.esbuild.version} (${result.esbuild.source})`);
} else {
// 环境有问题,显示提示
setShowEnvStatus(true);
console.warn('[Environment] Not ready:', result.esbuild.error);
}
}).catch((error) => {
console.error('[Environment] Check failed:', error);
});
}, []);
const translations = {
en: {
title: 'ESEngine Editor',
@@ -76,7 +97,11 @@ export function StartupPage({ onOpenProject, onCreateProject, onOpenRecentProjec
deleteConfirmTitle: 'Delete Project',
deleteConfirmMessage: 'Are you sure you want to permanently delete this project? This action cannot be undone.',
cancel: 'Cancel',
delete: 'Delete'
delete: 'Delete',
envReady: 'Environment Ready',
envNotReady: 'Environment Issue',
esbuildReady: 'esbuild ready',
esbuildMissing: 'esbuild not found'
},
zh: {
title: 'ESEngine 编辑器',
@@ -94,7 +119,11 @@ export function StartupPage({ onOpenProject, onCreateProject, onOpenRecentProjec
deleteConfirmTitle: '删除项目',
deleteConfirmMessage: '确定要永久删除此项目吗?此操作无法撤销。',
cancel: '取消',
delete: '删除'
delete: '删除',
envReady: '环境就绪',
envNotReady: '环境问题',
esbuildReady: 'esbuild 就绪',
esbuildMissing: '未找到 esbuild'
}
};
@@ -220,6 +249,43 @@ export function StartupPage({ onOpenProject, onCreateProject, onOpenRecentProjec
<div className="startup-footer">
<span className="startup-version">{versionText}</span>
{/* 环境状态指示器 | Environment Status Indicator */}
{envCheck && (
<div
className={`startup-env-status ${envCheck.ready ? 'ready' : 'warning'}`}
onClick={() => setShowEnvStatus(!showEnvStatus)}
title={envCheck.ready ? t.envReady : t.envNotReady}
>
{envCheck.ready ? (
<CheckCircle size={14} />
) : (
<AlertCircle size={14} />
)}
{showEnvStatus && (
<div className="startup-env-tooltip">
<div className="env-tooltip-title">
{envCheck.ready ? t.envReady : t.envNotReady}
</div>
<div className={`env-tooltip-item ${envCheck.esbuild.available ? 'ok' : 'error'}`}>
{envCheck.esbuild.available ? (
<>
<CheckCircle size={12} />
<span>esbuild {envCheck.esbuild.version}</span>
<span className="env-source">({envCheck.esbuild.source})</span>
</>
) : (
<>
<AlertCircle size={12} />
<span>{t.esbuildMissing}</span>
</>
)}
</div>
</div>
)}
</div>
)}
{onLocaleChange && (
<div className="startup-locale-dropdown" ref={langMenuRef}>
<button