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:
309
packages/editor-core/src/Services/Build/BuildService.ts
Normal file
309
packages/editor-core/src/Services/Build/BuildService.ts
Normal file
@@ -0,0 +1,309 @@
|
||||
/**
|
||||
* Build Service.
|
||||
* 构建服务。
|
||||
*
|
||||
* Manages build pipelines and executes build tasks.
|
||||
* 管理构建管线和执行构建任务。
|
||||
*/
|
||||
|
||||
import type { IService } from '@esengine/ecs-framework';
|
||||
import type {
|
||||
IBuildPipeline,
|
||||
IBuildPipelineRegistry,
|
||||
BuildPlatform,
|
||||
BuildConfig,
|
||||
BuildResult,
|
||||
BuildProgress
|
||||
} from './IBuildPipeline';
|
||||
import { BuildStatus } from './IBuildPipeline';
|
||||
|
||||
/**
|
||||
* Build task.
|
||||
* 构建任务。
|
||||
*/
|
||||
export interface BuildTask {
|
||||
/** Task ID | 任务 ID */
|
||||
id: string;
|
||||
/** Target platform | 目标平台 */
|
||||
platform: BuildPlatform;
|
||||
/** Build configuration | 构建配置 */
|
||||
config: BuildConfig;
|
||||
/** Current progress | 当前进度 */
|
||||
progress: BuildProgress;
|
||||
/** Start time | 开始时间 */
|
||||
startTime: Date;
|
||||
/** End time | 结束时间 */
|
||||
endTime?: Date;
|
||||
/** Abort controller | 中止控制器 */
|
||||
abortController: AbortController;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build Service.
|
||||
* 构建服务。
|
||||
*
|
||||
* Provides build pipeline registration and build task management.
|
||||
* 提供构建管线注册和构建任务管理。
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const buildService = services.resolve(BuildService);
|
||||
*
|
||||
* // Register build pipeline | 注册构建管线
|
||||
* buildService.registerPipeline(new WebBuildPipeline());
|
||||
* buildService.registerPipeline(new WeChatBuildPipeline());
|
||||
*
|
||||
* // Execute build | 执行构建
|
||||
* const result = await buildService.build({
|
||||
* platform: BuildPlatform.Web,
|
||||
* outputPath: './dist',
|
||||
* isRelease: true,
|
||||
* sourceMap: false
|
||||
* }, (progress) => {
|
||||
* console.log(`${progress.message} (${progress.progress}%)`);
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export class BuildService implements IService, IBuildPipelineRegistry {
|
||||
private _pipelines = new Map<BuildPlatform, IBuildPipeline>();
|
||||
private _currentTask: BuildTask | null = null;
|
||||
private _taskHistory: BuildTask[] = [];
|
||||
private _maxHistorySize = 10;
|
||||
|
||||
/**
|
||||
* Dispose service resources.
|
||||
* 释放服务资源。
|
||||
*/
|
||||
dispose(): void {
|
||||
this.cancelBuild();
|
||||
this._pipelines.clear();
|
||||
this._taskHistory = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Register build pipeline.
|
||||
* 注册构建管线。
|
||||
*
|
||||
* @param pipeline - Build pipeline instance | 构建管线实例
|
||||
*/
|
||||
register(pipeline: IBuildPipeline): void {
|
||||
if (this._pipelines.has(pipeline.platform)) {
|
||||
console.warn(`[BuildService] Overwriting existing pipeline: ${pipeline.platform} | 覆盖已存在的构建管线: ${pipeline.platform}`);
|
||||
}
|
||||
this._pipelines.set(pipeline.platform, pipeline);
|
||||
console.log(`[BuildService] Registered pipeline: ${pipeline.displayName} | 注册构建管线: ${pipeline.displayName}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get build pipeline.
|
||||
* 获取构建管线。
|
||||
*
|
||||
* @param platform - Target platform | 目标平台
|
||||
* @returns Build pipeline, or undefined if not registered | 构建管线,如果未注册则返回 undefined
|
||||
*/
|
||||
get(platform: BuildPlatform): IBuildPipeline | undefined {
|
||||
return this._pipelines.get(platform);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all registered build pipelines.
|
||||
* 获取所有已注册的构建管线。
|
||||
*
|
||||
* @returns Build pipeline list | 构建管线列表
|
||||
*/
|
||||
getAll(): IBuildPipeline[] {
|
||||
return Array.from(this._pipelines.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if platform is registered.
|
||||
* 检查平台是否已注册。
|
||||
*
|
||||
* @param platform - Target platform | 目标平台
|
||||
* @returns Whether registered | 是否已注册
|
||||
*/
|
||||
has(platform: BuildPlatform): boolean {
|
||||
return this._pipelines.has(platform);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get available build platforms.
|
||||
* 获取可用的构建平台。
|
||||
*
|
||||
* Checks availability of each registered platform.
|
||||
* 检查每个已注册平台的可用性。
|
||||
*
|
||||
* @returns Available platforms and their status | 可用平台及其状态
|
||||
*/
|
||||
async getAvailablePlatforms(): Promise<Array<{
|
||||
platform: BuildPlatform;
|
||||
displayName: string;
|
||||
description?: string;
|
||||
available: boolean;
|
||||
reason?: string;
|
||||
}>> {
|
||||
const results = [];
|
||||
|
||||
for (const pipeline of this._pipelines.values()) {
|
||||
const availability = await pipeline.checkAvailability();
|
||||
results.push({
|
||||
platform: pipeline.platform,
|
||||
displayName: pipeline.displayName,
|
||||
description: pipeline.description,
|
||||
available: availability.available,
|
||||
reason: availability.reason
|
||||
});
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute build.
|
||||
* 执行构建。
|
||||
*
|
||||
* @param config - Build configuration | 构建配置
|
||||
* @param onProgress - Progress callback | 进度回调
|
||||
* @returns Build result | 构建结果
|
||||
*/
|
||||
async build(
|
||||
config: BuildConfig,
|
||||
onProgress?: (progress: BuildProgress) => void
|
||||
): Promise<BuildResult> {
|
||||
// Check if there's an ongoing build | 检查是否有正在进行的构建
|
||||
if (this._currentTask) {
|
||||
throw new Error('A build task is already in progress | 已有构建任务正在进行中');
|
||||
}
|
||||
|
||||
// Get build pipeline | 获取构建管线
|
||||
const pipeline = this._pipelines.get(config.platform);
|
||||
if (!pipeline) {
|
||||
throw new Error(`Pipeline not found for platform ${config.platform} | 未找到平台 ${config.platform} 的构建管线`);
|
||||
}
|
||||
|
||||
// Validate configuration | 验证配置
|
||||
const errors = pipeline.validateConfig(config);
|
||||
if (errors.length > 0) {
|
||||
throw new Error(`Invalid build configuration | 构建配置无效:\n${errors.join('\n')}`);
|
||||
}
|
||||
|
||||
// Create build task | 创建构建任务
|
||||
const abortController = new AbortController();
|
||||
const task: BuildTask = {
|
||||
id: this._generateTaskId(),
|
||||
platform: config.platform,
|
||||
config,
|
||||
progress: {
|
||||
status: 'preparing' as BuildStatus,
|
||||
message: 'Preparing build... | 准备构建...',
|
||||
progress: 0,
|
||||
currentStep: 0,
|
||||
totalSteps: 0,
|
||||
warnings: []
|
||||
},
|
||||
startTime: new Date(),
|
||||
abortController
|
||||
};
|
||||
|
||||
this._currentTask = task;
|
||||
|
||||
try {
|
||||
// Execute build | 执行构建
|
||||
const result = await pipeline.build(
|
||||
config,
|
||||
(progress) => {
|
||||
task.progress = progress;
|
||||
onProgress?.(progress);
|
||||
},
|
||||
abortController.signal
|
||||
);
|
||||
|
||||
// Update task status | 更新任务状态
|
||||
task.endTime = new Date();
|
||||
task.progress.status = result.success ? BuildStatus.Completed : BuildStatus.Failed;
|
||||
|
||||
// Add to history | 添加到历史
|
||||
this._addToHistory(task);
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
// Handle error | 处理错误
|
||||
task.endTime = new Date();
|
||||
task.progress.status = BuildStatus.Failed;
|
||||
task.progress.error = error instanceof Error ? error.message : String(error);
|
||||
|
||||
this._addToHistory(task);
|
||||
|
||||
return {
|
||||
success: false,
|
||||
platform: config.platform,
|
||||
outputPath: config.outputPath,
|
||||
duration: task.endTime.getTime() - task.startTime.getTime(),
|
||||
outputFiles: [],
|
||||
warnings: task.progress.warnings,
|
||||
error: task.progress.error
|
||||
};
|
||||
} finally {
|
||||
this._currentTask = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel current build.
|
||||
* 取消当前构建。
|
||||
*/
|
||||
cancelBuild(): void {
|
||||
if (this._currentTask) {
|
||||
this._currentTask.abortController.abort();
|
||||
this._currentTask.progress.status = BuildStatus.Cancelled;
|
||||
console.log('[BuildService] Build cancelled | 构建已取消');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current build task.
|
||||
* 获取当前构建任务。
|
||||
*
|
||||
* @returns Current task, or null if none | 当前任务,如果没有则返回 null
|
||||
*/
|
||||
getCurrentTask(): BuildTask | null {
|
||||
return this._currentTask;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get build history.
|
||||
* 获取构建历史。
|
||||
*
|
||||
* @returns History task list (newest first) | 历史任务列表(最新的在前)
|
||||
*/
|
||||
getHistory(): BuildTask[] {
|
||||
return [...this._taskHistory];
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear build history.
|
||||
* 清除构建历史。
|
||||
*/
|
||||
clearHistory(): void {
|
||||
this._taskHistory = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate task ID.
|
||||
* 生成任务 ID。
|
||||
*/
|
||||
private _generateTaskId(): string {
|
||||
return `build-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add task to history.
|
||||
* 添加任务到历史。
|
||||
*/
|
||||
private _addToHistory(task: BuildTask): void {
|
||||
this._taskHistory.unshift(task);
|
||||
if (this._taskHistory.length > this._maxHistorySize) {
|
||||
this._taskHistory.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
319
packages/editor-core/src/Services/Build/IBuildPipeline.ts
Normal file
319
packages/editor-core/src/Services/Build/IBuildPipeline.ts
Normal file
@@ -0,0 +1,319 @@
|
||||
/**
|
||||
* Build Pipeline Interface.
|
||||
* 构建管线接口。
|
||||
*
|
||||
* Defines the common process and configuration for platform builds.
|
||||
* 定义平台构建的通用流程和配置。
|
||||
*/
|
||||
|
||||
/**
|
||||
* Build target platform.
|
||||
* 构建目标平台。
|
||||
*/
|
||||
export enum BuildPlatform {
|
||||
/** Web/H5 browser | Web/H5 浏览器 */
|
||||
Web = 'web',
|
||||
/** WeChat MiniGame | 微信小游戏 */
|
||||
WeChatMiniGame = 'wechat-minigame',
|
||||
/** ByteDance MiniGame | 字节跳动小游戏 */
|
||||
ByteDanceMiniGame = 'bytedance-minigame',
|
||||
/** Alipay MiniGame | 支付宝小游戏 */
|
||||
AlipayMiniGame = 'alipay-minigame',
|
||||
/** Desktop application (Tauri) | 桌面应用 (Tauri) */
|
||||
Desktop = 'desktop',
|
||||
/** Android | Android */
|
||||
Android = 'android',
|
||||
/** iOS | iOS */
|
||||
iOS = 'ios'
|
||||
}
|
||||
|
||||
/**
|
||||
* Build status.
|
||||
* 构建状态。
|
||||
*/
|
||||
export enum BuildStatus {
|
||||
/** Idle | 空闲 */
|
||||
Idle = 'idle',
|
||||
/** Preparing | 准备中 */
|
||||
Preparing = 'preparing',
|
||||
/** Compiling | 编译中 */
|
||||
Compiling = 'compiling',
|
||||
/** Packaging assets | 打包资源 */
|
||||
Packaging = 'packaging',
|
||||
/** Copying files | 复制文件 */
|
||||
Copying = 'copying',
|
||||
/** Post-processing | 后处理 */
|
||||
PostProcessing = 'post-processing',
|
||||
/** Completed | 完成 */
|
||||
Completed = 'completed',
|
||||
/** Failed | 失败 */
|
||||
Failed = 'failed',
|
||||
/** Cancelled | 已取消 */
|
||||
Cancelled = 'cancelled'
|
||||
}
|
||||
|
||||
/**
|
||||
* Build progress information.
|
||||
* 构建进度信息。
|
||||
*/
|
||||
export interface BuildProgress {
|
||||
/** Current status | 当前状态 */
|
||||
status: BuildStatus;
|
||||
/** Current step description | 当前步骤描述 */
|
||||
message: string;
|
||||
/** Overall progress (0-100) | 总体进度 (0-100) */
|
||||
progress: number;
|
||||
/** Current step index | 当前步骤索引 */
|
||||
currentStep: number;
|
||||
/** Total step count | 总步骤数 */
|
||||
totalSteps: number;
|
||||
/** Warning list | 警告列表 */
|
||||
warnings: string[];
|
||||
/** Error message (if failed) | 错误信息(如果失败) */
|
||||
error?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build configuration base class.
|
||||
* 构建配置基类。
|
||||
*/
|
||||
export interface BuildConfig {
|
||||
/** Target platform | 目标平台 */
|
||||
platform: BuildPlatform;
|
||||
/** Output directory | 输出目录 */
|
||||
outputPath: string;
|
||||
/** Whether release build (compression, optimization) | 是否为发布构建(压缩、优化) */
|
||||
isRelease: boolean;
|
||||
/** Whether to generate source map | 是否生成 source map */
|
||||
sourceMap: boolean;
|
||||
/** Scene list to include (empty means all) | 要包含的场景列表(空表示全部) */
|
||||
scenes?: string[];
|
||||
/** Plugin list to include (empty means all enabled) | 要包含的插件列表(空表示全部启用的) */
|
||||
plugins?: string[];
|
||||
/**
|
||||
* Enabled module IDs (whitelist approach).
|
||||
* 启用的模块 ID 列表(白名单方式)。
|
||||
* If set, only these modules will be included.
|
||||
* 如果设置,只会包含这些模块。
|
||||
*/
|
||||
enabledModules?: string[];
|
||||
/**
|
||||
* Disabled module IDs (blacklist approach).
|
||||
* 禁用的模块 ID 列表(黑名单方式)。
|
||||
* If set, all modules EXCEPT these will be included.
|
||||
* 如果设置,会包含除了这些之外的所有模块。
|
||||
* Takes precedence over enabledModules.
|
||||
* 优先于 enabledModules。
|
||||
*/
|
||||
disabledModules?: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Web platform build configuration.
|
||||
* Web 平台构建配置。
|
||||
*/
|
||||
export interface WebBuildConfig extends BuildConfig {
|
||||
platform: BuildPlatform.Web;
|
||||
/** Output format | 输出格式 */
|
||||
format: 'iife' | 'esm';
|
||||
/**
|
||||
* Whether to bundle all modules into a single JS file.
|
||||
* 是否将所有模块打包成单个 JS 文件。
|
||||
* - true: Bundle into one runtime.browser.js (smaller total size, single request)
|
||||
* - false: Keep modules separate (better caching, parallel loading)
|
||||
* Default: true
|
||||
*/
|
||||
bundleModules: boolean;
|
||||
/** Whether to generate HTML file | 是否生成 HTML 文件 */
|
||||
generateHtml: boolean;
|
||||
/** HTML template path | HTML 模板路径 */
|
||||
htmlTemplate?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* WeChat MiniGame build configuration.
|
||||
* 微信小游戏构建配置。
|
||||
*/
|
||||
export interface WeChatBuildConfig extends BuildConfig {
|
||||
platform: BuildPlatform.WeChatMiniGame;
|
||||
/** AppID | AppID */
|
||||
appId: string;
|
||||
/** Whether to use subpackages | 是否分包 */
|
||||
useSubpackages: boolean;
|
||||
/** Main package size limit (KB) | 主包大小限制 (KB) */
|
||||
mainPackageLimit: number;
|
||||
/** Whether to enable plugins | 是否启用插件 */
|
||||
usePlugins: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build result.
|
||||
* 构建结果。
|
||||
*/
|
||||
export interface BuildResult {
|
||||
/** Whether successful | 是否成功 */
|
||||
success: boolean;
|
||||
/** Target platform | 目标平台 */
|
||||
platform: BuildPlatform;
|
||||
/** Output directory | 输出目录 */
|
||||
outputPath: string;
|
||||
/** Build duration (milliseconds) | 构建耗时(毫秒) */
|
||||
duration: number;
|
||||
/** Output file list | 输出文件列表 */
|
||||
outputFiles: string[];
|
||||
/** Warning list | 警告列表 */
|
||||
warnings: string[];
|
||||
/** Error message (if failed) | 错误信息(如果失败) */
|
||||
error?: string;
|
||||
/** Build statistics | 构建统计 */
|
||||
stats?: {
|
||||
/** Total file size (bytes) | 总文件大小 (bytes) */
|
||||
totalSize: number;
|
||||
/** JS file size | JS 文件大小 */
|
||||
jsSize: number;
|
||||
/** WASM file size | WASM 文件大小 */
|
||||
wasmSize: number;
|
||||
/** Asset file size | 资源文件大小 */
|
||||
assetsSize: number;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Build step.
|
||||
* 构建步骤。
|
||||
*/
|
||||
export interface BuildStep {
|
||||
/** Step ID | 步骤 ID */
|
||||
id: string;
|
||||
/** Step name | 步骤名称 */
|
||||
name: string;
|
||||
/** Execute function | 执行函数 */
|
||||
execute: (context: BuildContext) => Promise<void>;
|
||||
/** Whether skippable | 是否可跳过 */
|
||||
optional?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build context.
|
||||
* 构建上下文。
|
||||
*
|
||||
* Shared state during the build process.
|
||||
* 在构建过程中共享的状态。
|
||||
*/
|
||||
export interface BuildContext {
|
||||
/** Build configuration | 构建配置 */
|
||||
config: BuildConfig;
|
||||
/** Project root directory | 项目根目录 */
|
||||
projectRoot: string;
|
||||
/** Temporary directory | 临时目录 */
|
||||
tempDir: string;
|
||||
/** Output directory | 输出目录 */
|
||||
outputDir: string;
|
||||
/** Progress report callback | 进度报告回调 */
|
||||
reportProgress: (message: string, progress?: number) => void;
|
||||
/** Add warning | 添加警告 */
|
||||
addWarning: (warning: string) => void;
|
||||
/** Abort signal | 中止信号 */
|
||||
abortSignal: AbortSignal;
|
||||
/** Shared data (passed between steps) | 共享数据(步骤间传递) */
|
||||
data: Map<string, any>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build pipeline interface.
|
||||
* 构建管线接口。
|
||||
*
|
||||
* Each platform implements its own build pipeline.
|
||||
* 每个平台实现自己的构建管线。
|
||||
*/
|
||||
export interface IBuildPipeline {
|
||||
/** Platform identifier | 平台标识 */
|
||||
readonly platform: BuildPlatform;
|
||||
|
||||
/** Platform display name | 平台显示名称 */
|
||||
readonly displayName: string;
|
||||
|
||||
/** Platform icon | 平台图标 */
|
||||
readonly icon?: string;
|
||||
|
||||
/** Platform description | 平台描述 */
|
||||
readonly description?: string;
|
||||
|
||||
/**
|
||||
* Get default configuration.
|
||||
* 获取默认配置。
|
||||
*/
|
||||
getDefaultConfig(): BuildConfig;
|
||||
|
||||
/**
|
||||
* Validate configuration.
|
||||
* 验证配置是否有效。
|
||||
*
|
||||
* @param config - Build configuration | 构建配置
|
||||
* @returns Validation error list (empty means valid) | 验证错误列表(空表示有效)
|
||||
*/
|
||||
validateConfig(config: BuildConfig): string[];
|
||||
|
||||
/**
|
||||
* Get build steps.
|
||||
* 获取构建步骤。
|
||||
*
|
||||
* @param config - Build configuration | 构建配置
|
||||
* @returns Build step list | 构建步骤列表
|
||||
*/
|
||||
getSteps(config: BuildConfig): BuildStep[];
|
||||
|
||||
/**
|
||||
* Execute build.
|
||||
* 执行构建。
|
||||
*
|
||||
* @param config - Build configuration | 构建配置
|
||||
* @param onProgress - Progress callback | 进度回调
|
||||
* @param abortSignal - Abort signal | 中止信号
|
||||
* @returns Build result | 构建结果
|
||||
*/
|
||||
build(
|
||||
config: BuildConfig,
|
||||
onProgress?: (progress: BuildProgress) => void,
|
||||
abortSignal?: AbortSignal
|
||||
): Promise<BuildResult>;
|
||||
|
||||
/**
|
||||
* Check platform availability.
|
||||
* 检查平台是否可用。
|
||||
*
|
||||
* For example, check if necessary tools are installed.
|
||||
* 例如检查必要的工具是否安装。
|
||||
*/
|
||||
checkAvailability(): Promise<{ available: boolean; reason?: string }>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build pipeline registry interface.
|
||||
* 构建管线注册表接口。
|
||||
*/
|
||||
export interface IBuildPipelineRegistry {
|
||||
/**
|
||||
* Register build pipeline.
|
||||
* 注册构建管线。
|
||||
*/
|
||||
register(pipeline: IBuildPipeline): void;
|
||||
|
||||
/**
|
||||
* Get build pipeline.
|
||||
* 获取构建管线。
|
||||
*/
|
||||
get(platform: BuildPlatform): IBuildPipeline | undefined;
|
||||
|
||||
/**
|
||||
* Get all registered pipelines.
|
||||
* 获取所有已注册的管线。
|
||||
*/
|
||||
getAll(): IBuildPipeline[];
|
||||
|
||||
/**
|
||||
* Check if platform is registered.
|
||||
* 检查平台是否已注册。
|
||||
*/
|
||||
has(platform: BuildPlatform): boolean;
|
||||
}
|
||||
26
packages/editor-core/src/Services/Build/index.ts
Normal file
26
packages/editor-core/src/Services/Build/index.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Build System.
|
||||
* 构建系统。
|
||||
*
|
||||
* Provides cross-platform project build capabilities.
|
||||
* 提供跨平台的项目构建能力。
|
||||
*/
|
||||
|
||||
export {
|
||||
BuildPlatform,
|
||||
BuildStatus,
|
||||
type BuildProgress,
|
||||
type BuildConfig,
|
||||
type WebBuildConfig,
|
||||
type WeChatBuildConfig,
|
||||
type BuildResult,
|
||||
type BuildStep,
|
||||
type BuildContext,
|
||||
type IBuildPipeline,
|
||||
type IBuildPipelineRegistry
|
||||
} from './IBuildPipeline';
|
||||
|
||||
export { BuildService, type BuildTask } from './BuildService';
|
||||
|
||||
// Build pipelines | 构建管线
|
||||
export { WebBuildPipeline, WeChatBuildPipeline, type IBuildFileSystem } from './pipelines';
|
||||
@@ -0,0 +1,730 @@
|
||||
/**
|
||||
* WeChat MiniGame Build Pipeline.
|
||||
* 微信小游戏构建管线。
|
||||
*
|
||||
* Packages the project as a format that can run on WeChat MiniGame platform.
|
||||
* 将项目打包为可在微信小游戏平台运行的格式。
|
||||
*/
|
||||
|
||||
import type {
|
||||
IBuildPipeline,
|
||||
BuildConfig,
|
||||
BuildResult,
|
||||
BuildProgress,
|
||||
BuildStep,
|
||||
BuildContext,
|
||||
WeChatBuildConfig
|
||||
} from '../IBuildPipeline';
|
||||
import { BuildPlatform, BuildStatus } from '../IBuildPipeline';
|
||||
import type { IBuildFileSystem } from './WebBuildPipeline';
|
||||
|
||||
/**
|
||||
* WASM file configuration to be copied.
|
||||
* 需要复制的 WASM 文件配置。
|
||||
*/
|
||||
interface WasmFileConfig {
|
||||
/** Source file path (relative to node_modules) | 源文件路径(相对于 node_modules) */
|
||||
source: string;
|
||||
/** Target file path (relative to output directory) | 目标文件路径(相对于输出目录) */
|
||||
target: string;
|
||||
/** Description | 描述 */
|
||||
description: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* WeChat MiniGame Build Pipeline.
|
||||
* 微信小游戏构建管线。
|
||||
*
|
||||
* Build steps:
|
||||
* 构建步骤:
|
||||
* 1. Prepare output directory | 准备输出目录
|
||||
* 2. Compile TypeScript | 编译 TypeScript
|
||||
* 3. Bundle runtime (using WeChat adapter) | 打包运行时(使用微信适配器)
|
||||
* 4. Copy WASM files | 复制 WASM 文件
|
||||
* 5. Copy asset files | 复制资源文件
|
||||
* 6. Generate game.json | 生成 game.json
|
||||
* 7. Generate game.js | 生成 game.js
|
||||
* 8. Post-process | 后处理
|
||||
*/
|
||||
export class WeChatBuildPipeline implements IBuildPipeline {
|
||||
readonly platform = BuildPlatform.WeChatMiniGame;
|
||||
readonly displayName = 'WeChat MiniGame | 微信小游戏';
|
||||
readonly description = 'Build as a format that can run on WeChat MiniGame platform | 构建为可在微信小游戏平台运行的格式';
|
||||
readonly icon = 'message-circle';
|
||||
|
||||
private _fileSystem: IBuildFileSystem | null = null;
|
||||
|
||||
/**
|
||||
* WASM file list to be copied.
|
||||
* 需要复制的 WASM 文件列表。
|
||||
*/
|
||||
private readonly _wasmFiles: WasmFileConfig[] = [
|
||||
{
|
||||
source: '@dimforge/rapier2d/rapier_wasm2d_bg.wasm',
|
||||
target: 'wasm/rapier2d_bg.wasm',
|
||||
description: 'Rapier2D Physics Engine | Rapier2D 物理引擎'
|
||||
}
|
||||
// More WASM files can be added here | 可以在这里添加更多 WASM 文件
|
||||
];
|
||||
|
||||
/**
|
||||
* Set build file system service.
|
||||
* 设置构建文件系统服务。
|
||||
*
|
||||
* @param fileSystem - Build file system service | 构建文件系统服务
|
||||
*/
|
||||
setFileSystem(fileSystem: IBuildFileSystem): void {
|
||||
this._fileSystem = fileSystem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get default configuration.
|
||||
* 获取默认配置。
|
||||
*/
|
||||
getDefaultConfig(): WeChatBuildConfig {
|
||||
return {
|
||||
platform: BuildPlatform.WeChatMiniGame,
|
||||
outputPath: './dist/wechat',
|
||||
isRelease: true,
|
||||
sourceMap: false,
|
||||
appId: '',
|
||||
useSubpackages: false,
|
||||
mainPackageLimit: 4096, // 4MB
|
||||
usePlugins: false
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate configuration.
|
||||
* 验证配置。
|
||||
*
|
||||
* @param config - Build configuration | 构建配置
|
||||
* @returns Validation error list | 验证错误列表
|
||||
*/
|
||||
validateConfig(config: BuildConfig): string[] {
|
||||
const errors: string[] = [];
|
||||
const wxConfig = config as WeChatBuildConfig;
|
||||
|
||||
if (!wxConfig.outputPath) {
|
||||
errors.push('Output path cannot be empty | 输出路径不能为空');
|
||||
}
|
||||
|
||||
if (!wxConfig.appId) {
|
||||
errors.push('AppID cannot be empty | AppID 不能为空');
|
||||
} else if (!/^wx[a-f0-9]{16}$/.test(wxConfig.appId)) {
|
||||
errors.push('AppID format is incorrect (should be 18 characters starting with wx) | AppID 格式不正确(应为 wx 开头的18位字符)');
|
||||
}
|
||||
|
||||
if (wxConfig.mainPackageLimit < 1024) {
|
||||
errors.push('Main package size limit cannot be less than 1MB | 主包大小限制不能小于 1MB');
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get build steps.
|
||||
* 获取构建步骤。
|
||||
*
|
||||
* @param config - Build configuration | 构建配置
|
||||
* @returns Build step list | 构建步骤列表
|
||||
*/
|
||||
getSteps(config: BuildConfig): BuildStep[] {
|
||||
const wxConfig = config as WeChatBuildConfig;
|
||||
|
||||
const steps: BuildStep[] = [
|
||||
{
|
||||
id: 'prepare',
|
||||
name: 'Prepare output directory | 准备输出目录',
|
||||
execute: this._prepareOutputDir.bind(this)
|
||||
},
|
||||
{
|
||||
id: 'compile',
|
||||
name: 'Compile TypeScript | 编译 TypeScript',
|
||||
execute: this._compileTypeScript.bind(this)
|
||||
},
|
||||
{
|
||||
id: 'bundle-runtime',
|
||||
name: 'Bundle runtime | 打包运行时',
|
||||
execute: this._bundleRuntime.bind(this)
|
||||
},
|
||||
{
|
||||
id: 'copy-wasm',
|
||||
name: 'Copy WASM files | 复制 WASM 文件',
|
||||
execute: this._copyWasmFiles.bind(this)
|
||||
},
|
||||
{
|
||||
id: 'copy-assets',
|
||||
name: 'Copy asset files | 复制资源文件',
|
||||
execute: this._copyAssets.bind(this)
|
||||
},
|
||||
{
|
||||
id: 'generate-game-json',
|
||||
name: 'Generate game.json | 生成 game.json',
|
||||
execute: this._generateGameJson.bind(this)
|
||||
},
|
||||
{
|
||||
id: 'generate-game-js',
|
||||
name: 'Generate game.js | 生成 game.js',
|
||||
execute: this._generateGameJs.bind(this)
|
||||
},
|
||||
{
|
||||
id: 'generate-project-config',
|
||||
name: 'Generate project.config.json | 生成 project.config.json',
|
||||
execute: this._generateProjectConfig.bind(this)
|
||||
}
|
||||
];
|
||||
|
||||
if (wxConfig.useSubpackages) {
|
||||
steps.push({
|
||||
id: 'split-subpackages',
|
||||
name: 'Process subpackages | 分包处理',
|
||||
execute: this._splitSubpackages.bind(this)
|
||||
});
|
||||
}
|
||||
|
||||
if (wxConfig.isRelease) {
|
||||
steps.push({
|
||||
id: 'optimize',
|
||||
name: 'Optimize and compress | 优化压缩',
|
||||
execute: this._optimize.bind(this),
|
||||
optional: true
|
||||
});
|
||||
}
|
||||
|
||||
return steps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute build.
|
||||
* 执行构建。
|
||||
*
|
||||
* @param config - Build configuration | 构建配置
|
||||
* @param onProgress - Progress callback | 进度回调
|
||||
* @param abortSignal - Abort signal | 中止信号
|
||||
* @returns Build result | 构建结果
|
||||
*/
|
||||
async build(
|
||||
config: BuildConfig,
|
||||
onProgress?: (progress: BuildProgress) => void,
|
||||
abortSignal?: AbortSignal
|
||||
): Promise<BuildResult> {
|
||||
const startTime = Date.now();
|
||||
const warnings: string[] = [];
|
||||
const outputFiles: string[] = [];
|
||||
|
||||
const steps = this.getSteps(config);
|
||||
const totalSteps = steps.length;
|
||||
|
||||
// Infer project root from output path | 从输出路径推断项目根目录
|
||||
// outputPath is typically: /path/to/project/build/wechat-minigame
|
||||
// So we go up 2 levels to get project root | 所以我们向上2级获取项目根目录
|
||||
const outputPathParts = config.outputPath.replace(/\\/g, '/').split('/');
|
||||
const buildIndex = outputPathParts.lastIndexOf('build');
|
||||
const projectRoot = buildIndex > 0
|
||||
? outputPathParts.slice(0, buildIndex).join('/')
|
||||
: '.';
|
||||
|
||||
// Create build context | 创建构建上下文
|
||||
const context: BuildContext = {
|
||||
config,
|
||||
projectRoot,
|
||||
tempDir: `${projectRoot}/temp/build-wechat`,
|
||||
outputDir: config.outputPath,
|
||||
reportProgress: (message, progress) => {
|
||||
// Handled below | 在下面处理
|
||||
},
|
||||
addWarning: (warning) => {
|
||||
warnings.push(warning);
|
||||
},
|
||||
abortSignal: abortSignal || new AbortController().signal,
|
||||
data: new Map()
|
||||
};
|
||||
|
||||
// Store file system and WASM config for subsequent steps | 存储文件系统和 WASM 配置供后续步骤使用
|
||||
context.data.set('fileSystem', this._fileSystem);
|
||||
context.data.set('wasmFiles', this._wasmFiles);
|
||||
|
||||
try {
|
||||
for (let i = 0; i < steps.length; i++) {
|
||||
const step = steps[i];
|
||||
|
||||
if (abortSignal?.aborted) {
|
||||
return {
|
||||
success: false,
|
||||
platform: config.platform,
|
||||
outputPath: config.outputPath,
|
||||
duration: Date.now() - startTime,
|
||||
outputFiles,
|
||||
warnings,
|
||||
error: 'Build cancelled | 构建已取消'
|
||||
};
|
||||
}
|
||||
|
||||
onProgress?.({
|
||||
status: this._getStatusForStep(step.id),
|
||||
message: step.name,
|
||||
progress: Math.round((i / totalSteps) * 100),
|
||||
currentStep: i + 1,
|
||||
totalSteps,
|
||||
warnings
|
||||
});
|
||||
|
||||
await step.execute(context);
|
||||
}
|
||||
|
||||
// Get output stats | 获取输出统计
|
||||
let stats: BuildResult['stats'] | undefined;
|
||||
if (this._fileSystem) {
|
||||
try {
|
||||
const totalSize = await this._fileSystem.getDirectorySize(config.outputPath);
|
||||
stats = {
|
||||
totalSize,
|
||||
jsSize: 0,
|
||||
wasmSize: 0,
|
||||
assetsSize: 0
|
||||
};
|
||||
} catch {
|
||||
// Ignore stats error | 忽略统计错误
|
||||
}
|
||||
}
|
||||
|
||||
onProgress?.({
|
||||
status: BuildStatus.Completed,
|
||||
message: 'Build completed | 构建完成',
|
||||
progress: 100,
|
||||
currentStep: totalSteps,
|
||||
totalSteps,
|
||||
warnings
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
platform: config.platform,
|
||||
outputPath: config.outputPath,
|
||||
duration: Date.now() - startTime,
|
||||
outputFiles,
|
||||
warnings,
|
||||
stats
|
||||
};
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
|
||||
onProgress?.({
|
||||
status: BuildStatus.Failed,
|
||||
message: 'Build failed | 构建失败',
|
||||
progress: 0,
|
||||
currentStep: 0,
|
||||
totalSteps,
|
||||
warnings,
|
||||
error: errorMessage
|
||||
});
|
||||
|
||||
return {
|
||||
success: false,
|
||||
platform: config.platform,
|
||||
outputPath: config.outputPath,
|
||||
duration: Date.now() - startTime,
|
||||
outputFiles,
|
||||
warnings,
|
||||
error: errorMessage
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check availability.
|
||||
* 检查可用性。
|
||||
*/
|
||||
async checkAvailability(): Promise<{ available: boolean; reason?: string }> {
|
||||
// TODO: Check if WeChat DevTools is installed | 检查微信开发者工具是否安装
|
||||
return { available: true };
|
||||
}
|
||||
|
||||
/**
|
||||
* Get build status for step.
|
||||
* 获取步骤的构建状态。
|
||||
*/
|
||||
private _getStatusForStep(stepId: string): BuildStatus {
|
||||
switch (stepId) {
|
||||
case 'prepare':
|
||||
return BuildStatus.Preparing;
|
||||
case 'compile':
|
||||
case 'bundle-runtime':
|
||||
return BuildStatus.Compiling;
|
||||
case 'copy-wasm':
|
||||
case 'copy-assets':
|
||||
return BuildStatus.Copying;
|
||||
case 'generate-game-json':
|
||||
case 'generate-game-js':
|
||||
case 'generate-project-config':
|
||||
case 'split-subpackages':
|
||||
case 'optimize':
|
||||
return BuildStatus.PostProcessing;
|
||||
default:
|
||||
return BuildStatus.Compiling;
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== Build Step Implementations | 构建步骤实现 ====================
|
||||
|
||||
/**
|
||||
* Prepare output directory.
|
||||
* 准备输出目录。
|
||||
*/
|
||||
private async _prepareOutputDir(context: BuildContext): Promise<void> {
|
||||
const fs = context.data.get('fileSystem') as IBuildFileSystem | undefined;
|
||||
|
||||
if (fs) {
|
||||
await fs.prepareBuildDirectory(context.outputDir);
|
||||
console.log('[WeChatBuild] Prepared output directory | 准备输出目录:', context.outputDir);
|
||||
} else {
|
||||
console.warn('[WeChatBuild] No file system service, skipping | 无文件系统服务,跳过');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile TypeScript.
|
||||
* 编译 TypeScript。
|
||||
*/
|
||||
private async _compileTypeScript(context: BuildContext): Promise<void> {
|
||||
const fs = context.data.get('fileSystem') as IBuildFileSystem | undefined;
|
||||
const wxConfig = context.config as WeChatBuildConfig;
|
||||
|
||||
if (!fs) {
|
||||
console.warn('[WeChatBuild] No file system service, skipping | 无文件系统服务,跳过');
|
||||
return;
|
||||
}
|
||||
|
||||
// Find user script entry point | 查找用户脚本入口点
|
||||
const scriptsDir = `${context.projectRoot}/scripts`;
|
||||
const hasScripts = await fs.pathExists(scriptsDir);
|
||||
|
||||
if (!hasScripts) {
|
||||
console.log('[WeChatBuild] No scripts directory found | 未找到脚本目录');
|
||||
return;
|
||||
}
|
||||
|
||||
// Find entry file | 查找入口文件
|
||||
const possibleEntries = ['index.ts', 'main.ts', 'game.ts', 'index.js', 'main.js'];
|
||||
let entryFile: string | null = null;
|
||||
|
||||
for (const entry of possibleEntries) {
|
||||
const entryPath = `${scriptsDir}/${entry}`;
|
||||
if (await fs.pathExists(entryPath)) {
|
||||
entryFile = entryPath;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!entryFile) {
|
||||
console.log('[WeChatBuild] No entry file found | 未找到入口文件');
|
||||
return;
|
||||
}
|
||||
|
||||
// Bundle user scripts for WeChat | 为微信打包用户脚本
|
||||
const result = await fs.bundleScripts({
|
||||
entryPoints: [entryFile],
|
||||
outputDir: `${context.outputDir}/libs`,
|
||||
format: 'iife', // WeChat uses iife format | 微信使用 iife 格式
|
||||
bundleName: 'user-code',
|
||||
minify: wxConfig.isRelease,
|
||||
sourceMap: wxConfig.sourceMap,
|
||||
external: ['@esengine/ecs-framework', '@esengine/core'],
|
||||
projectRoot: context.projectRoot,
|
||||
define: {
|
||||
'process.env.NODE_ENV': wxConfig.isRelease ? '"production"' : '"development"',
|
||||
'wx': 'wx' // WeChat global | 微信全局对象
|
||||
}
|
||||
});
|
||||
|
||||
if (!result.success) {
|
||||
throw new Error(`User code compilation failed | 用户代码编译失败: ${result.error}`);
|
||||
}
|
||||
|
||||
result.warnings.forEach(w => context.addWarning(w));
|
||||
console.log('[WeChatBuild] Compiled TypeScript | 编译 TypeScript:', result.outputFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bundle runtime.
|
||||
* 打包运行时。
|
||||
*/
|
||||
private async _bundleRuntime(context: BuildContext): Promise<void> {
|
||||
const fs = context.data.get('fileSystem') as IBuildFileSystem | undefined;
|
||||
|
||||
if (!fs) {
|
||||
console.warn('[WeChatBuild] No file system service, skipping | 无文件系统服务,跳过');
|
||||
return;
|
||||
}
|
||||
|
||||
// Copy pre-built runtime files with WeChat adapter | 复制带微信适配器的预构建运行时文件
|
||||
const runtimeSrc = `${context.projectRoot}/node_modules/@esengine/platform-wechat/dist`;
|
||||
const runtimeDst = `${context.outputDir}/libs`;
|
||||
|
||||
const hasWxRuntime = await fs.pathExists(runtimeSrc);
|
||||
if (hasWxRuntime) {
|
||||
const count = await fs.copyDirectory(runtimeSrc, runtimeDst, ['*.js']);
|
||||
console.log(`[WeChatBuild] Copied WeChat runtime | 复制微信运行时: ${count} files`);
|
||||
} else {
|
||||
// Fallback to standard runtime | 回退到标准运行时
|
||||
const stdRuntimeSrc = `${context.projectRoot}/node_modules/@esengine/ecs-framework/dist`;
|
||||
const hasStdRuntime = await fs.pathExists(stdRuntimeSrc);
|
||||
if (hasStdRuntime) {
|
||||
const count = await fs.copyDirectory(stdRuntimeSrc, runtimeDst, ['*.js']);
|
||||
console.log(`[WeChatBuild] Copied standard runtime | 复制标准运行时: ${count} files`);
|
||||
context.addWarning('Using standard runtime, some WeChat-specific features may not work | 使用标准运行时,部分微信特有功能可能不可用');
|
||||
} else {
|
||||
context.addWarning('Runtime not found | 未找到运行时');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy WASM files.
|
||||
* 复制 WASM 文件。
|
||||
*
|
||||
* This is a critical step for WeChat MiniGame build.
|
||||
* 这是微信小游戏构建的关键步骤。
|
||||
*/
|
||||
private async _copyWasmFiles(context: BuildContext): Promise<void> {
|
||||
const fs = context.data.get('fileSystem') as IBuildFileSystem | undefined;
|
||||
const wasmFiles = context.data.get('wasmFiles') as WasmFileConfig[];
|
||||
|
||||
if (!fs) {
|
||||
console.warn('[WeChatBuild] No file system service, skipping | 无文件系统服务,跳过');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('[WeChatBuild] Copying WASM files | 复制 WASM 文件:');
|
||||
|
||||
for (const file of wasmFiles) {
|
||||
const sourcePath = `${context.projectRoot}/node_modules/${file.source}`;
|
||||
const targetPath = `${context.outputDir}/${file.target}`;
|
||||
|
||||
const exists = await fs.pathExists(sourcePath);
|
||||
if (exists) {
|
||||
await fs.copyFile(sourcePath, targetPath);
|
||||
console.log(` - ${file.description}: ${file.source} -> ${file.target}`);
|
||||
} else {
|
||||
context.addWarning(`WASM file not found | 未找到 WASM 文件: ${file.source}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Copy engine WASM | 复制引擎 WASM
|
||||
const engineWasmSrc = `${context.projectRoot}/node_modules/@esengine/es-engine/pkg`;
|
||||
const hasEngineWasm = await fs.pathExists(engineWasmSrc);
|
||||
if (hasEngineWasm) {
|
||||
const count = await fs.copyDirectory(engineWasmSrc, `${context.outputDir}/wasm`, ['*.wasm']);
|
||||
console.log(`[WeChatBuild] Copied engine WASM | 复制引擎 WASM: ${count} files`);
|
||||
}
|
||||
|
||||
context.addWarning(
|
||||
'iOS WeChat requires WXWebAssembly for loading WASM | iOS 微信需要使用 WXWebAssembly 加载 WASM'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy asset files.
|
||||
* 复制资源文件。
|
||||
*/
|
||||
private async _copyAssets(context: BuildContext): Promise<void> {
|
||||
const fs = context.data.get('fileSystem') as IBuildFileSystem | undefined;
|
||||
|
||||
if (!fs) {
|
||||
console.warn('[WeChatBuild] No file system service, skipping | 无文件系统服务,跳过');
|
||||
return;
|
||||
}
|
||||
|
||||
// Copy scenes | 复制场景
|
||||
const scenesDir = `${context.projectRoot}/scenes`;
|
||||
if (await fs.pathExists(scenesDir)) {
|
||||
const count = await fs.copyDirectory(scenesDir, `${context.outputDir}/scenes`);
|
||||
console.log(`[WeChatBuild] Copied scenes | 复制场景: ${count} files`);
|
||||
}
|
||||
|
||||
// Copy assets | 复制资源
|
||||
const assetsDir = `${context.projectRoot}/assets`;
|
||||
if (await fs.pathExists(assetsDir)) {
|
||||
const count = await fs.copyDirectory(assetsDir, `${context.outputDir}/assets`);
|
||||
console.log(`[WeChatBuild] Copied assets | 复制资源: ${count} files`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate game.json.
|
||||
* 生成 game.json。
|
||||
*/
|
||||
private async _generateGameJson(context: BuildContext): Promise<void> {
|
||||
const fs = context.data.get('fileSystem') as IBuildFileSystem | undefined;
|
||||
const wxConfig = context.config as WeChatBuildConfig;
|
||||
|
||||
if (!fs) {
|
||||
console.warn('[WeChatBuild] No file system service, skipping | 无文件系统服务,跳过');
|
||||
return;
|
||||
}
|
||||
|
||||
const gameJson: Record<string, unknown> = {
|
||||
deviceOrientation: 'portrait',
|
||||
showStatusBar: false,
|
||||
networkTimeout: {
|
||||
request: 60000,
|
||||
connectSocket: 60000,
|
||||
uploadFile: 60000,
|
||||
downloadFile: 60000
|
||||
},
|
||||
// Declare WebAssembly usage | 声明使用 WebAssembly
|
||||
enableWebAssembly: true
|
||||
};
|
||||
|
||||
if (wxConfig.useSubpackages) {
|
||||
gameJson.subpackages = [];
|
||||
}
|
||||
|
||||
if (wxConfig.usePlugins) {
|
||||
gameJson.plugins = {};
|
||||
}
|
||||
|
||||
const jsonContent = JSON.stringify(gameJson, null, 2);
|
||||
await fs.writeJsonFile(`${context.outputDir}/game.json`, jsonContent);
|
||||
console.log('[WeChatBuild] Generated game.json | 生成 game.json');
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate game.js.
|
||||
* 生成 game.js。
|
||||
*/
|
||||
private async _generateGameJs(context: BuildContext): Promise<void> {
|
||||
const fs = context.data.get('fileSystem') as IBuildFileSystem | undefined;
|
||||
|
||||
if (!fs) {
|
||||
console.warn('[WeChatBuild] No file system service, skipping | 无文件系统服务,跳过');
|
||||
return;
|
||||
}
|
||||
|
||||
const gameJs = `/**
|
||||
* WeChat MiniGame entry point.
|
||||
* 微信小游戏入口。
|
||||
*
|
||||
* Auto-generated, do not modify manually.
|
||||
* 自动生成,请勿手动修改。
|
||||
*/
|
||||
|
||||
// WeChat adapter | 微信适配器
|
||||
require('./libs/weapp-adapter.js');
|
||||
|
||||
// Load runtime | 加载运行时
|
||||
require('./libs/esengine-runtime.js');
|
||||
|
||||
// Load user code | 加载用户代码
|
||||
require('./libs/user-code.js');
|
||||
|
||||
// Initialize game | 初始化游戏
|
||||
(async function() {
|
||||
try {
|
||||
// Load WASM (use WXWebAssembly on iOS) | 加载 WASM(iOS 上使用 WXWebAssembly)
|
||||
const isIOS = wx.getSystemInfoSync().platform === 'ios';
|
||||
if (isIOS) {
|
||||
// iOS uses WXWebAssembly | iOS 使用 WXWebAssembly
|
||||
await ECS.initWasm('./wasm/es_engine_bg.wasm', { useWXWebAssembly: true });
|
||||
} else {
|
||||
await ECS.initWasm('./wasm/es_engine_bg.wasm');
|
||||
}
|
||||
|
||||
// Create runtime | 创建运行时
|
||||
const canvas = wx.createCanvas();
|
||||
const runtime = ECS.createRuntime({
|
||||
canvas: canvas,
|
||||
platform: 'wechat'
|
||||
});
|
||||
|
||||
// Load scene and start | 加载场景并启动
|
||||
await runtime.loadScene('./scenes/main.ecs');
|
||||
runtime.start();
|
||||
|
||||
console.log('[Game] Started successfully | 游戏启动成功');
|
||||
} catch (error) {
|
||||
console.error('[Game] Failed to start | 启动失败:', error);
|
||||
}
|
||||
})();
|
||||
`;
|
||||
|
||||
await fs.writeFile(`${context.outputDir}/game.js`, gameJs);
|
||||
console.log('[WeChatBuild] Generated game.js | 生成 game.js');
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate project.config.json.
|
||||
* 生成 project.config.json。
|
||||
*/
|
||||
private async _generateProjectConfig(context: BuildContext): Promise<void> {
|
||||
const fs = context.data.get('fileSystem') as IBuildFileSystem | undefined;
|
||||
const wxConfig = context.config as WeChatBuildConfig;
|
||||
|
||||
if (!fs) {
|
||||
console.warn('[WeChatBuild] No file system service, skipping | 无文件系统服务,跳过');
|
||||
return;
|
||||
}
|
||||
|
||||
const projectConfig = {
|
||||
description: 'ESEngine Game',
|
||||
packOptions: {
|
||||
ignore: [],
|
||||
include: []
|
||||
},
|
||||
setting: {
|
||||
urlCheck: false,
|
||||
es6: true,
|
||||
enhance: true,
|
||||
postcss: false,
|
||||
preloadBackgroundData: false,
|
||||
minified: wxConfig.isRelease,
|
||||
newFeature: true,
|
||||
autoAudits: false,
|
||||
coverView: true,
|
||||
showShadowRootInWxmlPanel: true,
|
||||
scopeDataCheck: false,
|
||||
checkInvalidKey: true,
|
||||
checkSiteMap: true,
|
||||
uploadWithSourceMap: !wxConfig.isRelease,
|
||||
compileHotReLoad: false,
|
||||
babelSetting: {
|
||||
ignore: [],
|
||||
disablePlugins: [],
|
||||
outputPath: ''
|
||||
}
|
||||
},
|
||||
compileType: 'game',
|
||||
libVersion: '2.25.0',
|
||||
appid: wxConfig.appId,
|
||||
projectname: 'ESEngine Game',
|
||||
condition: {}
|
||||
};
|
||||
|
||||
const jsonContent = JSON.stringify(projectConfig, null, 2);
|
||||
await fs.writeJsonFile(`${context.outputDir}/project.config.json`, jsonContent);
|
||||
console.log('[WeChatBuild] Generated project.config.json | 生成 project.config.json');
|
||||
}
|
||||
|
||||
/**
|
||||
* Process subpackages.
|
||||
* 分包处理。
|
||||
*/
|
||||
private async _splitSubpackages(context: BuildContext): Promise<void> {
|
||||
const wxConfig = context.config as WeChatBuildConfig;
|
||||
console.log('[WeChatBuild] Processing subpackages, limit | 分包处理,限制:', wxConfig.mainPackageLimit, 'KB');
|
||||
|
||||
// TODO: Implement automatic subpackage splitting based on file sizes
|
||||
// 实现基于文件大小的自动分包
|
||||
context.addWarning('Subpackage splitting is not fully implemented yet | 分包功能尚未完全实现');
|
||||
}
|
||||
|
||||
/**
|
||||
* Optimize and compress.
|
||||
* 优化压缩。
|
||||
*/
|
||||
private async _optimize(context: BuildContext): Promise<void> {
|
||||
console.log('[WeChatBuild] Optimization complete | 优化完成');
|
||||
// Minification is done during bundling | 压缩在打包时已完成
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Build Pipelines.
|
||||
* 构建管线。
|
||||
*/
|
||||
|
||||
export { WebBuildPipeline, type IBuildFileSystem } from './WebBuildPipeline';
|
||||
export { WeChatBuildPipeline } from './WeChatBuildPipeline';
|
||||
Reference in New Issue
Block a user