Feature/editor optimization (#251)

* refactor: 编辑器/运行时架构拆分与构建系统升级

* feat(core): 层级系统重构与UI变换矩阵修复

* refactor: 移除 ecs-components 聚合包并修复跨包组件查找问题

* fix(physics): 修复跨包组件类引用问题

* feat: 统一运行时架构与浏览器运行支持

* feat(asset): 实现浏览器运行时资产加载系统

* fix: 修复文档、CodeQL安全问题和CI类型检查错误

* fix: 修复文档、CodeQL安全问题和CI类型检查错误

* fix: 修复文档、CodeQL安全问题、CI类型检查和测试错误

* test: 补齐核心模块测试用例,修复CI构建配置

* fix: 修复测试用例中的类型错误和断言问题

* fix: 修复 turbo build:npm 任务的依赖顺序问题

* fix: 修复 CI 构建错误并优化构建性能
This commit is contained in:
YHH
2025-12-01 22:28:51 +08:00
committed by GitHub
parent 189714c727
commit b42a7b4e43
468 changed files with 18301 additions and 9075 deletions

View File

@@ -0,0 +1,215 @@
# @esengine/build-config
ES Engine 统一构建配置包,提供标准化的 Vite 配置预设和共享插件。
## 快速开始
### 创建新包
使用脚手架工具快速创建新包:
```bash
# 交互式创建
node scripts/create-package.mjs
# 或指定参数
node scripts/create-package.mjs my-plugin --type plugin
```
### 包类型
| 类型 | 说明 | 示例 |
|------|------|------|
| `runtime-only` | 纯运行时库,不含编辑器代码 | core, math, components |
| `plugin` | 插件包,同时有 runtime 和 editor 入口 | ui, tilemap, behavior-tree |
| `editor-only` | 纯编辑器包,仅用于编辑器 | editor-core, node-editor |
## 使用预设
### 1. runtime-only纯运行时包
```typescript
// vite.config.ts
import { runtimeOnlyPreset } from '@esengine/build-config/presets';
export default runtimeOnlyPreset({
root: __dirname
});
```
目录结构:
```
packages/my-lib/
├── src/
│ └── index.ts # 主入口
├── vite.config.ts
└── package.json
```
### 2. plugin插件包
```typescript
// vite.config.ts
import { pluginPreset } from '@esengine/build-config/presets';
export default pluginPreset({
root: __dirname,
hasCSS: true // 如果有 CSS 文件
});
```
目录结构:
```
packages/my-plugin/
├── src/
│ ├── index.ts # 主入口(导出全部)
│ ├── runtime.ts # 运行时入口(不含 React!
│ ├── MyRuntimeModule.ts
│ └── editor/
│ ├── index.ts # 编辑器模块
│ └── MyPlugin.ts
├── plugin.json # 插件描述文件
├── vite.config.ts
└── package.json
```
生成的 exports
```json
{
".": "./dist/index.js",
"./runtime": "./dist/runtime.js",
"./editor": "./dist/editor/index.js",
"./plugin.json": "./plugin.json"
}
```
### 3. editor-only纯编辑器包
```typescript
// vite.config.ts
import { editorOnlyPreset } from '@esengine/build-config/presets';
export default editorOnlyPreset({
root: __dirname,
hasReact: true,
hasCSS: true
});
```
## 共享插件
### CSS 注入插件
将 CSS 内联到 JS 中,避免单独的 CSS 文件:
```typescript
import { cssInjectPlugin } from '@esengine/build-config/plugins';
export default defineConfig({
plugins: [cssInjectPlugin()]
});
```
### 阻止编辑器代码泄漏
在运行时构建中检测并阻止编辑器代码被打包:
```typescript
import { blockEditorPlugin } from '@esengine/build-config/plugins';
export default defineConfig({
plugins: [
blockEditorPlugin({ bIsRuntimeBuild: true })
]
});
```
## Runtime vs Editor 分离规则
### ✅ runtime.ts 中可以:
- 导入 @esengine/ecs-framework
- 导入 @esengine/ecs-components
- 导入其他包的 `/runtime` 路径
### ❌ runtime.ts 中不能:
- 导入 `react``react-dom`
- 导入 `@esengine/editor-core`
- 导入 `lucide-react` 等 UI 库
- 导入任何包的 `/editor` 路径
### 示例
```typescript
// ✅ 正确
import { Core } from '@esengine/ecs-framework';
import { UIRuntimeModule } from '@esengine/ui/runtime';
// ❌ 错误 - 会把编辑器代码打包进来
import { UIPlugin } from '@esengine/ui'; // 主入口包含编辑器
import { UIPlugin } from '@esengine/ui/editor'; // 直接导入编辑器
import React from 'react'; // React 不应在运行时
```
## 迁移现有包
### 从 Rollup 迁移到 Vite 预设
1. 安装依赖:
```bash
pnpm add -D @esengine/build-config vite vite-plugin-dts
```
2. 替换 `rollup.config.js``vite.config.ts`
```typescript
import { pluginPreset } from '@esengine/build-config/presets';
export default pluginPreset({
root: __dirname,
hasCSS: true
});
```
3. 更新 `package.json` 的 scripts
```json
{
"scripts": {
"build": "vite build",
"build:watch": "vite build --watch"
}
}
```
4. 删除旧的 rollup 配置和依赖。
## API 参考
### runtimeOnlyPreset(options)
| 选项 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| root | string | - | 包根目录(必填) |
| entry | string | 'src/index.ts' | 入口文件 |
| external | (string\|RegExp)[] | [] | 额外的外部依赖 |
| viteConfig | Partial<UserConfig> | {} | 额外的 Vite 配置 |
### pluginPreset(options)
| 选项 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| root | string | - | 包根目录(必填) |
| entries.main | string | 'src/index.ts' | 主入口 |
| entries.runtime | string | 'src/runtime.ts' | 运行时入口 |
| entries.editor | string | 'src/editor/index.ts' | 编辑器入口 |
| hasCSS | boolean | false | 是否包含 CSS |
| hasPluginJson | boolean | true | 是否导出 plugin.json |
| external | (string\|RegExp)[] | [] | 额外的外部依赖 |
### editorOnlyPreset(options)
| 选项 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| root | string | - | 包根目录(必填) |
| entry | string | 'src/index.ts' | 入口文件 |
| hasReact | boolean | true | 是否包含 React |
| hasCSS | boolean | false | 是否包含 CSS |
| external | (string\|RegExp)[] | [] | 额外的外部依赖 |

View File

@@ -0,0 +1,39 @@
{
"name": "@esengine/build-config",
"version": "1.0.0",
"description": "Shared build configuration for ES Engine packages",
"type": "module",
"main": "src/index.ts",
"exports": {
".": "./src/index.ts",
"./presets": "./src/presets/index.ts",
"./presets/tsup": "./src/presets/plugin-tsup.ts",
"./plugins": "./src/plugins/index.ts"
},
"scripts": {
"type-check": "tsc --noEmit"
},
"keywords": [
"build",
"tsup",
"config"
],
"author": "yhh",
"license": "MIT",
"devDependencies": {
"@vitejs/plugin-react": "^4.3.4",
"tsup": "^8.0.0",
"typescript": "^5.8.3",
"vite": "^6.3.5",
"vite-plugin-dts": "^4.5.4"
},
"peerDependencies": {
"tsup": "^8.0.0",
"vite": "^6.0.0"
},
"peerDependenciesMeta": {
"vite": {
"optional": true
}
}
}

View File

@@ -0,0 +1,90 @@
/**
* @esengine/build-config
*
* 统一构建配置包,提供标准化的 Vite 配置预设和共享插件
* Unified build configuration with standardized Vite presets and shared plugins
*
* @example
* ```typescript
* // 1. 纯运行时包 (core, math, components)
* import { runtimeOnlyPreset } from '@esengine/build-config/presets';
* export default runtimeOnlyPreset({ root: __dirname });
*
* // 2. 插件包 (ui, tilemap, behavior-tree)
* import { pluginPreset } from '@esengine/build-config/presets';
* export default pluginPreset({
* root: __dirname,
* hasCSS: true
* });
*
* // 3. 纯编辑器包 (editor-core, node-editor)
* import { editorOnlyPreset } from '@esengine/build-config/presets';
* export default editorOnlyPreset({
* root: __dirname,
* hasReact: true
* });
* ```
*
* ## 包类型说明
*
* | 类型 | 说明 | 示例 |
* |------|------|------|
* | RuntimeOnly | 纯运行时库,不含编辑器代码 | core, math, components |
* | Plugin | 插件包,同时有 runtime 和 editor 入口 | ui, tilemap, behavior-tree |
* | EditorOnly | 纯编辑器包,仅用于编辑器 | editor-core, node-editor |
*
* ## 目录结构约定
*
* ### RuntimeOnly 包
* ```
* packages/my-lib/
* ├── src/
* │ └── index.ts # 主入口
* ├── vite.config.ts
* └── package.json
* ```
*
* ### Plugin 包
* ```
* packages/my-plugin/
* ├── src/
* │ ├── index.ts # 主入口(编辑器环境)
* │ ├── runtime.ts # 运行时入口(不含 React
* │ └── editor/
* │ └── index.ts # 编辑器模块
* ├── plugin.json # 插件描述文件
* ├── vite.config.ts
* └── package.json
* ```
*
* ### EditorOnly 包
* ```
* packages/my-editor-tool/
* ├── src/
* │ └── index.ts # 主入口
* ├── vite.config.ts
* └── package.json
* ```
*/
// Types
export { EPackageType, STANDARD_EXTERNALS, EDITOR_ONLY_EXTERNALS } from './types';
export type { PackageBuildConfig } from './types';
// Presets
export {
runtimeOnlyPreset,
pluginPreset,
standaloneRuntimeConfig,
editorOnlyPreset
} from './presets';
export type {
RuntimeOnlyOptions,
PluginPackageOptions,
StandaloneRuntimeOptions,
EditorOnlyOptions
} from './presets';
// Plugins
export { cssInjectPlugin, blockEditorPlugin } from './plugins';
export type { BlockEditorOptions } from './plugins';

View File

@@ -0,0 +1,100 @@
/**
* Block Editor Plugin
* 阻止编辑器代码泄漏插件
*
* 在运行时构建中检测并阻止编辑器代码被打包
* Detects and blocks editor code from being bundled in runtime builds
*/
import type { Plugin } from 'vite';
export interface BlockEditorOptions {
/** 是否为运行时构建 */
bIsRuntimeBuild: boolean;
/** 要阻止的模块模式 */
blockedPatterns?: (string | RegExp)[];
/** 是否只警告而不报错 */
bWarnOnly?: boolean;
}
const DEFAULT_BLOCKED_PATTERNS: (string | RegExp)[] = [
// React 相关
/^react$/,
/^react-dom$/,
/^react\/jsx-runtime$/,
/^lucide-react$/,
// 编辑器包
/@esengine\/editor-core/,
/@esengine\/node-editor/,
// 编辑器子路径
/\/editor$/,
/\/editor\//,
];
/**
* 创建阻止编辑器代码泄漏的插件
*
* @example
* ```typescript
* import { blockEditorPlugin } from '@esengine/build-config/plugins';
*
* // 在运行时构建中使用
* export default defineConfig({
* plugins: [
* blockEditorPlugin({ bIsRuntimeBuild: true })
* ]
* });
* ```
*/
export function blockEditorPlugin(options: BlockEditorOptions): Plugin {
const {
bIsRuntimeBuild,
blockedPatterns = DEFAULT_BLOCKED_PATTERNS,
bWarnOnly = false
} = options;
if (!bIsRuntimeBuild) {
// 非运行时构建不需要此插件
return { name: 'esengine:block-editor-noop' };
}
const isBlocked = (source: string): boolean => {
return blockedPatterns.some(pattern => {
if (typeof pattern === 'string') {
return source === pattern || source.startsWith(pattern + '/');
}
return pattern.test(source);
});
};
return {
name: 'esengine:block-editor',
enforce: 'pre',
resolveId(source: string, importer: string | undefined) {
if (isBlocked(source)) {
const message = `[block-editor] Editor dependency detected in runtime build:\n` +
` Source: ${source}\n` +
` Importer: ${importer || 'entry'}\n` +
`\n` +
` This usually means:\n` +
` 1. A runtime module is importing from a non-/runtime path\n` +
` 2. An editor-only dependency leaked into the dependency chain\n` +
`\n` +
` Fix: Change the import to use /runtime subpath, e.g.:\n` +
` import { X } from '@esengine/ui/runtime' // ✓\n` +
` import { X } from '@esengine/ui' // ✗`;
if (bWarnOnly) {
console.warn('\x1b[33m' + message + '\x1b[0m');
return { id: source, external: true };
} else {
throw new Error(message);
}
}
return null;
}
};
}

View File

@@ -0,0 +1,71 @@
/**
* CSS Inject Plugin
* CSS 注入插件
*
* 将 CSS 内联到 JS 中,避免单独的 CSS 文件
* Inlines CSS into JS to avoid separate CSS files
*/
import type { Plugin } from 'vite';
import type { OutputBundle, NormalizedOutputOptions, OutputAsset, OutputChunk } from 'rollup';
/**
* 创建 CSS 注入插件
*
* @example
* ```typescript
* import { cssInjectPlugin } from '@esengine/build-config/plugins';
*
* export default defineConfig({
* plugins: [cssInjectPlugin()]
* });
* ```
*/
export function cssInjectPlugin(): Plugin {
return {
name: 'esengine:css-inject',
apply: 'build',
generateBundle(_options: NormalizedOutputOptions, bundle: OutputBundle) {
// 收集所有 CSS 内容
const cssChunks: string[] = [];
const cssFileNames: string[] = [];
for (const [fileName, chunk] of Object.entries(bundle)) {
if (fileName.endsWith('.css') && chunk.type === 'asset') {
cssChunks.push(chunk.source as string);
cssFileNames.push(fileName);
}
}
if (cssChunks.length === 0) return;
// 合并所有 CSS
const combinedCSS = cssChunks.join('\n');
// 创建注入代码
const injectCode = `
(function() {
if (typeof document === 'undefined') return;
var style = document.createElement('style');
style.setAttribute('data-esengine', 'true');
style.textContent = ${JSON.stringify(combinedCSS)};
document.head.appendChild(style);
})();
`;
// 找到主入口 JS 文件并注入
for (const chunk of Object.values(bundle)) {
if (chunk.type === 'chunk' && chunk.isEntry) {
chunk.code = injectCode + chunk.code;
break;
}
}
// 删除独立的 CSS 文件
for (const fileName of cssFileNames) {
delete bundle[fileName];
}
}
};
}

View File

@@ -0,0 +1,7 @@
/**
* Shared Vite Plugins
* 共享 Vite 插件
*/
export { cssInjectPlugin } from './css-inject';
export { blockEditorPlugin, type BlockEditorOptions } from './block-editor';

View File

@@ -0,0 +1,109 @@
/**
* Editor-Only Package Preset
* 纯编辑器包预设
*
* 用于仅在编辑器环境使用的包
* For packages only used in the editor environment
*
* Examples: editor-core, node-editor
*/
import { resolve } from 'path';
import { defineConfig, type UserConfig } from 'vite';
import dts from 'vite-plugin-dts';
import react from '@vitejs/plugin-react';
import { STANDARD_EXTERNALS } from '../types';
import { cssInjectPlugin } from '../plugins/css-inject';
export interface EditorOnlyOptions {
/** 包根目录 (通常是 __dirname) */
root: string;
/** 入口文件 (默认: src/index.ts) */
entry?: string;
/** 是否包含 React 组件 (默认: true) */
hasReact?: boolean;
/** 是否包含 CSS (默认: false) */
hasCSS?: boolean;
/** 额外的外部依赖 */
external?: (string | RegExp)[];
/** 额外的 Vite 配置 */
viteConfig?: Partial<UserConfig>;
}
/**
* 创建纯编辑器包的 Vite 配置
*
* @example
* ```typescript
* // vite.config.ts
* import { editorOnlyPreset } from '@esengine/build-config/presets';
*
* export default editorOnlyPreset({
* root: __dirname,
* hasReact: true,
* hasCSS: true
* });
* ```
*/
export function editorOnlyPreset(options: EditorOnlyOptions): UserConfig {
const {
root,
entry = 'src/index.ts',
hasReact = true,
hasCSS = false,
external = [],
viteConfig = {}
} = options;
const plugins: any[] = [];
// React 支持
if (hasReact) {
plugins.push(react());
}
// DTS 生成
plugins.push(
dts({
include: ['src'],
outDir: 'dist',
rollupTypes: false
})
);
// CSS 注入
if (hasCSS) {
plugins.push(cssInjectPlugin());
}
return defineConfig({
plugins,
esbuild: hasReact ? { jsx: 'automatic' } : undefined,
build: {
lib: {
entry: resolve(root, entry),
formats: ['es'],
fileName: () => 'index.js'
},
rollupOptions: {
external: [
...STANDARD_EXTERNALS,
...external
],
output: {
exports: 'named',
preserveModules: false
}
},
target: 'es2020',
minify: false,
sourcemap: true
},
...viteConfig
});
}

View File

@@ -0,0 +1,10 @@
/**
* Build Presets
* 构建预设
*
* 提供不同类型包的标准化 Vite 配置
*/
export { runtimeOnlyPreset, type RuntimeOnlyOptions } from './runtime-only';
export { pluginPreset, standaloneRuntimeConfig, type PluginPackageOptions, type StandaloneRuntimeOptions } from './plugin';
export { editorOnlyPreset, type EditorOnlyOptions } from './editor-only';

View File

@@ -0,0 +1,157 @@
/**
* Plugin Package Preset (tsup)
* 插件包预设 - 基于 tsup/esbuild
*
* 用于同时包含运行时和编辑器模块的插件包
* For plugin packages with both runtime and editor modules
*
* 生成三个入口点:
* - index.js - 完整导出(编辑器环境)
* - runtime.js - 纯运行时(游戏运行时环境,不含 React
* - editor/index.js - 编辑器模块
*
* Examples: ui, tilemap, behavior-tree, physics-rapier2d
*/
import type { Options } from 'tsup';
import { STANDARD_EXTERNALS } from '../types';
export interface PluginPackageOptions {
/** 入口点配置 */
entries?: {
/** 主入口 (默认: src/index.ts) */
main?: string;
/** 运行时入口 (默认: src/runtime.ts) */
runtime?: string;
/** 编辑器入口 (默认: src/editor/index.ts) */
editor?: string;
};
/** 额外的外部依赖 */
external?: (string | RegExp)[];
/** 额外的 tsup 配置 */
tsupConfig?: Partial<Options>;
}
/**
* 创建插件包的 tsup 配置
*
* @example
* ```typescript
* // tsup.config.ts
* import { defineConfig } from 'tsup';
* import { pluginPreset } from '@esengine/build-config/presets';
*
* export default defineConfig(pluginPreset());
* ```
*/
export function pluginPreset(options: PluginPackageOptions = {}): Options {
const {
entries = {},
external = [],
tsupConfig = {}
} = options;
const mainEntry = entries.main ?? 'src/index.ts';
const runtimeEntry = entries.runtime ?? 'src/runtime.ts';
const editorEntry = entries.editor ?? 'src/editor/index.ts';
// 合并外部依赖
const allExternal = [
...STANDARD_EXTERNALS,
...external
];
return {
entry: {
index: mainEntry,
runtime: runtimeEntry,
'editor/index': editorEntry
},
format: ['esm'],
dts: true,
splitting: false, // 禁用代码分割
sourcemap: true,
clean: true,
external: allExternal,
esbuildOptions(options) {
options.jsx = 'automatic';
},
...tsupConfig
};
}
/**
* 创建纯运行时包的 tsup 配置
*/
export interface RuntimeOnlyOptions {
/** 入口文件 (默认: src/index.ts) */
entry?: string;
/** 额外的外部依赖 */
external?: (string | RegExp)[];
/** 额外的 tsup 配置 */
tsupConfig?: Partial<Options>;
}
export function runtimeOnlyPreset(options: RuntimeOnlyOptions = {}): Options {
const {
entry = 'src/index.ts',
external = [],
tsupConfig = {}
} = options;
return {
entry: [entry],
format: ['esm'],
dts: true,
splitting: false,
sourcemap: true,
clean: true,
external: [
...STANDARD_EXTERNALS,
...external
],
...tsupConfig
};
}
/**
* 创建纯编辑器包的 tsup 配置
*/
export interface EditorOnlyOptions {
/** 入口文件 (默认: src/index.ts) */
entry?: string;
/** 额外的外部依赖 */
external?: (string | RegExp)[];
/** 额外的 tsup 配置 */
tsupConfig?: Partial<Options>;
}
export function editorOnlyPreset(options: EditorOnlyOptions = {}): Options {
const {
entry = 'src/index.ts',
external = [],
tsupConfig = {}
} = options;
return {
entry: [entry],
format: ['esm'],
dts: true,
splitting: false,
sourcemap: true,
clean: true,
// 将 CSS 内联到 JS 中,运行时自动注入到 DOM
// Inline CSS into JS, auto-inject to DOM at runtime
injectStyle: true,
external: [
...STANDARD_EXTERNALS,
...external
],
esbuildOptions(options) {
options.jsx = 'automatic';
},
...tsupConfig
};
}

View File

@@ -0,0 +1,176 @@
/**
* Plugin Package Preset
* 插件包预设
*
* 用于同时包含运行时和编辑器模块的插件包
* For plugin packages with both runtime and editor modules
*
* 生成三个入口点:
* - index.js - 完整导出(编辑器环境)
* - runtime.js - 纯运行时(游戏运行时环境,不含 React
* - editor/index.js - 编辑器模块
*
* Examples: ui, tilemap, behavior-tree, physics-rapier2d
*/
import { resolve } from 'path';
import { defineConfig, type UserConfig } from 'vite';
import dts from 'vite-plugin-dts';
import { STANDARD_EXTERNALS, EDITOR_ONLY_EXTERNALS } from '../types';
import { cssInjectPlugin } from '../plugins/css-inject';
export interface PluginPackageOptions {
/** 包根目录 (通常是 __dirname) */
root: string;
/** 入口点配置 */
entries?: {
/** 主入口 (默认: src/index.ts) */
main?: string;
/** 运行时入口 (默认: src/runtime.ts) */
runtime?: string;
/** 编辑器入口 (默认: src/editor/index.ts) */
editor?: string;
};
/** 是否包含 CSS (默认: false) */
hasCSS?: boolean;
/** 是否生成 plugin.json 导出 (默认: true) */
hasPluginJson?: boolean;
/** 额外的外部依赖 */
external?: (string | RegExp)[];
/** 额外的 Vite 配置 */
viteConfig?: Partial<UserConfig>;
}
/**
* 创建插件包的 Vite 配置
*
* @example
* ```typescript
* // vite.config.ts
* import { pluginPreset } from '@esengine/build-config/presets';
*
* export default pluginPreset({
* root: __dirname,
* hasCSS: true
* });
* ```
*/
export function pluginPreset(options: PluginPackageOptions): UserConfig {
const {
root,
entries = {},
hasCSS = false,
external = [],
viteConfig = {}
} = options;
const mainEntry = entries.main ?? 'src/index.ts';
const runtimeEntry = entries.runtime ?? 'src/runtime.ts';
const editorEntry = entries.editor ?? 'src/editor/index.ts';
// 构建入口点映射
const entryPoints: Record<string, string> = {
index: resolve(root, mainEntry),
runtime: resolve(root, runtimeEntry),
'editor/index': resolve(root, editorEntry)
};
const plugins: any[] = [
dts({
include: ['src'],
outDir: 'dist',
rollupTypes: false
})
];
// CSS 注入插件
if (hasCSS) {
plugins.push(cssInjectPlugin());
}
return defineConfig({
plugins,
esbuild: {
jsx: 'automatic',
},
build: {
lib: {
entry: entryPoints,
formats: ['es'],
fileName: (_format: string, entryName: string) => `${entryName}.js`
},
rollupOptions: {
external: [
...STANDARD_EXTERNALS,
...external
],
output: {
exports: 'named',
preserveModules: false,
// 禁用自动代码分割,所有共享代码内联到各入口
manualChunks: () => undefined
}
},
target: 'es2020',
minify: false,
sourcemap: true
},
...viteConfig
});
}
/**
* 创建独立运行时构建配置
* 用于 platform-web 等需要生成独立 IIFE 运行时的场景
*
* @example
* ```typescript
* // rollup.runtime.config.js
* import { standaloneRuntimeConfig } from '@esengine/build-config/presets';
*
* export default standaloneRuntimeConfig({
* root: __dirname,
* entry: 'src/runtime.ts',
* globalName: 'ECSRuntime'
* });
* ```
*/
export interface StandaloneRuntimeOptions {
/** 包根目录 */
root: string;
/** 入口文件 */
entry: string;
/** 全局变量名 (IIFE 格式) */
globalName: string;
/** 额外的外部依赖 */
external?: (string | RegExp)[];
}
export function standaloneRuntimeConfig(options: StandaloneRuntimeOptions) {
const { root, entry, globalName, external = [] } = options;
// 返回 Rollup 配置(而非 Vite因为需要 IIFE 格式)
return {
input: resolve(root, entry),
output: {
file: 'dist/runtime.browser.js',
format: 'iife' as const,
name: globalName,
sourcemap: true,
exports: 'default' as const
},
external: [
...STANDARD_EXTERNALS,
...EDITOR_ONLY_EXTERNALS,
...external
],
plugins: [
// 需要在使用时传入 rollup 插件
]
};
}

View File

@@ -0,0 +1,78 @@
/**
* Runtime-Only Package Preset
* 纯运行时包预设
*
* 用于不包含任何编辑器代码的基础库
* For basic libraries without any editor code
*
* Examples: core, math, components, asset-system
*/
import { resolve } from 'path';
import { defineConfig, type UserConfig } from 'vite';
import dts from 'vite-plugin-dts';
import { STANDARD_EXTERNALS } from '../types';
export interface RuntimeOnlyOptions {
/** 包根目录 (通常是 __dirname) */
root: string;
/** 入口文件 (默认: src/index.ts) */
entry?: string;
/** 额外的外部依赖 */
external?: (string | RegExp)[];
/** 额外的 Vite 配置 */
viteConfig?: Partial<UserConfig>;
}
/**
* 创建纯运行时包的 Vite 配置
*
* @example
* ```typescript
* // vite.config.ts
* import { runtimeOnlyPreset } from '@esengine/build-config/presets';
*
* export default runtimeOnlyPreset({
* root: __dirname
* });
* ```
*/
export function runtimeOnlyPreset(options: RuntimeOnlyOptions): UserConfig {
const {
root,
entry = 'src/index.ts',
external = [],
viteConfig = {}
} = options;
return defineConfig({
plugins: [
dts({
include: ['src'],
outDir: 'dist',
rollupTypes: false
})
],
build: {
lib: {
entry: resolve(root, entry),
formats: ['es'],
fileName: () => 'index.js'
},
rollupOptions: {
external: [
...STANDARD_EXTERNALS,
...external
],
output: {
exports: 'named',
preserveModules: false
}
},
target: 'es2020',
minify: false,
sourcemap: true
},
...viteConfig
});
}

View File

@@ -0,0 +1,107 @@
/**
* Build Configuration Types
* 构建配置类型定义
*/
import type { UserConfig } from 'vite';
/**
* 包类型
* Package types for different build configurations
*/
export const enum EPackageType {
/**
* 纯运行时库 - 不含任何编辑器代码
* Pure runtime library - no editor dependencies
*
* Examples: core, math, components, asset-system
*/
RuntimeOnly = 'runtime-only',
/**
* 插件包 - 同时包含运行时和编辑器模块
* Plugin package - contains both runtime and editor modules
*
* Examples: ui, tilemap, behavior-tree, physics-rapier2d
*/
Plugin = 'plugin',
/**
* 纯编辑器包 - 仅用于编辑器
* Editor-only package - only used in editor
*
* Examples: editor-core, node-editor
*/
EditorOnly = 'editor-only',
/**
* 应用包 - 最终应用(不发布到 npm
* Application package - final app (not published)
*
* Examples: editor-app
*/
Application = 'application'
}
/**
* 包构建配置
*/
export interface PackageBuildConfig {
/** 包名 */
name: string;
/** 包类型 */
type: EPackageType;
/** 入口点配置 */
entries?: {
/** 主入口 (默认: src/index.ts) */
main?: string;
/** 运行时入口 (仅 Plugin 类型) */
runtime?: string;
/** 编辑器入口 (Plugin 和 EditorOnly 类型) */
editor?: string;
};
/** 额外的外部依赖 */
external?: (string | RegExp)[];
/** 是否包含 CSS */
hasCSS?: boolean;
/** 是否生成 plugin.json 导出 */
hasPluginJson?: boolean;
/** 额外的 Vite 配置 */
viteConfig?: Partial<UserConfig>;
}
/**
* 标准外部依赖列表
* Standard external dependencies that should never be bundled
*/
export const STANDARD_EXTERNALS = [
// React 生态
'react',
'react-dom',
'react/jsx-runtime',
'lucide-react',
// 状态管理
'zustand',
'immer',
// 所有 @esengine 包
/^@esengine\//,
] as const;
/**
* 编辑器专用依赖(运行时构建必须排除)
* Editor-only dependencies that must be excluded from runtime builds
*/
export const EDITOR_ONLY_EXTERNALS = [
'@esengine/editor-core',
'@esengine/node-editor',
/\/editor$/,
/\/editor\//,
] as const;

View File

@@ -0,0 +1,62 @@
{
"name": "{{fullName}}",
"version": "1.0.0",
"description": "{{description}}",
"type": "module",
"main": "dist/index.js",
"module": "dist/index.js",
"types": "dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
},
"./runtime": {
"types": "./dist/runtime.d.ts",
"import": "./dist/runtime.js"
},
"./editor": {
"types": "./dist/editor/index.d.ts",
"import": "./dist/editor/index.js"
},
"./plugin.json": "./plugin.json"
},
"files": [
"dist",
"plugin.json"
],
"scripts": {
"build": "tsup",
"build:watch": "tsup --watch",
"clean": "rimraf dist",
"type-check": "tsc --noEmit"
},
"peerDependencies": {
"@esengine/ecs-framework": "{{depVersion}}",
"@esengine/ecs-components": "{{depVersion}}",
"@esengine/editor-core": "{{depVersion}}",
"react": "^18.3.1"
},
"peerDependenciesMeta": {
"@esengine/editor-core": {
"optional": true
},
"react": {
"optional": true
}
},
"devDependencies": {
"@esengine/build-config": "{{depVersion}}",
"@types/react": "^18.3.12",
"rimraf": "^5.0.5",
"tsup": "^8.0.0",
"typescript": "^5.8.3"
},
"keywords": [
"ecs",
"esengine",
"plugin"
],
"author": "",
"license": "MIT"
}

