feat: 添加跨平台运行时、资产系统和UI适配功能 (#256)
* feat(platform-common): 添加WASM加载器和环境检测API * feat(rapier2d): 新增Rapier2D WASM绑定包 * feat(physics-rapier2d): 添加跨平台WASM加载器 * feat(asset-system): 添加运行时资产目录和bundle格式 * feat(asset-system-editor): 新增编辑器资产管理包 * feat(editor-core): 添加构建系统和模块管理 * feat(editor-app): 重构浏览器预览使用import maps * feat(platform-web): 添加BrowserRuntime和资产读取 * feat(engine): 添加材质系统和着色器管理 * feat(material): 新增材质系统和着色器编辑器 * feat(tilemap): 增强tilemap编辑器和动画系统 * feat(modules): 添加module.json配置 * feat(core): 添加module.json和类型定义更新 * chore: 更新依赖和构建配置 * refactor(plugins): 更新插件模板使用ModuleManifest * chore: 添加第三方依赖库 * chore: 移除BehaviourTree-ai和ecs-astar子模块 * docs: 更新README和文档主题样式 * fix: 修复Rust文档测试和添加rapier2d WASM绑定 * fix(tilemap-editor): 修复画布高DPI屏幕分辨率适配问题 * feat(ui): 添加UI屏幕适配系统(CanvasScaler/SafeArea) * fix(ecs-engine-bindgen): 添加缺失的ecs-framework-math依赖 * fix: 添加缺失的包依赖修复CI构建 * fix: 修复CodeQL检测到的代码问题 * fix: 修复构建错误和缺失依赖 * fix: 修复类型检查错误 * fix(material-system): 修复tsconfig配置支持TypeScript项目引用 * fix(editor-core): 修复Rollup构建配置添加tauri external * fix: 修复CodeQL检测到的代码问题 * fix: 修复CodeQL检测到的代码问题
This commit is contained in:
@@ -58,6 +58,343 @@ function copyPluginModulesPlugin(): Plugin {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Plugin to copy engine modules after each build.
|
||||
* 每次构建后复制引擎模块的插件。
|
||||
*/
|
||||
function copyEngineModulesPlugin(): Plugin {
|
||||
const packagesDir = path.resolve(__dirname, '..');
|
||||
|
||||
function getEngineModules() {
|
||||
const modules: Array<{
|
||||
id: string;
|
||||
name: string;
|
||||
displayName: string;
|
||||
packageDir: string;
|
||||
moduleJsonPath: string;
|
||||
distPath: string;
|
||||
editorPackage?: string;
|
||||
isCore: boolean;
|
||||
category: string;
|
||||
}> = [];
|
||||
|
||||
let packages: string[];
|
||||
try {
|
||||
packages = fs.readdirSync(packagesDir);
|
||||
} catch {
|
||||
return modules;
|
||||
}
|
||||
|
||||
for (const pkg of packages) {
|
||||
const pkgDir = path.join(packagesDir, pkg);
|
||||
const moduleJsonPath = path.join(pkgDir, 'module.json');
|
||||
|
||||
try {
|
||||
if (!fs.statSync(pkgDir).isDirectory()) continue;
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!fs.existsSync(moduleJsonPath)) continue;
|
||||
|
||||
try {
|
||||
const moduleJson = JSON.parse(fs.readFileSync(moduleJsonPath, 'utf-8'));
|
||||
if (moduleJson.isEngineModule !== false) {
|
||||
// Use outputPath from module.json, default to "dist/index.js"
|
||||
const outputPath = moduleJson.outputPath || 'dist/index.js';
|
||||
const distPath = path.join(pkgDir, outputPath);
|
||||
|
||||
modules.push({
|
||||
id: moduleJson.id || pkg,
|
||||
name: moduleJson.name || `@esengine/${pkg}`,
|
||||
displayName: moduleJson.displayName || pkg,
|
||||
packageDir: pkgDir,
|
||||
moduleJsonPath,
|
||||
distPath,
|
||||
editorPackage: moduleJson.editorPackage,
|
||||
isCore: moduleJson.isCore || false,
|
||||
category: moduleJson.category || 'Other'
|
||||
});
|
||||
}
|
||||
} catch {
|
||||
// Ignore parse errors
|
||||
}
|
||||
}
|
||||
|
||||
return modules;
|
||||
}
|
||||
|
||||
return {
|
||||
name: 'copy-engine-modules',
|
||||
writeBundle(options) {
|
||||
const outDir = options.dir || 'dist';
|
||||
const engineDir = path.join(outDir, 'engine');
|
||||
|
||||
// Clean and recreate engine directory
|
||||
if (fs.existsSync(engineDir)) {
|
||||
fs.rmSync(engineDir, { recursive: true });
|
||||
}
|
||||
fs.mkdirSync(engineDir, { recursive: true });
|
||||
|
||||
const modules = getEngineModules();
|
||||
const moduleInfos: Array<{
|
||||
id: string;
|
||||
name: string;
|
||||
displayName: string;
|
||||
hasRuntime: boolean;
|
||||
editorPackage?: string;
|
||||
isCore: boolean;
|
||||
category: string;
|
||||
jsSize?: number;
|
||||
requiresWasm?: boolean;
|
||||
wasmSize?: number;
|
||||
wasmFiles?: string[];
|
||||
}> = [];
|
||||
|
||||
const editorPackages = new Set<string>();
|
||||
|
||||
/**
|
||||
* Calculate total WASM file size in a directory.
|
||||
* 计算目录中 WASM 文件的总大小。
|
||||
*/
|
||||
function getWasmSize(pkgDir: string): number {
|
||||
let totalSize = 0;
|
||||
const checkDirs = [
|
||||
pkgDir,
|
||||
path.join(pkgDir, 'pkg'),
|
||||
path.join(pkgDir, 'dist')
|
||||
];
|
||||
|
||||
for (const dir of checkDirs) {
|
||||
if (!fs.existsSync(dir)) continue;
|
||||
try {
|
||||
const files = fs.readdirSync(dir);
|
||||
for (const file of files) {
|
||||
if (file.endsWith('.wasm')) {
|
||||
const filePath = path.join(dir, file);
|
||||
const stat = fs.statSync(filePath);
|
||||
totalSize += stat.size;
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Ignore errors
|
||||
}
|
||||
}
|
||||
return totalSize;
|
||||
}
|
||||
|
||||
console.log(`[copy-engine-modules] Copying ${modules.length} modules to dist/engine/`);
|
||||
|
||||
for (const module of modules) {
|
||||
const moduleOutputDir = path.join(engineDir, module.id);
|
||||
fs.mkdirSync(moduleOutputDir, { recursive: true });
|
||||
|
||||
// Read full module.json for additional fields
|
||||
// 读取完整 module.json 获取额外字段
|
||||
let moduleJson: Record<string, unknown> = {};
|
||||
try {
|
||||
moduleJson = JSON.parse(fs.readFileSync(module.moduleJsonPath, 'utf-8'));
|
||||
} catch {
|
||||
// Ignore parse errors
|
||||
}
|
||||
|
||||
// Copy module.json
|
||||
fs.copyFileSync(module.moduleJsonPath, path.join(moduleOutputDir, 'module.json'));
|
||||
|
||||
// Copy dist/index.js if exists
|
||||
let hasRuntime = false;
|
||||
let jsSize = 0;
|
||||
if (fs.existsSync(module.distPath)) {
|
||||
fs.copyFileSync(module.distPath, path.join(moduleOutputDir, 'index.js'));
|
||||
// Get JS file size
|
||||
jsSize = fs.statSync(module.distPath).size;
|
||||
// Copy source map if exists
|
||||
const sourceMapPath = module.distPath + '.map';
|
||||
if (fs.existsSync(sourceMapPath)) {
|
||||
fs.copyFileSync(sourceMapPath, path.join(moduleOutputDir, 'index.js.map'));
|
||||
}
|
||||
hasRuntime = true;
|
||||
|
||||
// Copy additional included files (e.g., chunks)
|
||||
// 复制额外包含的文件(如 chunk)
|
||||
const includes = moduleJson.includes as string[] | undefined;
|
||||
if (includes && includes.length > 0) {
|
||||
const distDir = path.dirname(module.distPath);
|
||||
for (const pattern of includes) {
|
||||
// Convert glob pattern to regex
|
||||
const regexPattern = pattern
|
||||
.replace(/\\/g, '\\\\')
|
||||
.replace(/\./g, '\\.')
|
||||
.replace(/\*/g, '.*')
|
||||
.replace(/\?/g, '.');
|
||||
const regex = new RegExp(`^${regexPattern}$`);
|
||||
|
||||
// Find matching files in dist directory
|
||||
if (fs.existsSync(distDir)) {
|
||||
const files = fs.readdirSync(distDir);
|
||||
for (const file of files) {
|
||||
if (regex.test(file)) {
|
||||
const srcFile = path.join(distDir, file);
|
||||
const destFile = path.join(moduleOutputDir, file);
|
||||
fs.copyFileSync(srcFile, destFile);
|
||||
jsSize += fs.statSync(srcFile).size;
|
||||
// Copy source map for included file if exists
|
||||
const mapFile = srcFile + '.map';
|
||||
if (fs.existsSync(mapFile)) {
|
||||
fs.copyFileSync(mapFile, destFile + '.map');
|
||||
}
|
||||
console.log(`[copy-engine-modules] Copied include to ${module.id}/: ${file}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate WASM size and copy WASM files if module requires WASM
|
||||
// 如果模块需要 WASM,计算 WASM 大小并复制 WASM 文件
|
||||
const requiresWasm = moduleJson.requiresWasm === true;
|
||||
let wasmSize = 0;
|
||||
const copiedWasmFiles: string[] = [];
|
||||
if (requiresWasm) {
|
||||
wasmSize = getWasmSize(module.packageDir);
|
||||
if (wasmSize > 0) {
|
||||
console.log(`[copy-engine-modules] ${module.id}: WASM size = ${(wasmSize / 1024).toFixed(1)} KB`);
|
||||
}
|
||||
|
||||
// Copy WASM files from wasmPaths defined in module.json
|
||||
// wasmPaths 现在是相对于源包目录的路径,如 "rapier_wasm2d_bg.wasm"
|
||||
// 需要找到实际的 WASM 文件并复制到输出的模块目录
|
||||
const wasmPaths = moduleJson.wasmPaths as string[] | undefined;
|
||||
if (wasmPaths && wasmPaths.length > 0) {
|
||||
for (const wasmRelPath of wasmPaths) {
|
||||
const wasmFileName = path.basename(wasmRelPath);
|
||||
|
||||
// 查找源 WASM 文件的可能位置
|
||||
// wasmPaths 里配置的是相对路径,实际文件在源包里
|
||||
// 对于 @esengine/rapier2d,WASM 在 packages/rapier2d/pkg/ 下
|
||||
const possibleSrcPaths = [
|
||||
// 直接在包目录下(如果 wasmRelPath 就是文件名)
|
||||
path.join(module.packageDir, wasmRelPath),
|
||||
// 在包的 pkg 目录下(wasm-pack 输出)
|
||||
path.join(module.packageDir, 'pkg', wasmFileName),
|
||||
// 在包的 dist 目录下
|
||||
path.join(module.packageDir, 'dist', wasmFileName),
|
||||
];
|
||||
|
||||
// 对于依赖其他包 WASM 的情况,检查依赖包
|
||||
// 例如 physics-rapier2d 依赖 rapier2d 的 WASM
|
||||
const depMatch = moduleJson.name?.toString().match(/@esengine\/(.+)/);
|
||||
if (depMatch) {
|
||||
// 检查同名的依赖包(去掉 physics- 前缀)
|
||||
const baseName = depMatch[1].replace('physics-', '');
|
||||
possibleSrcPaths.push(
|
||||
path.join(packagesDir, baseName, 'pkg', wasmFileName),
|
||||
path.join(packagesDir, baseName, wasmFileName)
|
||||
);
|
||||
}
|
||||
|
||||
let copied = false;
|
||||
for (const srcPath of possibleSrcPaths) {
|
||||
if (fs.existsSync(srcPath)) {
|
||||
const destPath = path.join(moduleOutputDir, wasmFileName);
|
||||
fs.copyFileSync(srcPath, destPath);
|
||||
copiedWasmFiles.push(wasmFileName);
|
||||
console.log(`[copy-engine-modules] Copied WASM to ${module.id}/: ${wasmFileName}`);
|
||||
copied = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!copied) {
|
||||
console.warn(`[copy-engine-modules] WASM file not found: ${wasmRelPath} (tried ${possibleSrcPaths.length} paths)`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Copy pkg directory if exists (for WASM JS bindings like rapier2d)
|
||||
// 如果存在 pkg 目录则复制(用于 WASM JS 绑定如 rapier2d)
|
||||
// The JS and WASM files must be in the same directory for import.meta.url to work
|
||||
// JS 和 WASM 文件必须在同一目录才能让 import.meta.url 正常工作
|
||||
const pkgDir = path.join(module.packageDir, 'pkg');
|
||||
if (fs.existsSync(pkgDir)) {
|
||||
const pkgOutputDir = path.join(moduleOutputDir, 'pkg');
|
||||
fs.mkdirSync(pkgOutputDir, { recursive: true });
|
||||
const pkgFiles = fs.readdirSync(pkgDir);
|
||||
for (const file of pkgFiles) {
|
||||
// Copy both JS and WASM files to pkg directory
|
||||
// 将 JS 和 WASM 文件都复制到 pkg 目录
|
||||
if (file.endsWith('.js') || file.endsWith('.wasm')) {
|
||||
const srcFile = path.join(pkgDir, file);
|
||||
const destFile = path.join(pkgOutputDir, file);
|
||||
fs.copyFileSync(srcFile, destFile);
|
||||
console.log(`[copy-engine-modules] Copied pkg to ${module.id}/pkg/: ${file}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
moduleInfos.push({
|
||||
id: module.id,
|
||||
name: module.name,
|
||||
displayName: module.displayName,
|
||||
hasRuntime,
|
||||
editorPackage: module.editorPackage,
|
||||
isCore: module.isCore,
|
||||
category: module.category,
|
||||
// Only include jsSize if there's actual runtime code
|
||||
// 只有实际有运行时代码时才包含 jsSize
|
||||
jsSize: jsSize > 0 ? jsSize : undefined,
|
||||
requiresWasm: requiresWasm || undefined,
|
||||
wasmSize: wasmSize > 0 ? wasmSize : undefined,
|
||||
// WASM files that were copied to dist/wasm/
|
||||
// 复制到 dist/wasm/ 的 WASM 文件
|
||||
wasmFiles: copiedWasmFiles.length > 0 ? copiedWasmFiles : undefined
|
||||
});
|
||||
|
||||
if (module.editorPackage) {
|
||||
editorPackages.add(module.editorPackage);
|
||||
}
|
||||
}
|
||||
|
||||
// Copy editor packages
|
||||
for (const editorPkg of editorPackages) {
|
||||
const match = editorPkg.match(/@esengine\/(.+)/);
|
||||
if (!match) continue;
|
||||
|
||||
const pkgName = match[1];
|
||||
const pkgDir = path.join(packagesDir, pkgName);
|
||||
const distPath = path.join(pkgDir, 'dist', 'index.js');
|
||||
|
||||
if (!fs.existsSync(distPath)) continue;
|
||||
|
||||
const editorOutputDir = path.join(engineDir, pkgName);
|
||||
fs.mkdirSync(editorOutputDir, { recursive: true });
|
||||
fs.copyFileSync(distPath, path.join(editorOutputDir, 'index.js'));
|
||||
|
||||
const sourceMapPath = distPath + '.map';
|
||||
if (fs.existsSync(sourceMapPath)) {
|
||||
fs.copyFileSync(sourceMapPath, path.join(editorOutputDir, 'index.js.map'));
|
||||
}
|
||||
}
|
||||
|
||||
// Create index.json
|
||||
const indexData = {
|
||||
version: '1.0.0',
|
||||
generatedAt: new Date().toISOString(),
|
||||
modules: moduleInfos
|
||||
};
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(engineDir, 'index.json'),
|
||||
JSON.stringify(indexData, null, 2)
|
||||
);
|
||||
|
||||
console.log(`[copy-engine-modules] Done! Created dist/engine/index.json`);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const host = process.env.TAURI_DEV_HOST;
|
||||
const wasmPackages: string[] = [];
|
||||
|
||||
@@ -161,6 +498,7 @@ export default defineConfig({
|
||||
tsDecorators: true,
|
||||
}),
|
||||
copyPluginModulesPlugin(),
|
||||
copyEngineModulesPlugin(),
|
||||
],
|
||||
clearScreen: false,
|
||||
server: {
|
||||
|
||||
Reference in New Issue
Block a user