@playcraft/build 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +96 -0
- package/dist/base-builder.d.ts +66 -0
- package/dist/base-builder.js +415 -0
- package/dist/converter.d.ts +35 -0
- package/dist/converter.js +148 -0
- package/dist/generators/config-generator.d.ts +7 -0
- package/dist/generators/config-generator.js +122 -0
- package/dist/generators/settings-generator.d.ts +14 -0
- package/dist/generators/settings-generator.js +100 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +14 -0
- package/dist/loaders/playcanvas-loader.d.ts +10 -0
- package/dist/loaders/playcanvas-loader.js +18 -0
- package/dist/loaders/playcraft-loader.d.ts +10 -0
- package/dist/loaders/playcraft-loader.js +51 -0
- package/dist/platforms/applovin.d.ts +10 -0
- package/dist/platforms/applovin.js +67 -0
- package/dist/platforms/base.d.ts +29 -0
- package/dist/platforms/base.js +11 -0
- package/dist/platforms/bigo.d.ts +15 -0
- package/dist/platforms/bigo.js +77 -0
- package/dist/platforms/facebook.d.ts +9 -0
- package/dist/platforms/facebook.js +37 -0
- package/dist/platforms/google.d.ts +10 -0
- package/dist/platforms/google.js +53 -0
- package/dist/platforms/index.d.ts +14 -0
- package/dist/platforms/index.js +47 -0
- package/dist/platforms/ironsource.d.ts +10 -0
- package/dist/platforms/ironsource.js +71 -0
- package/dist/platforms/liftoff.d.ts +10 -0
- package/dist/platforms/liftoff.js +56 -0
- package/dist/platforms/moloco.d.ts +10 -0
- package/dist/platforms/moloco.js +53 -0
- package/dist/platforms/snapchat.d.ts +10 -0
- package/dist/platforms/snapchat.js +59 -0
- package/dist/platforms/tiktok.d.ts +15 -0
- package/dist/platforms/tiktok.js +65 -0
- package/dist/platforms/unity.d.ts +10 -0
- package/dist/platforms/unity.js +69 -0
- package/dist/playable-builder.d.ts +97 -0
- package/dist/playable-builder.js +590 -0
- package/dist/types.d.ts +90 -0
- package/dist/types.js +1 -0
- package/dist/vite/config-builder.d.ts +15 -0
- package/dist/vite/config-builder.js +212 -0
- package/dist/vite/platform-configs.d.ts +38 -0
- package/dist/vite/platform-configs.js +257 -0
- package/dist/vite/plugin-model-compression.d.ts +11 -0
- package/dist/vite/plugin-model-compression.js +63 -0
- package/dist/vite/plugin-platform.d.ts +17 -0
- package/dist/vite/plugin-platform.js +241 -0
- package/dist/vite/plugin-playcanvas.d.ts +18 -0
- package/dist/vite/plugin-playcanvas.js +711 -0
- package/dist/vite/plugin-source-builder.d.ts +15 -0
- package/dist/vite/plugin-source-builder.js +344 -0
- package/dist/vite-builder.d.ts +51 -0
- package/dist/vite-builder.js +122 -0
- package/package.json +51 -0
- package/templates/__loading__.js +100 -0
- package/templates/__modules__.js +47 -0
- package/templates/__settings__.template.js +20 -0
- package/templates/__start__.js +332 -0
- package/templates/index.html +18 -0
- package/templates/logo.png +0 -0
- package/templates/manifest.json +1 -0
- package/templates/patches/cannon.min.js +28 -0
- package/templates/patches/lz4.js +10 -0
- package/templates/patches/one-page-http-get.js +20 -0
- package/templates/patches/one-page-inline-game-scripts.js +20 -0
- package/templates/patches/one-page-mraid-resize-canvas.js +46 -0
- package/templates/patches/p2.min.js +27 -0
- package/templates/patches/playcraft-no-xhr.js +52 -0
- package/templates/playcanvas-stable.min.js +16363 -0
- package/templates/styles.css +43 -0
package/README.md
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# @playcraft/build
|
|
2
|
+
|
|
3
|
+
PlayCraft 核心构建模块。将 PlayCanvas/PlayCraft 项目打包为 Playable Ads。
|
|
4
|
+
|
|
5
|
+
## 功能
|
|
6
|
+
|
|
7
|
+
- 两阶段构建(Base Build → Channel Build)
|
|
8
|
+
- 支持 10 个广告平台
|
|
9
|
+
- 使用 Vite 进行资源内联与压缩
|
|
10
|
+
- 平台特定适配与 SDK 注入
|
|
11
|
+
|
|
12
|
+
## 支持的平台
|
|
13
|
+
|
|
14
|
+
- Facebook
|
|
15
|
+
- Snapchat
|
|
16
|
+
- ironSource
|
|
17
|
+
- AppLovin
|
|
18
|
+
- Google Ads
|
|
19
|
+
- TikTok/Pangle
|
|
20
|
+
- Unity Ads
|
|
21
|
+
- Liftoff
|
|
22
|
+
- Moloco
|
|
23
|
+
- BIGO Ads
|
|
24
|
+
|
|
25
|
+
## 使用
|
|
26
|
+
|
|
27
|
+
### 基础用法
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
import { BaseBuilder, ViteBuilder, type BuildOptions } from '@playcraft/build';
|
|
31
|
+
|
|
32
|
+
// 阶段1:Base Build
|
|
33
|
+
const baseBuilder = new BaseBuilder('/path/to/project', {
|
|
34
|
+
outputDir: './build',
|
|
35
|
+
});
|
|
36
|
+
const baseBuildResult = await baseBuilder.build();
|
|
37
|
+
|
|
38
|
+
// 阶段2:Channel Build
|
|
39
|
+
const buildOptions: BuildOptions = {
|
|
40
|
+
platform: 'facebook',
|
|
41
|
+
format: 'html',
|
|
42
|
+
outputDir: './dist',
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const viteBuilder = new ViteBuilder(baseBuildResult.outputDir, buildOptions);
|
|
46
|
+
const outputPath = await viteBuilder.build();
|
|
47
|
+
const sizeReport = viteBuilder.getSizeReport();
|
|
48
|
+
|
|
49
|
+
console.log('输出:', outputPath);
|
|
50
|
+
console.log('大小报告:', sizeReport);
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### 平台适配器
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
import { createPlatformAdapter } from '@playcraft/build';
|
|
57
|
+
|
|
58
|
+
const adapter = createPlatformAdapter({
|
|
59
|
+
platform: 'facebook',
|
|
60
|
+
format: 'html',
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
console.log(adapter.getName()); // "Facebook"
|
|
64
|
+
console.log(adapter.getSizeLimit()); // 2097152 (2MB)
|
|
65
|
+
console.log(adapter.getDefaultFormat()); // "html"
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## 架构
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
@playcraft/build
|
|
72
|
+
├── BaseBuilder # 阶段1:生成多文件构建产物
|
|
73
|
+
├── ViteBuilder # 阶段2:Vite 单文件打包
|
|
74
|
+
├── PlayableBuilder # 阶段2:旧构建器(兼容)
|
|
75
|
+
├── platforms/ # 平台适配器
|
|
76
|
+
│ ├── FacebookAdapter
|
|
77
|
+
│ ├── SnapchatAdapter
|
|
78
|
+
│ ├── IronSourceAdapter
|
|
79
|
+
│ ├── AppLovinAdapter
|
|
80
|
+
│ ├── GoogleAdapter
|
|
81
|
+
│ ├── TikTokAdapter
|
|
82
|
+
│ ├── UnityAdapter
|
|
83
|
+
│ ├── LiftoffAdapter
|
|
84
|
+
│ ├── MolocoAdapter
|
|
85
|
+
│ └── BigoAdapter
|
|
86
|
+
└── vite/ # Vite 插件
|
|
87
|
+
├── plugin-playcanvas
|
|
88
|
+
├── plugin-platform
|
|
89
|
+
└── plugin-model-compression
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## 依赖方
|
|
93
|
+
|
|
94
|
+
- `@playcraft/cli`:CLI 工具
|
|
95
|
+
- `@playcraft/worker`:队列消费与异步构建
|
|
96
|
+
- `@playcraft/backend`:类型定义用于参数校验
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
export interface BaseBuildOptions {
|
|
2
|
+
outputDir: string;
|
|
3
|
+
}
|
|
4
|
+
export interface BaseBuildOutput {
|
|
5
|
+
outputDir: string;
|
|
6
|
+
files: {
|
|
7
|
+
html: string;
|
|
8
|
+
engine: string | null;
|
|
9
|
+
config: string;
|
|
10
|
+
settings: string | null;
|
|
11
|
+
modules: string | null;
|
|
12
|
+
start: string;
|
|
13
|
+
scenes: string[];
|
|
14
|
+
assets: string[];
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* 基础构建器 - 生成可运行的多文件构建产物
|
|
19
|
+
*
|
|
20
|
+
* 职责:
|
|
21
|
+
* 1. 从源代码或构建产物加载项目
|
|
22
|
+
* 2. 确保所有必需文件存在且格式正确
|
|
23
|
+
* 3. 输出可直接运行的多文件版本
|
|
24
|
+
* 4. 不做任何内联或压缩
|
|
25
|
+
*/
|
|
26
|
+
export declare class BaseBuilder {
|
|
27
|
+
private projectDir;
|
|
28
|
+
private options;
|
|
29
|
+
constructor(projectDir: string, options: BaseBuildOptions);
|
|
30
|
+
/**
|
|
31
|
+
* 执行基础构建
|
|
32
|
+
*/
|
|
33
|
+
build(): Promise<BaseBuildOutput>;
|
|
34
|
+
/**
|
|
35
|
+
* 检测项目类型
|
|
36
|
+
*/
|
|
37
|
+
private detectProjectType;
|
|
38
|
+
/**
|
|
39
|
+
* 从官方构建产物构建
|
|
40
|
+
*/
|
|
41
|
+
private buildFromOfficial;
|
|
42
|
+
/**
|
|
43
|
+
* 验证官方构建产物
|
|
44
|
+
*/
|
|
45
|
+
private validateOfficialBuild;
|
|
46
|
+
/**
|
|
47
|
+
* 复制构建文件到输出目录
|
|
48
|
+
*/
|
|
49
|
+
private copyBuildFiles;
|
|
50
|
+
/**
|
|
51
|
+
* 递归复制目录
|
|
52
|
+
*/
|
|
53
|
+
private copyDirectory;
|
|
54
|
+
/**
|
|
55
|
+
* 从源代码构建(使用 Vite)
|
|
56
|
+
*/
|
|
57
|
+
private buildFromSource;
|
|
58
|
+
/**
|
|
59
|
+
* 扫描输出文件
|
|
60
|
+
*/
|
|
61
|
+
private scanOutputFiles;
|
|
62
|
+
/**
|
|
63
|
+
* 递归扫描目录
|
|
64
|
+
*/
|
|
65
|
+
private scanDirectory;
|
|
66
|
+
}
|
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import { build as viteBuild } from 'vite';
|
|
5
|
+
import { viteSourceBuilderPlugin } from './vite/plugin-source-builder.js';
|
|
6
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
+
const __dirname = path.dirname(__filename);
|
|
8
|
+
/**
|
|
9
|
+
* 基础构建器 - 生成可运行的多文件构建产物
|
|
10
|
+
*
|
|
11
|
+
* 职责:
|
|
12
|
+
* 1. 从源代码或构建产物加载项目
|
|
13
|
+
* 2. 确保所有必需文件存在且格式正确
|
|
14
|
+
* 3. 输出可直接运行的多文件版本
|
|
15
|
+
* 4. 不做任何内联或压缩
|
|
16
|
+
*/
|
|
17
|
+
export class BaseBuilder {
|
|
18
|
+
constructor(projectDir, options) {
|
|
19
|
+
this.projectDir = projectDir;
|
|
20
|
+
this.options = options;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* 执行基础构建
|
|
24
|
+
*/
|
|
25
|
+
async build() {
|
|
26
|
+
// 1. 检测项目类型
|
|
27
|
+
const projectType = await this.detectProjectType();
|
|
28
|
+
if (projectType === 'official-build') {
|
|
29
|
+
// 官方构建产物 - 直接复制并验证
|
|
30
|
+
return await this.buildFromOfficial();
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
// 源代码格式(PlayCanvas 或 PlayCraft)- 使用 Vite 生成构建产物
|
|
34
|
+
const formatName = projectType === 'playcraft' ? 'PlayCraft' : 'PlayCanvas';
|
|
35
|
+
console.log(`[BaseBuilder] 检测到 ${formatName} 源代码格式,使用 Vite 构建...`);
|
|
36
|
+
return await this.buildFromSource();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* 检测项目类型
|
|
41
|
+
*/
|
|
42
|
+
async detectProjectType() {
|
|
43
|
+
// 1. 检查是否是官方构建产物
|
|
44
|
+
const buildIndicators = [
|
|
45
|
+
path.join(this.projectDir, 'index.html'),
|
|
46
|
+
path.join(this.projectDir, 'config.json'),
|
|
47
|
+
];
|
|
48
|
+
try {
|
|
49
|
+
await fs.access(buildIndicators[0]);
|
|
50
|
+
await fs.access(buildIndicators[1]);
|
|
51
|
+
return 'official-build';
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
// 2. 检查是否是 PlayCraft 项目格式
|
|
55
|
+
const manifestPath = path.join(this.projectDir, 'manifest.json');
|
|
56
|
+
try {
|
|
57
|
+
await fs.access(manifestPath);
|
|
58
|
+
const manifestContent = await fs.readFile(manifestPath, 'utf-8');
|
|
59
|
+
const manifest = JSON.parse(manifestContent);
|
|
60
|
+
if (manifest.format === 'playcraft' || Array.isArray(manifest.assets)) {
|
|
61
|
+
return 'playcraft';
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
// manifest.json 不存在或格式不对
|
|
66
|
+
}
|
|
67
|
+
// 3. 默认视为源代码项目
|
|
68
|
+
return 'source';
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* 从官方构建产物构建
|
|
73
|
+
*/
|
|
74
|
+
async buildFromOfficial() {
|
|
75
|
+
// 验证必需文件存在
|
|
76
|
+
await this.validateOfficialBuild();
|
|
77
|
+
// 创建输出目录
|
|
78
|
+
await fs.mkdir(this.options.outputDir, { recursive: true });
|
|
79
|
+
// 复制所有文件到输出目录
|
|
80
|
+
const files = await this.copyBuildFiles();
|
|
81
|
+
return {
|
|
82
|
+
outputDir: this.options.outputDir,
|
|
83
|
+
files,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* 验证官方构建产物
|
|
88
|
+
*/
|
|
89
|
+
async validateOfficialBuild() {
|
|
90
|
+
const requiredFiles = [
|
|
91
|
+
'index.html',
|
|
92
|
+
'config.json',
|
|
93
|
+
'__start__.js',
|
|
94
|
+
];
|
|
95
|
+
const missingFiles = [];
|
|
96
|
+
for (const file of requiredFiles) {
|
|
97
|
+
try {
|
|
98
|
+
await fs.access(path.join(this.projectDir, file));
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
missingFiles.push(file);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (missingFiles.length > 0) {
|
|
105
|
+
throw new Error(`官方构建产物缺少必需文件: ${missingFiles.join(', ')}\n` +
|
|
106
|
+
`请确保项目目录包含完整的构建产物。`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* 复制构建文件到输出目录
|
|
111
|
+
*/
|
|
112
|
+
async copyBuildFiles() {
|
|
113
|
+
const files = {
|
|
114
|
+
html: '',
|
|
115
|
+
engine: null,
|
|
116
|
+
config: '',
|
|
117
|
+
settings: null,
|
|
118
|
+
modules: null,
|
|
119
|
+
start: '',
|
|
120
|
+
scenes: [],
|
|
121
|
+
assets: [],
|
|
122
|
+
};
|
|
123
|
+
// 复制 index.html
|
|
124
|
+
const htmlPath = path.join(this.projectDir, 'index.html');
|
|
125
|
+
const outputHtmlPath = path.join(this.options.outputDir, 'index.html');
|
|
126
|
+
await fs.copyFile(htmlPath, outputHtmlPath);
|
|
127
|
+
files.html = outputHtmlPath;
|
|
128
|
+
// 复制 config.json
|
|
129
|
+
const configPath = path.join(this.projectDir, 'config.json');
|
|
130
|
+
const outputConfigPath = path.join(this.options.outputDir, 'config.json');
|
|
131
|
+
await fs.copyFile(configPath, outputConfigPath);
|
|
132
|
+
files.config = outputConfigPath;
|
|
133
|
+
// 读取 config.json 以获取场景信息
|
|
134
|
+
const configContent = await fs.readFile(configPath, 'utf-8');
|
|
135
|
+
const configJson = JSON.parse(configContent);
|
|
136
|
+
// 复制 __start__.js
|
|
137
|
+
const startPath = path.join(this.projectDir, '__start__.js');
|
|
138
|
+
const outputStartPath = path.join(this.options.outputDir, '__start__.js');
|
|
139
|
+
await fs.copyFile(startPath, outputStartPath);
|
|
140
|
+
files.start = outputStartPath;
|
|
141
|
+
// 复制 __settings__.js(如果存在)
|
|
142
|
+
const settingsPath = path.join(this.projectDir, '__settings__.js');
|
|
143
|
+
try {
|
|
144
|
+
await fs.access(settingsPath);
|
|
145
|
+
const outputSettingsPath = path.join(this.options.outputDir, '__settings__.js');
|
|
146
|
+
await fs.copyFile(settingsPath, outputSettingsPath);
|
|
147
|
+
files.settings = outputSettingsPath;
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
// __settings__.js 不是必需的
|
|
151
|
+
}
|
|
152
|
+
// 复制 __modules__.js(如果存在)
|
|
153
|
+
const modulesPath = path.join(this.projectDir, '__modules__.js');
|
|
154
|
+
try {
|
|
155
|
+
await fs.access(modulesPath);
|
|
156
|
+
const outputModulesPath = path.join(this.options.outputDir, '__modules__.js');
|
|
157
|
+
await fs.copyFile(modulesPath, outputModulesPath);
|
|
158
|
+
files.modules = outputModulesPath;
|
|
159
|
+
}
|
|
160
|
+
catch (error) {
|
|
161
|
+
// __modules__.js 不是必需的
|
|
162
|
+
}
|
|
163
|
+
// 复制 PlayCanvas Engine(查找可能的文件名)
|
|
164
|
+
const engineNames = [
|
|
165
|
+
'playcanvas-stable.min.js',
|
|
166
|
+
'playcanvas.min.js',
|
|
167
|
+
'__lib__.js',
|
|
168
|
+
];
|
|
169
|
+
for (const engineName of engineNames) {
|
|
170
|
+
const enginePath = path.join(this.projectDir, engineName);
|
|
171
|
+
try {
|
|
172
|
+
await fs.access(enginePath);
|
|
173
|
+
const outputEnginePath = path.join(this.options.outputDir, engineName);
|
|
174
|
+
await fs.copyFile(enginePath, outputEnginePath);
|
|
175
|
+
files.engine = outputEnginePath;
|
|
176
|
+
break;
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
// 继续尝试下一个
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
// 复制场景文件
|
|
183
|
+
if (configJson.scenes && Array.isArray(configJson.scenes)) {
|
|
184
|
+
for (const scene of configJson.scenes) {
|
|
185
|
+
if (scene.url && !scene.url.startsWith('data:')) {
|
|
186
|
+
const scenePath = path.join(this.projectDir, scene.url);
|
|
187
|
+
try {
|
|
188
|
+
await fs.access(scenePath);
|
|
189
|
+
const sceneDir = path.dirname(scene.url);
|
|
190
|
+
if (sceneDir && sceneDir !== '.') {
|
|
191
|
+
const outputSceneDir = path.join(this.options.outputDir, sceneDir);
|
|
192
|
+
await fs.mkdir(outputSceneDir, { recursive: true });
|
|
193
|
+
}
|
|
194
|
+
const outputScenePath = path.join(this.options.outputDir, scene.url);
|
|
195
|
+
await fs.copyFile(scenePath, outputScenePath);
|
|
196
|
+
files.scenes.push(outputScenePath);
|
|
197
|
+
}
|
|
198
|
+
catch (error) {
|
|
199
|
+
console.warn(`警告: 场景文件不存在: ${scene.url}`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
// 复制资产文件(从 config.json 中的 assets)
|
|
205
|
+
if (configJson.assets) {
|
|
206
|
+
const assetsDir = path.join(this.options.outputDir, 'files');
|
|
207
|
+
await fs.mkdir(assetsDir, { recursive: true });
|
|
208
|
+
for (const [assetId, assetData] of Object.entries(configJson.assets)) {
|
|
209
|
+
const asset = assetData;
|
|
210
|
+
if (asset.file && asset.file.url && !asset.file.url.startsWith('data:')) {
|
|
211
|
+
const assetPath = path.join(this.projectDir, asset.file.url);
|
|
212
|
+
try {
|
|
213
|
+
await fs.access(assetPath);
|
|
214
|
+
const assetDir = path.dirname(asset.file.url);
|
|
215
|
+
if (assetDir && assetDir !== '.') {
|
|
216
|
+
const outputAssetDir = path.join(this.options.outputDir, assetDir);
|
|
217
|
+
await fs.mkdir(outputAssetDir, { recursive: true });
|
|
218
|
+
}
|
|
219
|
+
const outputAssetPath = path.join(this.options.outputDir, asset.file.url);
|
|
220
|
+
await fs.copyFile(assetPath, outputAssetPath);
|
|
221
|
+
files.assets.push(outputAssetPath);
|
|
222
|
+
}
|
|
223
|
+
catch (error) {
|
|
224
|
+
// 资产文件可能不存在(可能是内联的)
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
// 复制 files/ 目录(如果存在)
|
|
230
|
+
const filesDir = path.join(this.projectDir, 'files');
|
|
231
|
+
try {
|
|
232
|
+
const filesDirStat = await fs.stat(filesDir);
|
|
233
|
+
if (filesDirStat.isDirectory()) {
|
|
234
|
+
const outputFilesDir = path.join(this.options.outputDir, 'files');
|
|
235
|
+
await this.copyDirectory(filesDir, outputFilesDir);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
catch (error) {
|
|
239
|
+
// files/ 目录可能不存在
|
|
240
|
+
}
|
|
241
|
+
// 复制 styles.css(如果存在)
|
|
242
|
+
const stylesPath = path.join(this.projectDir, 'styles.css');
|
|
243
|
+
try {
|
|
244
|
+
await fs.access(stylesPath);
|
|
245
|
+
const outputStylesPath = path.join(this.options.outputDir, 'styles.css');
|
|
246
|
+
await fs.copyFile(stylesPath, outputStylesPath);
|
|
247
|
+
}
|
|
248
|
+
catch (error) {
|
|
249
|
+
// styles.css 可能不存在
|
|
250
|
+
}
|
|
251
|
+
// 复制 manifest.json(如果存在)
|
|
252
|
+
const manifestPath = path.join(this.projectDir, 'manifest.json');
|
|
253
|
+
try {
|
|
254
|
+
await fs.access(manifestPath);
|
|
255
|
+
const outputManifestPath = path.join(this.options.outputDir, 'manifest.json');
|
|
256
|
+
await fs.copyFile(manifestPath, outputManifestPath);
|
|
257
|
+
}
|
|
258
|
+
catch (error) {
|
|
259
|
+
// manifest.json 可能不存在
|
|
260
|
+
}
|
|
261
|
+
return files;
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* 递归复制目录
|
|
265
|
+
*/
|
|
266
|
+
async copyDirectory(src, dest) {
|
|
267
|
+
await fs.mkdir(dest, { recursive: true });
|
|
268
|
+
const entries = await fs.readdir(src, { withFileTypes: true });
|
|
269
|
+
for (const entry of entries) {
|
|
270
|
+
const srcPath = path.join(src, entry.name);
|
|
271
|
+
const destPath = path.join(dest, entry.name);
|
|
272
|
+
if (entry.isDirectory()) {
|
|
273
|
+
await this.copyDirectory(srcPath, destPath);
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
await fs.copyFile(srcPath, destPath);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* 从源代码构建(使用 Vite)
|
|
282
|
+
*/
|
|
283
|
+
async buildFromSource() {
|
|
284
|
+
console.log('[BaseBuilder] 使用 Vite 从源代码构建...');
|
|
285
|
+
// 1. 创建 Vite 配置
|
|
286
|
+
const viteConfig = {
|
|
287
|
+
root: this.projectDir,
|
|
288
|
+
base: './',
|
|
289
|
+
build: {
|
|
290
|
+
outDir: this.options.outputDir,
|
|
291
|
+
emptyOutDir: true,
|
|
292
|
+
// 多文件输出(不内联资源)
|
|
293
|
+
assetsInlineLimit: 0,
|
|
294
|
+
// Base Build 不压缩(保持可读性和调试性)
|
|
295
|
+
minify: false,
|
|
296
|
+
cssMinify: false,
|
|
297
|
+
sourcemap: false,
|
|
298
|
+
rollupOptions: {
|
|
299
|
+
input: {
|
|
300
|
+
// 虚拟模块:用户脚本入口
|
|
301
|
+
'game-scripts': 'virtual:game-scripts',
|
|
302
|
+
},
|
|
303
|
+
output: {
|
|
304
|
+
entryFileNames: '__[name].js',
|
|
305
|
+
chunkFileNames: '__[name]-[hash].js',
|
|
306
|
+
assetFileNames: 'files/assets/[name].[ext]',
|
|
307
|
+
},
|
|
308
|
+
// 外部化 PlayCanvas Engine(不打包)
|
|
309
|
+
external: ['pc', 'playcanvas'],
|
|
310
|
+
// 仅对用户脚本模块启用 tree-shake(引擎已外部化)
|
|
311
|
+
treeshake: true,
|
|
312
|
+
},
|
|
313
|
+
},
|
|
314
|
+
plugins: [
|
|
315
|
+
// 源代码构建插件
|
|
316
|
+
viteSourceBuilderPlugin({
|
|
317
|
+
projectDir: this.projectDir,
|
|
318
|
+
outputDir: this.options.outputDir,
|
|
319
|
+
}),
|
|
320
|
+
],
|
|
321
|
+
};
|
|
322
|
+
// 2. 执行 Vite 构建
|
|
323
|
+
await viteBuild(viteConfig);
|
|
324
|
+
// 3. 扫描生成的文件
|
|
325
|
+
const files = await this.scanOutputFiles();
|
|
326
|
+
// 4. 返回构建结果
|
|
327
|
+
return {
|
|
328
|
+
outputDir: this.options.outputDir,
|
|
329
|
+
files,
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* 扫描输出文件
|
|
334
|
+
*/
|
|
335
|
+
async scanOutputFiles() {
|
|
336
|
+
const files = {
|
|
337
|
+
html: path.join(this.options.outputDir, 'index.html'),
|
|
338
|
+
engine: null,
|
|
339
|
+
config: path.join(this.options.outputDir, 'config.json'),
|
|
340
|
+
settings: path.join(this.options.outputDir, '__settings__.js'),
|
|
341
|
+
modules: null,
|
|
342
|
+
start: path.join(this.options.outputDir, '__start__.js'),
|
|
343
|
+
scenes: [],
|
|
344
|
+
assets: [],
|
|
345
|
+
};
|
|
346
|
+
// 检查 engine
|
|
347
|
+
const enginePath = path.join(this.options.outputDir, 'playcanvas-stable.min.js');
|
|
348
|
+
try {
|
|
349
|
+
await fs.access(enginePath);
|
|
350
|
+
files.engine = enginePath;
|
|
351
|
+
}
|
|
352
|
+
catch (error) {
|
|
353
|
+
// engine 可能不存在
|
|
354
|
+
}
|
|
355
|
+
// 检查 modules
|
|
356
|
+
const modulesPath = path.join(this.options.outputDir, '__modules__.js');
|
|
357
|
+
try {
|
|
358
|
+
await fs.access(modulesPath);
|
|
359
|
+
files.modules = modulesPath;
|
|
360
|
+
}
|
|
361
|
+
catch (error) {
|
|
362
|
+
// modules 可能不存在
|
|
363
|
+
}
|
|
364
|
+
// 读取 config.json 以获取场景信息
|
|
365
|
+
try {
|
|
366
|
+
const configContent = await fs.readFile(files.config, 'utf-8');
|
|
367
|
+
const configJson = JSON.parse(configContent);
|
|
368
|
+
// 扫描场景文件
|
|
369
|
+
if (configJson.scenes && Array.isArray(configJson.scenes)) {
|
|
370
|
+
for (const scene of configJson.scenes) {
|
|
371
|
+
if (scene.url && !scene.url.startsWith('data:')) {
|
|
372
|
+
const scenePath = path.join(this.options.outputDir, scene.url);
|
|
373
|
+
try {
|
|
374
|
+
await fs.access(scenePath);
|
|
375
|
+
files.scenes.push(scenePath);
|
|
376
|
+
}
|
|
377
|
+
catch (error) {
|
|
378
|
+
// 场景文件可能不存在
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
// 扫描资产文件(从 files/ 目录)
|
|
384
|
+
const filesDir = path.join(this.options.outputDir, 'files');
|
|
385
|
+
try {
|
|
386
|
+
const filesDirStat = await fs.stat(filesDir);
|
|
387
|
+
if (filesDirStat.isDirectory()) {
|
|
388
|
+
await this.scanDirectory(filesDir, files.assets);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
catch (error) {
|
|
392
|
+
// files/ 目录可能不存在
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
catch (error) {
|
|
396
|
+
console.warn('警告: 无法读取 config.json:', error);
|
|
397
|
+
}
|
|
398
|
+
return files;
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* 递归扫描目录
|
|
402
|
+
*/
|
|
403
|
+
async scanDirectory(dir, files) {
|
|
404
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
405
|
+
for (const entry of entries) {
|
|
406
|
+
const fullPath = path.join(dir, entry.name);
|
|
407
|
+
if (entry.isDirectory()) {
|
|
408
|
+
await this.scanDirectory(fullPath, files);
|
|
409
|
+
}
|
|
410
|
+
else {
|
|
411
|
+
files.push(fullPath);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { AssetInfo, BuildOptions } from './types.js';
|
|
2
|
+
export declare class OnePageConverter {
|
|
3
|
+
private projectDir;
|
|
4
|
+
private options;
|
|
5
|
+
private assets;
|
|
6
|
+
constructor(projectDir: string, options: BuildOptions);
|
|
7
|
+
/**
|
|
8
|
+
* 添加资产到转换列表
|
|
9
|
+
*/
|
|
10
|
+
addAsset(assetPath: string, type: string): void;
|
|
11
|
+
/**
|
|
12
|
+
* 编码所有资产为 Base64
|
|
13
|
+
*/
|
|
14
|
+
encodeAssets(): Promise<void>;
|
|
15
|
+
/**
|
|
16
|
+
* 获取资产的 Base64 数据 URL
|
|
17
|
+
*/
|
|
18
|
+
getDataUrl(assetPath: string): string | null;
|
|
19
|
+
/**
|
|
20
|
+
* 替换 HTML 中的 URL 为 Base64 数据 URL
|
|
21
|
+
*/
|
|
22
|
+
replaceUrlsWithBase64(html: string, urlMap: Map<string, string>): string;
|
|
23
|
+
/**
|
|
24
|
+
* 内联脚本到 HTML
|
|
25
|
+
*/
|
|
26
|
+
inlineScripts(html: string): Promise<string>;
|
|
27
|
+
/**
|
|
28
|
+
* 获取所有资产信息
|
|
29
|
+
*/
|
|
30
|
+
getAssets(): AssetInfo[];
|
|
31
|
+
/**
|
|
32
|
+
* 获取总大小
|
|
33
|
+
*/
|
|
34
|
+
getTotalSize(): number;
|
|
35
|
+
}
|