View File

@@ -0,0 +1,22 @@
{
"id": "{{fullName}}",
"name": "{{displayName}}",
"version": "1.0.0",
"category": "{{category}}",
"enabledByDefault": true,
"isEnginePlugin": false,
"modules": [
{
"name": "{{pascalName}}Runtime",
"type": "runtime",
"entry": "./src/runtime.ts"
},
{
"name": "{{pascalName}}Editor",
"type": "editor",
"entry": "./src/editor/index.ts"
}
],
"components": [],
"dependencies": []
}

View File

@@ -0,0 +1,49 @@
/**
* {{displayName}} Runtime Module
*
* 运行时模块 - 负责注册组件、服务和系统
*/
import type {
IRuntimeModuleLoader,
IComponentRegistry,
SystemContext
} from '@esengine/ecs-components';
import type { IScene, ServiceContainer } from '@esengine/ecs-framework';
export class {{name}}RuntimeModule implements IRuntimeModuleLoader {
/**
* 注册组件到组件注册表
*/
registerComponents(registry: IComponentRegistry): void {
// registry.register(MyComponent);
}
/**
* 注册服务到服务容器
*/
registerServices?(services: ServiceContainer): void {
// services.registerSingleton(MyService);
}
/**
* 初始化回调
*/
async onInitialize?(): Promise<void> {
// 执行初始化逻辑
}
/**
* 为场景创建系统
*/
createSystems?(scene: IScene, context: SystemContext): void {
// scene.addSystem(new MySystem());
}
/**
* 系统创建完成后的回调,用于连接跨插件依赖
*/
onSystemsCreated?(scene: IScene, context: SystemContext): void {
// 连接跨插件依赖
}
}

