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:
@@ -22,10 +22,11 @@ if (!fs.existsSync(bundleDir)) {
|
||||
}
|
||||
|
||||
// Files to bundle
|
||||
// 需要打包的文件
|
||||
const filesToBundle = [
|
||||
{
|
||||
src: path.join(rootPath, 'packages/platform-web/dist/runtime.browser.js'),
|
||||
dst: path.join(bundleDir, 'runtime.browser.js')
|
||||
src: path.join(rootPath, 'packages/platform-web/dist/index.mjs'),
|
||||
dst: path.join(bundleDir, 'platform-web.mjs')
|
||||
},
|
||||
{
|
||||
src: path.join(rootPath, 'packages/engine/pkg/es_engine_bg.wasm'),
|
||||
@@ -96,34 +97,101 @@ for (const { src, dst } of typeFilesToBundle) {
|
||||
}
|
||||
}
|
||||
|
||||
// Update tauri.conf.json to include runtime directory
|
||||
if (success) {
|
||||
const tauriConfigPath = path.join(editorPath, 'src-tauri', 'tauri.conf.json');
|
||||
const config = JSON.parse(fs.readFileSync(tauriConfigPath, 'utf8'));
|
||||
// Copy engine modules directory from dist/engine to src-tauri/engine
|
||||
// 复制引擎模块目录从 dist/engine 到 src-tauri/engine
|
||||
const engineSrcDir = path.join(editorPath, 'dist', 'engine');
|
||||
const engineDstDir = path.join(editorPath, 'src-tauri', 'engine');
|
||||
|
||||
// Add runtime directory to resources
|
||||
if (!config.bundle) {
|
||||
config.bundle = {};
|
||||
}
|
||||
if (!config.bundle.resources) {
|
||||
config.bundle.resources = {};
|
||||
/**
|
||||
* Recursively copy directory
|
||||
* 递归复制目录
|
||||
*/
|
||||
function copyDirRecursive(src, dst) {
|
||||
if (!fs.existsSync(src)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Handle both array and object format for resources
|
||||
if (Array.isArray(config.bundle.resources)) {
|
||||
if (!config.bundle.resources.includes('runtime/**/*')) {
|
||||
config.bundle.resources.push('runtime/**/*');
|
||||
fs.writeFileSync(tauriConfigPath, JSON.stringify(config, null, 2));
|
||||
console.log('✓ Updated tauri.conf.json with runtime resources');
|
||||
}
|
||||
} else if (typeof config.bundle.resources === 'object') {
|
||||
// Object format - add runtime files if not present
|
||||
if (!config.bundle.resources['runtime/**/*']) {
|
||||
config.bundle.resources['runtime/**/*'] = '.';
|
||||
fs.writeFileSync(tauriConfigPath, JSON.stringify(config, null, 2));
|
||||
console.log('✓ Updated tauri.conf.json with runtime resources');
|
||||
if (!fs.existsSync(dst)) {
|
||||
fs.mkdirSync(dst, { recursive: true });
|
||||
}
|
||||
|
||||
const entries = fs.readdirSync(src, { withFileTypes: true });
|
||||
for (const entry of entries) {
|
||||
const srcPath = path.join(src, entry.name);
|
||||
const dstPath = path.join(dst, entry.name);
|
||||
|
||||
if (entry.isDirectory()) {
|
||||
copyDirRecursive(srcPath, dstPath);
|
||||
} else {
|
||||
fs.copyFileSync(srcPath, dstPath);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (fs.existsSync(engineSrcDir)) {
|
||||
// Remove old engine directory if exists
|
||||
if (fs.existsSync(engineDstDir)) {
|
||||
fs.rmSync(engineDstDir, { recursive: true });
|
||||
}
|
||||
|
||||
if (copyDirRecursive(engineSrcDir, engineDstDir)) {
|
||||
// Count files
|
||||
let fileCount = 0;
|
||||
function countFiles(dir) {
|
||||
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
||||
for (const entry of entries) {
|
||||
if (entry.isDirectory()) {
|
||||
countFiles(path.join(dir, entry.name));
|
||||
} else {
|
||||
fileCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
countFiles(engineDstDir);
|
||||
console.log(`✓ Copied engine modules directory (${fileCount} files)`);
|
||||
}
|
||||
} else {
|
||||
console.warn(`Engine modules directory not found: ${engineSrcDir}`);
|
||||
console.log(' Build editor-app first: pnpm --filter @esengine/editor-app build');
|
||||
}
|
||||
|
||||
// Copy esbuild binary for user code compilation
|
||||
// 复制 esbuild 二进制文件用于用户代码编译
|
||||
const binDir = path.join(editorPath, 'src-tauri', 'bin');
|
||||
if (!fs.existsSync(binDir)) {
|
||||
fs.mkdirSync(binDir, { recursive: true });
|
||||
console.log(`Created bin directory: ${binDir}`);
|
||||
}
|
||||
|
||||
// Platform-specific esbuild binary paths
|
||||
// 平台特定的 esbuild 二进制路径
|
||||
const esbuildSources = {
|
||||
win32: path.join(rootPath, 'node_modules/@esbuild/win32-x64/esbuild.exe'),
|
||||
darwin: path.join(rootPath, 'node_modules/@esbuild/darwin-x64/bin/esbuild'),
|
||||
linux: path.join(rootPath, 'node_modules/@esbuild/linux-x64/bin/esbuild'),
|
||||
};
|
||||
|
||||
const platform = process.platform;
|
||||
const esbuildSrc = esbuildSources[platform];
|
||||
const esbuildDst = path.join(binDir, platform === 'win32' ? 'esbuild.exe' : 'esbuild');
|
||||
|
||||
if (esbuildSrc && fs.existsSync(esbuildSrc)) {
|
||||
try {
|
||||
fs.copyFileSync(esbuildSrc, esbuildDst);
|
||||
// Ensure executable permission on Unix
|
||||
if (platform !== 'win32') {
|
||||
fs.chmodSync(esbuildDst, 0o755);
|
||||
}
|
||||
const stats = fs.statSync(esbuildDst);
|
||||
console.log(`✓ Bundled esbuild binary (${(stats.size / 1024 / 1024).toFixed(2)} MB)`);
|
||||
} catch (error) {
|
||||
console.warn(`Failed to bundle esbuild: ${error.message}`);
|
||||
console.log(' User code compilation will require global esbuild installation');
|
||||
}
|
||||
} else {
|
||||
console.warn(`esbuild binary not found for platform ${platform}: ${esbuildSrc}`);
|
||||
console.log(' User code compilation will require global esbuild installation');
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
|
||||
Reference in New Issue
Block a user