Files
esengine/packages/editor-app/scripts/bundle-runtime.mjs
YHH 6702f0bfad 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: 添加热更新模式的测试用例
2025-12-05 14:24:09 +08:00

202 lines
6.6 KiB
JavaScript

/**
* Bundle runtime files for production build
* 为生产构建打包运行时文件
*/
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import { dirname } from 'path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const editorPath = path.resolve(__dirname, '..');
const rootPath = path.resolve(editorPath, '../..');
const bundleDir = path.join(editorPath, 'src-tauri', 'runtime');
// Create bundle directory
if (!fs.existsSync(bundleDir)) {
fs.mkdirSync(bundleDir, { recursive: true });
console.log(`Created bundle directory: ${bundleDir}`);
}
// Files to bundle
// 需要打包的文件
const filesToBundle = [
{
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'),
dst: path.join(bundleDir, 'es_engine_bg.wasm')
},
{
src: path.join(rootPath, 'packages/engine/pkg/es_engine.js'),
dst: path.join(bundleDir, 'es_engine.js')
}
];
// Type definition files for IDE intellisense
// 用于 IDE 智能感知的类型定义文件
const typesDir = path.join(bundleDir, 'types');
if (!fs.existsSync(typesDir)) {
fs.mkdirSync(typesDir, { recursive: true });
console.log(`Created types directory: ${typesDir}`);
}
const typeFilesToBundle = [
{
src: path.join(rootPath, 'packages/core/dist/index.d.ts'),
dst: path.join(typesDir, 'ecs-framework.d.ts')
},
{
src: path.join(rootPath, 'packages/engine-core/dist/index.d.ts'),
dst: path.join(typesDir, 'engine-core.d.ts')
}
];
// Copy files
let success = true;
for (const { src, dst } of filesToBundle) {
try {
if (!fs.existsSync(src)) {
console.error(`Source file not found: ${src}`);
console.log('Please build the runtime modules first:');
console.log(' npm run build --workspace=@esengine/platform-web');
console.log(' cd packages/engine && wasm-pack build --target web');
success = false;
continue;
}
fs.copyFileSync(src, dst);
const stats = fs.statSync(dst);
console.log(`✓ Bundled ${path.basename(dst)} (${(stats.size / 1024).toFixed(2)} KB)`);
} catch (error) {
console.error(`Failed to bundle ${path.basename(src)}: ${error.message}`);
success = false;
}
}
// Copy type definition files (optional - don't fail if not found)
// 复制类型定义文件(可选 - 找不到不报错)
for (const { src, dst } of typeFilesToBundle) {
try {
if (!fs.existsSync(src)) {
console.warn(`Type definition not found: ${src}`);
console.log(' Build packages first: pnpm --filter @esengine/core build');
continue;
}
fs.copyFileSync(src, dst);
const stats = fs.statSync(dst);
console.log(`✓ Bundled type definition ${path.basename(dst)} (${(stats.size / 1024).toFixed(2)} KB)`);
} catch (error) {
console.warn(`Failed to bundle type definition ${path.basename(src)}: ${error.message}`);
}
}
// 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');
/**
* Recursively copy directory
* 递归复制目录
*/
function copyDirRecursive(src, dst) {
if (!fs.existsSync(src)) {
return false;
}
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) {
console.error('Runtime bundling failed');
process.exit(1);
}
console.log('Runtime files bundled successfully!');