View File

@@ -0,0 +1,34 @@
/**
* {{displayName}} Plugin
*
* 插件定义 - 注册编辑器模块Inspector、工具等
*/
import type { IPluginLoader, PluginDescriptor, IEditorModuleLoader } from '@esengine/ecs-components';
import { {{name}}RuntimeModule } from '../{{name}}RuntimeModule';
class {{name}}EditorModule implements IEditorModuleLoader {
registerInspectors(registry: any): void {
// 注册组件 Inspector
// registry.register('MyComponent', MyComponentInspector);
}
}
const descriptor: PluginDescriptor = {
id: '@esengine/{{name}}',
name: '{{displayName}}',
version: '1.0.0',
category: '{{category}}',
enabledByDefault: true,
isEnginePlugin: false,
modules: [
{ name: '{{name}}Runtime', type: 'runtime', entry: './src/runtime.ts' },
{ name: '{{name}}Editor', type: 'editor', entry: './src/editor/index.ts' }
]
};
export const {{name}}Plugin: IPluginLoader = {
descriptor,
runtimeModule: new {{name}}RuntimeModule(),
editorModule: new {{name}}EditorModule()
};

View File

@@ -0,0 +1,13 @@
/**
* @esengine/{{name}} Editor Module
*
* 编辑器模块 - 包含 Inspector、工具等编辑器专用代码
* Editor module - contains Inspector, tools, and other editor-specific code
*
* This module can safely import React and editor-core packages.
*/
export { {{name}}Plugin } from './{{name}}Plugin';
// Inspectors
// export { MyComponentInspector } from './inspectors/MyComponentInspector';

View File

@@ -0,0 +1,14 @@
/**
* @esengine/{{name}}
*
* {{description}}
*
* 主入口 - 导出所有内容(包括编辑器模块)
* Main entry - exports everything (including editor modules)
*/
// Runtime exports (always available)
export * from './runtime';
// Editor exports (only in editor environment)
export { {{name}}Plugin } from './editor';

View File

@@ -0,0 +1,17 @@
/**
* @esengine/{{name}} Runtime Entry Point
*
* 运行时入口 - 仅导出运行时代码,不包含任何编辑器依赖
* Runtime entry - exports only runtime code without any editor dependencies
*
* IMPORTANT: Do not import React or any editor packages here!
*/
// Components
// export { MyComponent } from './components/MyComponent';
// Systems
// export { MySystem } from './systems/MySystem';
// Runtime Module
export { {{name}}RuntimeModule } from './{{name}}RuntimeModule';

View File

@@ -0,0 +1,23 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "ES2020",
"moduleResolution": "bundler",
"lib": ["ES2020", "DOM"],
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"declaration": true,
"declarationMap": true,
"jsx": "react-jsx",
"resolveJsonModule": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}

View File

@@ -0,0 +1,20 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "bundler",
"lib": ["ES2020", "DOM"],
"strict": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"jsx": "react-jsx",
"resolveJsonModule": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}

View File

@@ -0,0 +1,7 @@
import { defineConfig } from 'tsup';
import { pluginPreset } from '@esengine/build-config/presets/tsup';
export default defineConfig({
...pluginPreset(),
tsconfig: 'tsconfig.build.json'
});

View File

@@ -0,0 +1,38 @@
{
"name": "{{fullName}}",
"version": "1.0.0",
"description": "{{description}}",
"type": "module",
"main": "dist/index.js",
"module": "dist/index.js",
"types": "dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
}
},
"files": [
"dist"
],
"scripts": {
"build": "tsup",
"build:watch": "tsup --watch",
"clean": "rimraf dist",
"type-check": "tsc --noEmit"
},
"peerDependencies": {
"@esengine/ecs-framework": "{{depVersion}}"
},
"devDependencies": {
"@esengine/build-config": "{{depVersion}}",
"rimraf": "^5.0.5",
"tsup": "^8.0.0",
"typescript": "^5.8.3"
},
"keywords": [
"ecs"
],
"author": "",
"license": "MIT"
}

View File

@@ -0,0 +1,8 @@
/**
* @esengine/{{name}}
*
* {{description}}
*/
// Export your public API here
export {};

View File

@@ -0,0 +1,22 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "ES2020",
"moduleResolution": "bundler",
"lib": ["ES2020", "DOM"],
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"declaration": true,
"declarationMap": true,
"resolveJsonModule": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}

View File

@@ -0,0 +1,19 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "bundler",
"lib": ["ES2020", "DOM"],
"strict": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"resolveJsonModule": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}

View File

@@ -0,0 +1,7 @@
import { defineConfig } from 'tsup';
import { runtimeOnlyPreset } from '@esengine/build-config/presets/tsup';
export default defineConfig({
...runtimeOnlyPreset(),
tsconfig: 'tsconfig.build.json'
});

View File

@@ -0,0 +1,17 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"declaration": true,
"declarationDir": "dist",
"outDir": "dist",
"rootDir": "src",
"resolveJsonModule": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}