@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.
Files changed (74) hide show
  1. package/README.md +96 -0
  2. package/dist/base-builder.d.ts +66 -0
  3. package/dist/base-builder.js +415 -0
  4. package/dist/converter.d.ts +35 -0
  5. package/dist/converter.js +148 -0
  6. package/dist/generators/config-generator.d.ts +7 -0
  7. package/dist/generators/config-generator.js +122 -0
  8. package/dist/generators/settings-generator.d.ts +14 -0
  9. package/dist/generators/settings-generator.js +100 -0
  10. package/dist/index.d.ts +14 -0
  11. package/dist/index.js +14 -0
  12. package/dist/loaders/playcanvas-loader.d.ts +10 -0
  13. package/dist/loaders/playcanvas-loader.js +18 -0
  14. package/dist/loaders/playcraft-loader.d.ts +10 -0
  15. package/dist/loaders/playcraft-loader.js +51 -0
  16. package/dist/platforms/applovin.d.ts +10 -0
  17. package/dist/platforms/applovin.js +67 -0
  18. package/dist/platforms/base.d.ts +29 -0
  19. package/dist/platforms/base.js +11 -0
  20. package/dist/platforms/bigo.d.ts +15 -0
  21. package/dist/platforms/bigo.js +77 -0
  22. package/dist/platforms/facebook.d.ts +9 -0
  23. package/dist/platforms/facebook.js +37 -0
  24. package/dist/platforms/google.d.ts +10 -0
  25. package/dist/platforms/google.js +53 -0
  26. package/dist/platforms/index.d.ts +14 -0
  27. package/dist/platforms/index.js +47 -0
  28. package/dist/platforms/ironsource.d.ts +10 -0
  29. package/dist/platforms/ironsource.js +71 -0
  30. package/dist/platforms/liftoff.d.ts +10 -0
  31. package/dist/platforms/liftoff.js +56 -0
  32. package/dist/platforms/moloco.d.ts +10 -0
  33. package/dist/platforms/moloco.js +53 -0
  34. package/dist/platforms/snapchat.d.ts +10 -0
  35. package/dist/platforms/snapchat.js +59 -0
  36. package/dist/platforms/tiktok.d.ts +15 -0
  37. package/dist/platforms/tiktok.js +65 -0
  38. package/dist/platforms/unity.d.ts +10 -0
  39. package/dist/platforms/unity.js +69 -0
  40. package/dist/playable-builder.d.ts +97 -0
  41. package/dist/playable-builder.js +590 -0
  42. package/dist/types.d.ts +90 -0
  43. package/dist/types.js +1 -0
  44. package/dist/vite/config-builder.d.ts +15 -0
  45. package/dist/vite/config-builder.js +212 -0
  46. package/dist/vite/platform-configs.d.ts +38 -0
  47. package/dist/vite/platform-configs.js +257 -0
  48. package/dist/vite/plugin-model-compression.d.ts +11 -0
  49. package/dist/vite/plugin-model-compression.js +63 -0
  50. package/dist/vite/plugin-platform.d.ts +17 -0
  51. package/dist/vite/plugin-platform.js +241 -0
  52. package/dist/vite/plugin-playcanvas.d.ts +18 -0
  53. package/dist/vite/plugin-playcanvas.js +711 -0
  54. package/dist/vite/plugin-source-builder.d.ts +15 -0
  55. package/dist/vite/plugin-source-builder.js +344 -0
  56. package/dist/vite-builder.d.ts +51 -0
  57. package/dist/vite-builder.js +122 -0
  58. package/package.json +51 -0
  59. package/templates/__loading__.js +100 -0
  60. package/templates/__modules__.js +47 -0
  61. package/templates/__settings__.template.js +20 -0
  62. package/templates/__start__.js +332 -0
  63. package/templates/index.html +18 -0
  64. package/templates/logo.png +0 -0
  65. package/templates/manifest.json +1 -0
  66. package/templates/patches/cannon.min.js +28 -0
  67. package/templates/patches/lz4.js +10 -0
  68. package/templates/patches/one-page-http-get.js +20 -0
  69. package/templates/patches/one-page-inline-game-scripts.js +20 -0
  70. package/templates/patches/one-page-mraid-resize-canvas.js +46 -0
  71. package/templates/patches/p2.min.js +27 -0
  72. package/templates/patches/playcraft-no-xhr.js +52 -0
  73. package/templates/playcanvas-stable.min.js +16363 -0
  74. package/templates/styles.css +43 -0
@@ -0,0 +1,15 @@
1
+ import type { Plugin } from 'vite';
2
+ export interface SourceBuilderPluginOptions {
3
+ projectDir: string;
4
+ outputDir: string;
5
+ }
6
+ export interface UserScript {
7
+ id: string;
8
+ path: string;
9
+ code: string;
10
+ name: string;
11
+ }
12
+ /**
13
+ * Vite 插件:从源代码构建 PlayCanvas 项目
14
+ */
15
+ export declare function viteSourceBuilderPlugin(options: SourceBuilderPluginOptions): Plugin;
@@ -0,0 +1,344 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ import crypto from 'crypto';
5
+ import { loadPlayCanvasProject } from '../loaders/playcanvas-loader.js';
6
+ import { loadPlayCraftProject } from '../loaders/playcraft-loader.js';
7
+ import { generateConfig } from '../generators/config-generator.js';
8
+ import { generateSettings } from '../generators/settings-generator.js';
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const __dirname = path.dirname(__filename);
11
+ /**
12
+ * Vite 插件:从源代码构建 PlayCanvas 项目
13
+ */
14
+ export function viteSourceBuilderPlugin(options) {
15
+ let projectFormat = null;
16
+ let projectConfig = null;
17
+ let userScripts = [];
18
+ return {
19
+ name: 'vite-plugin-source-builder',
20
+ enforce: 'pre',
21
+ async buildStart() {
22
+ console.log('[SourceBuilder] 开始构建源代码项目...');
23
+ // 1. 检测项目格式
24
+ projectFormat = await detectFormat(options.projectDir);
25
+ console.log(`[SourceBuilder] 检测到项目格式: ${projectFormat}`);
26
+ // 2. 读取项目配置
27
+ if (projectFormat === 'playcanvas') {
28
+ projectConfig = await loadPlayCanvasProject(options.projectDir);
29
+ }
30
+ else {
31
+ projectConfig = await loadPlayCraftProject(options.projectDir);
32
+ }
33
+ // 2.1 规范化资源文件 URL(补齐 file.url)
34
+ await normalizeAssetUrls(projectConfig, options.projectDir);
35
+ // 3. 收集用户脚本
36
+ userScripts = await collectUserScripts(projectConfig, options.projectDir);
37
+ console.log(`[SourceBuilder] 收集到 ${userScripts.length} 个用户脚本`);
38
+ },
39
+ // 处理虚拟模块(用户脚本)
40
+ resolveId(id) {
41
+ if (id === 'virtual:game-scripts') {
42
+ return '\0virtual:game-scripts';
43
+ }
44
+ return null;
45
+ },
46
+ load(id) {
47
+ if (id === '\0virtual:game-scripts') {
48
+ // 合并所有用户脚本
49
+ const scriptCode = userScripts.length > 0
50
+ ? userScripts
51
+ .map(s => wrapPlayCanvasScript(s.code, s.name, s))
52
+ .join('\n\n')
53
+ : '// No user scripts';
54
+ return scriptCode;
55
+ }
56
+ return null;
57
+ },
58
+ async closeBundle() {
59
+ if (!projectConfig) {
60
+ return;
61
+ }
62
+ // 确保输出目录存在
63
+ await fs.mkdir(options.outputDir, { recursive: true });
64
+ // 生成 config.json
65
+ const config = generateConfig(projectConfig);
66
+ // 为脚本资产补齐 __game-scripts.js 信息
67
+ await patchScriptAssets(config, options.outputDir);
68
+ await fs.writeFile(path.join(options.outputDir, 'config.json'), JSON.stringify(config, null, 2), 'utf-8');
69
+ console.log('[SourceBuilder] 生成 config.json');
70
+ // 生成 __settings__.js
71
+ const settings = generateSettings(projectConfig);
72
+ await fs.writeFile(path.join(options.outputDir, '__settings__.js'), settings, 'utf-8');
73
+ console.log('[SourceBuilder] 生成 __settings__.js');
74
+ // 复制模板文件
75
+ await copyTemplates(options.outputDir);
76
+ console.log('[SourceBuilder] 复制模板文件');
77
+ // 复制资源文件
78
+ await copyAssets(projectConfig, options.projectDir, options.outputDir, config.assets);
79
+ console.log('[SourceBuilder] 复制资源文件');
80
+ // 生成场景文件
81
+ await generateSceneFiles(projectConfig, options.projectDir, options.outputDir);
82
+ console.log('[SourceBuilder] 生成场景文件');
83
+ },
84
+ };
85
+ }
86
+ /**
87
+ * 检测项目格式
88
+ */
89
+ async function detectFormat(projectDir) {
90
+ // 检查 manifest.json(PlayCraft)
91
+ const manifestPath = path.join(projectDir, 'manifest.json');
92
+ try {
93
+ const content = await fs.readFile(manifestPath, 'utf-8');
94
+ const manifest = JSON.parse(content);
95
+ if (manifest.format === 'playcraft' || Array.isArray(manifest.assets)) {
96
+ return 'playcraft';
97
+ }
98
+ }
99
+ catch (error) {
100
+ // manifest.json 不存在或格式不对
101
+ }
102
+ // 默认为 PlayCanvas
103
+ return 'playcanvas';
104
+ }
105
+ /**
106
+ * 收集用户脚本
107
+ */
108
+ async function collectUserScripts(projectConfig, projectDir) {
109
+ const scripts = [];
110
+ if (projectConfig.format === 'playcanvas') {
111
+ const pcProject = projectConfig;
112
+ const scriptIds = pcProject.project.settings?.scripts || [];
113
+ // 从 assets.json 读取脚本资产
114
+ for (const scriptId of scriptIds) {
115
+ const asset = pcProject.assets[String(scriptId)];
116
+ if (asset && asset.type === 'script') {
117
+ const assetData = asset;
118
+ if (assetData.file?.url && !assetData.file.url.startsWith('data:')) {
119
+ const scriptPath = path.join(projectDir, assetData.file.url);
120
+ try {
121
+ const code = await fs.readFile(scriptPath, 'utf-8');
122
+ scripts.push({
123
+ id: String(scriptId),
124
+ path: assetData.file.url,
125
+ code,
126
+ name: assetData.name || `script_${scriptId}`,
127
+ });
128
+ }
129
+ catch (error) {
130
+ console.warn(`警告: 无法读取脚本文件: ${scriptPath}`);
131
+ }
132
+ }
133
+ }
134
+ }
135
+ }
136
+ else {
137
+ // PlayCraft 格式
138
+ const pcProject = projectConfig;
139
+ const scriptIds = pcProject.manifest.settings?.scripts || [];
140
+ for (const scriptId of scriptIds) {
141
+ const asset = pcProject.assets[String(scriptId)];
142
+ if (asset && asset.type === 'script') {
143
+ const assetData = asset;
144
+ if (assetData.file?.url && !assetData.file.url.startsWith('data:')) {
145
+ const scriptPath = path.join(projectDir, assetData.file.url);
146
+ try {
147
+ const code = await fs.readFile(scriptPath, 'utf-8');
148
+ scripts.push({
149
+ id: String(scriptId),
150
+ path: assetData.file.url,
151
+ code,
152
+ name: assetData.name || `script_${scriptId}`,
153
+ });
154
+ }
155
+ catch (error) {
156
+ console.warn(`警告: 无法读取脚本文件: ${scriptPath}`);
157
+ }
158
+ }
159
+ }
160
+ }
161
+ }
162
+ return scripts;
163
+ }
164
+ /**
165
+ * 包装 PlayCanvas 脚本
166
+ */
167
+ function wrapPlayCanvasScript(code, scriptName, script) {
168
+ // 清理脚本名称(移除特殊字符)
169
+ const cleanName = scriptName.replace(/[^a-zA-Z0-9_]/g, '_');
170
+ return `
171
+ var ${cleanName} = pc.createScript('${cleanName}');
172
+ ${code}
173
+ `.trim();
174
+ }
175
+ /**
176
+ * 复制模板文件
177
+ */
178
+ async function copyTemplates(outputDir) {
179
+ const templatesDir = path.resolve(__dirname, '../../templates');
180
+ const templateFiles = [
181
+ '__start__.js',
182
+ '__modules__.js',
183
+ '__loading__.js',
184
+ 'playcanvas-stable.min.js',
185
+ 'styles.css',
186
+ 'logo.png',
187
+ 'manifest.json',
188
+ ];
189
+ for (const file of templateFiles) {
190
+ const sourcePath = path.join(templatesDir, file);
191
+ const targetPath = path.join(outputDir, file);
192
+ try {
193
+ await fs.copyFile(sourcePath, targetPath);
194
+ }
195
+ catch (error) {
196
+ console.warn(`警告: 无法复制模板文件 ${file}:`, error);
197
+ }
198
+ }
199
+ // 生成 index.html
200
+ const projectName = 'PlayCanvas Project';
201
+ const htmlTemplate = await fs.readFile(path.join(templatesDir, 'index.html'), 'utf-8');
202
+ const htmlContent = htmlTemplate.replace(/\{\{PROJECT_NAME\}\}/g, projectName);
203
+ await fs.writeFile(path.join(outputDir, 'index.html'), htmlContent, 'utf-8');
204
+ }
205
+ /**
206
+ * 复制资源文件
207
+ */
208
+ async function copyAssets(projectConfig, projectDir, outputDir, assetsOverride) {
209
+ const assetsOutputDir = path.join(outputDir, 'files');
210
+ await fs.mkdir(assetsOutputDir, { recursive: true });
211
+ const assets = assetsOverride || (projectConfig.format === 'playcanvas'
212
+ ? projectConfig.assets
213
+ : projectConfig.assets);
214
+ // 遍历 assets
215
+ for (const [assetId, asset] of Object.entries(assets)) {
216
+ const assetData = asset;
217
+ if (!assetData.file?.url || assetData.file.url.startsWith('data:')) {
218
+ continue;
219
+ }
220
+ const sourcePath = path.join(projectDir, assetData.file.url);
221
+ const targetPath = path.join(outputDir, assetData.file.url);
222
+ try {
223
+ // 检查源文件是否存在
224
+ await fs.access(sourcePath);
225
+ // 创建目标目录
226
+ const targetDir = path.dirname(targetPath);
227
+ await fs.mkdir(targetDir, { recursive: true });
228
+ // 复制文件
229
+ await fs.copyFile(sourcePath, targetPath);
230
+ }
231
+ catch (error) {
232
+ // 资源文件可能不存在(可能是内联的)
233
+ // console.warn(`警告: 无法复制资源文件 ${assetData.file.url}:`, error);
234
+ }
235
+ }
236
+ }
237
+ /**
238
+ * 生成场景文件
239
+ */
240
+ async function generateSceneFiles(projectConfig, projectDir, outputDir) {
241
+ if (projectConfig.format === 'playcanvas') {
242
+ const pcProject = projectConfig;
243
+ // PlayCanvas 格式:scenes.json 中已包含场景数据
244
+ if (pcProject.scenes) {
245
+ const scenes = Array.isArray(pcProject.scenes)
246
+ ? pcProject.scenes
247
+ : Object.entries(pcProject.scenes).map(([id, scene]) => ({ id, ...scene }));
248
+ for (const scene of scenes) {
249
+ const sceneData = scene;
250
+ const sceneId = sceneData.id || sceneData.name || 'scene';
251
+ const scenePath = path.join(outputDir, `${sceneId}.json`);
252
+ // 写入场景 JSON
253
+ await fs.writeFile(scenePath, JSON.stringify(sceneData, null, 2), 'utf-8');
254
+ }
255
+ }
256
+ }
257
+ else {
258
+ // PlayCraft 格式:从 scenes/ 目录读取
259
+ const pcProject = projectConfig;
260
+ for (const scene of pcProject.scenes) {
261
+ const sceneData = scene;
262
+ const sceneId = sceneData.id || sceneData.name || 'scene';
263
+ const scenePath = path.join(outputDir, `${sceneId}.json`);
264
+ // 写入场景 JSON
265
+ await fs.writeFile(scenePath, JSON.stringify(sceneData, null, 2), 'utf-8');
266
+ }
267
+ }
268
+ }
269
+ /**
270
+ * 规范化资源 URL(补齐 file.url)
271
+ */
272
+ async function normalizeAssetUrls(projectConfig, projectDir) {
273
+ const assets = projectConfig.format === 'playcanvas'
274
+ ? projectConfig.assets
275
+ : projectConfig.assets;
276
+ for (const [assetId, asset] of Object.entries(assets)) {
277
+ const assetData = asset;
278
+ if (!assetData.file || assetData.file.url) {
279
+ continue;
280
+ }
281
+ const filename = assetData.file.filename;
282
+ if (!filename) {
283
+ continue;
284
+ }
285
+ const revision = assetData.revision ?? assetData.file.revision ?? 1;
286
+ const candidates = [
287
+ path.join('files', 'assets', String(assetId), String(revision), filename),
288
+ path.join('files', 'assets', String(assetId), '1', filename),
289
+ path.join('files', 'assets', String(assetId), filename),
290
+ path.join('files', filename),
291
+ ];
292
+ for (const rel of candidates) {
293
+ const abs = path.join(projectDir, rel);
294
+ try {
295
+ await fs.access(abs);
296
+ assetData.file.url = rel;
297
+ break;
298
+ }
299
+ catch (error) {
300
+ // 继续尝试下一个
301
+ }
302
+ }
303
+ }
304
+ }
305
+ /**
306
+ * 为脚本资产补齐 __game-scripts.js 信息
307
+ */
308
+ async function patchScriptAssets(config, outputDir) {
309
+ if (!config?.assets) {
310
+ return;
311
+ }
312
+ const scriptIds = Array.isArray(config?.application_properties?.scripts)
313
+ ? config.application_properties.scripts.map((id) => String(id))
314
+ : [];
315
+ if (scriptIds.length === 0) {
316
+ return;
317
+ }
318
+ const gameScriptsPath = path.join(outputDir, '__game-scripts.js');
319
+ let stat = null;
320
+ let hash = null;
321
+ try {
322
+ const buffer = await fs.readFile(gameScriptsPath);
323
+ stat = { size: buffer.byteLength };
324
+ hash = crypto.createHash('md5').update(buffer).digest('hex');
325
+ }
326
+ catch (error) {
327
+ // __game-scripts.js 可能不存在
328
+ }
329
+ for (const [assetId, asset] of Object.entries(config.assets)) {
330
+ const assetData = asset;
331
+ if (!scriptIds.includes(String(assetId))) {
332
+ continue;
333
+ }
334
+ if (assetData.type !== 'script' || !assetData.file) {
335
+ continue;
336
+ }
337
+ assetData.file.url = '__game-scripts.js';
338
+ assetData.file.filename = '__game-scripts.js';
339
+ if (stat && hash) {
340
+ assetData.file.size = stat.size;
341
+ assetData.file.hash = hash;
342
+ }
343
+ }
344
+ }
@@ -0,0 +1,51 @@
1
+ import type { BuildOptions, SizeReport } from './types.js';
2
+ export interface ViteBuildOutput {
3
+ outputPath: string;
4
+ size: number;
5
+ sizeLimit: number;
6
+ valid: boolean;
7
+ }
8
+ /**
9
+ * Vite 构建器 - 使用 Vite 构建 Playable Ads
10
+ *
11
+ * 职责:
12
+ * 1. 验证输入是有效的基础构建
13
+ * 2. 创建 Vite 配置
14
+ * 3. 执行 Vite 构建
15
+ * 4. 验证输出大小
16
+ * 5. 生成报告
17
+ */
18
+ export declare class ViteBuilder {
19
+ private baseBuildDir;
20
+ private options;
21
+ private sizeReport;
22
+ constructor(baseBuildDir: string, options: BuildOptions);
23
+ /**
24
+ * 执行构建
25
+ */
26
+ build(): Promise<string>;
27
+ /**
28
+ * 验证基础构建
29
+ */
30
+ private validateBaseBuild;
31
+ /**
32
+ * 获取输出路径
33
+ */
34
+ private getOutputPath;
35
+ /**
36
+ * 验证输出大小
37
+ */
38
+ private validateSize;
39
+ /**
40
+ * 生成报告
41
+ */
42
+ private generateReport;
43
+ /**
44
+ * 获取大小报告
45
+ */
46
+ getSizeReport(): SizeReport;
47
+ /**
48
+ * 格式化字节数
49
+ */
50
+ private formatBytes;
51
+ }
@@ -0,0 +1,122 @@
1
+ import { build as viteBuild } from 'vite';
2
+ import fs from 'fs/promises';
3
+ import path from 'path';
4
+ import { ViteConfigBuilder } from './vite/config-builder.js';
5
+ import { PLATFORM_CONFIGS } from './vite/platform-configs.js';
6
+ /**
7
+ * Vite 构建器 - 使用 Vite 构建 Playable Ads
8
+ *
9
+ * 职责:
10
+ * 1. 验证输入是有效的基础构建
11
+ * 2. 创建 Vite 配置
12
+ * 3. 执行 Vite 构建
13
+ * 4. 验证输出大小
14
+ * 5. 生成报告
15
+ */
16
+ export class ViteBuilder {
17
+ constructor(baseBuildDir, options) {
18
+ this.baseBuildDir = baseBuildDir;
19
+ this.options = options;
20
+ const platformConfig = PLATFORM_CONFIGS[options.platform];
21
+ this.sizeReport = {
22
+ engine: 0,
23
+ assets: {},
24
+ total: 0,
25
+ limit: platformConfig.sizeLimit,
26
+ };
27
+ }
28
+ /**
29
+ * 执行构建
30
+ */
31
+ async build() {
32
+ // 1. 验证输入
33
+ await this.validateBaseBuild();
34
+ // 2. 创建 Vite 配置
35
+ const configBuilder = new ViteConfigBuilder(this.baseBuildDir, this.options.platform, this.options);
36
+ const viteConfig = configBuilder.create();
37
+ // 3. 执行 Vite 构建
38
+ await viteBuild(viteConfig);
39
+ // 4. 验证输出大小
40
+ const outputPath = this.getOutputPath();
41
+ await this.validateSize(outputPath);
42
+ // 5. 生成报告
43
+ this.generateReport(outputPath);
44
+ return outputPath;
45
+ }
46
+ /**
47
+ * 验证基础构建
48
+ */
49
+ async validateBaseBuild() {
50
+ const requiredFiles = [
51
+ 'index.html',
52
+ 'config.json',
53
+ '__start__.js',
54
+ ];
55
+ const missingFiles = [];
56
+ for (const file of requiredFiles) {
57
+ try {
58
+ await fs.access(path.join(this.baseBuildDir, file));
59
+ }
60
+ catch (error) {
61
+ missingFiles.push(file);
62
+ }
63
+ }
64
+ if (missingFiles.length > 0) {
65
+ throw new Error(`基础构建产物缺少必需文件: ${missingFiles.join(', ')}\n` +
66
+ `请确保输入目录包含完整的多文件构建产物。`);
67
+ }
68
+ }
69
+ /**
70
+ * 获取输出路径
71
+ */
72
+ getOutputPath() {
73
+ const platformConfig = PLATFORM_CONFIGS[this.options.platform];
74
+ const outputDir = this.options.outputDir || './dist';
75
+ if (platformConfig.outputFormat === 'zip') {
76
+ return path.join(outputDir, 'playable.zip');
77
+ }
78
+ return path.join(outputDir, platformConfig.outputFileName);
79
+ }
80
+ /**
81
+ * 验证输出大小
82
+ */
83
+ async validateSize(outputPath) {
84
+ try {
85
+ const stats = await fs.stat(outputPath);
86
+ this.sizeReport.total = stats.size;
87
+ this.sizeReport.assets[path.basename(outputPath)] = stats.size;
88
+ if (stats.size > this.sizeReport.limit) {
89
+ const sizeMB = (stats.size / 1024 / 1024).toFixed(2);
90
+ const limitMB = (this.sizeReport.limit / 1024 / 1024).toFixed(2);
91
+ console.warn(`⚠️ 警告: 文件大小 ${sizeMB} MB 超过限制 ${limitMB} MB`);
92
+ }
93
+ }
94
+ catch (error) {
95
+ console.warn(`警告: 无法读取输出文件: ${outputPath}`);
96
+ }
97
+ }
98
+ /**
99
+ * 生成报告
100
+ */
101
+ generateReport(outputPath) {
102
+ // 报告已在 validateSize 中生成
103
+ // 这里可以添加额外的报告逻辑
104
+ }
105
+ /**
106
+ * 获取大小报告
107
+ */
108
+ getSizeReport() {
109
+ return this.sizeReport;
110
+ }
111
+ /**
112
+ * 格式化字节数
113
+ */
114
+ formatBytes(bytes) {
115
+ if (bytes === 0)
116
+ return '0 Bytes';
117
+ const k = 1024;
118
+ const sizes = ['Bytes', 'KB', 'MB', 'GB'];
119
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
120
+ return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i];
121
+ }
122
+ }
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@playcraft/build",
3
+ "version": "0.0.2",
4
+ "private": false,
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ },
13
+ "./types": {
14
+ "import": "./dist/types.js",
15
+ "types": "./dist/types.d.ts"
16
+ }
17
+ },
18
+ "files": [
19
+ "dist",
20
+ "templates",
21
+ "README.md"
22
+ ],
23
+ "scripts": {
24
+ "dev": "tsc -w",
25
+ "build": "tsc",
26
+ "release": "node scripts/release.js"
27
+ },
28
+ "dependencies": {
29
+ "@gltf-transform/core": "^4.0.0",
30
+ "@gltf-transform/extensions": "^4.0.0",
31
+ "@gltf-transform/functions": "^4.0.0",
32
+ "@vheemstra/vite-plugin-imagemin": "^2.0.0",
33
+ "archiver": "^7.0.1",
34
+ "draco3dgltf": "^1.5.7",
35
+ "imagemin-mozjpeg": "^10.0.0",
36
+ "imagemin-pngquant": "^10.0.0",
37
+ "imagemin-webp": "^8.0.0",
38
+ "lightningcss": "^1.27.0",
39
+ "mime-types": "^2.1.35",
40
+ "rollup-plugin-visualizer": "^6.0.5",
41
+ "terser": "^5.30.0",
42
+ "vite": "^6.0.0",
43
+ "vite-plugin-singlefile": "^2.0.0"
44
+ },
45
+ "devDependencies": {
46
+ "@types/archiver": "^6.0.4",
47
+ "@types/mime-types": "^2.1.4",
48
+ "@types/node": "^22.10.5",
49
+ "typescript": "^5.7.2"
50
+ }
51
+ }
@@ -0,0 +1,100 @@
1
+ pc.script.createLoadingScreen((app) => {
2
+ const createCss = () => {
3
+ const css = `
4
+ body {
5
+ background-color: #283538;
6
+ }
7
+
8
+ #application-splash-wrapper {
9
+ position: absolute;
10
+ top: 0;
11
+ left: 0;
12
+ height: 100%;
13
+ width: 100%;
14
+ background-color: #283538;
15
+ }
16
+
17
+ #application-splash {
18
+ position: absolute;
19
+ top: calc(50% - 28px);
20
+ width: 264px;
21
+ left: calc(50% - 132px);
22
+ }
23
+
24
+ #application-splash img {
25
+ width: 100%;
26
+ }
27
+
28
+ #progress-bar-container {
29
+ margin: 20px auto 0 auto;
30
+ height: 2px;
31
+ width: 100%;
32
+ background-color: #1d292c;
33
+ }
34
+
35
+ #progress-bar {
36
+ width: 0%;
37
+ height: 100%;
38
+ background-color: #f60;
39
+ }
40
+
41
+ @media (max-width: 480px) {
42
+ #application-splash {
43
+ width: 170px;
44
+ left: calc(50% - 85px);
45
+ }
46
+ }
47
+ `;
48
+
49
+ const style = document.createElement('style');
50
+ style.textContent = css;
51
+ document.head.appendChild(style);
52
+ };
53
+
54
+ const showSplash = () => {
55
+ const wrapper = document.createElement('div');
56
+ wrapper.id = 'application-splash-wrapper';
57
+ document.body.appendChild(wrapper);
58
+
59
+ const splash = document.createElement('div');
60
+ splash.id = 'application-splash';
61
+ wrapper.appendChild(splash);
62
+ splash.style.display = 'none';
63
+
64
+ const logo = document.createElement('img');
65
+ logo.src = `${ASSET_PREFIX}logo.png`;
66
+ splash.appendChild(logo);
67
+ logo.onload = () => {
68
+ splash.style.display = 'block';
69
+ };
70
+
71
+ const container = document.createElement('div');
72
+ container.id = 'progress-bar-container';
73
+ splash.appendChild(container);
74
+
75
+ const bar = document.createElement('div');
76
+ bar.id = 'progress-bar';
77
+ container.appendChild(bar);
78
+ };
79
+
80
+ const setProgress = (value) => {
81
+ const bar = document.getElementById('progress-bar');
82
+ if (bar) {
83
+ value = Math.min(1, Math.max(0, value));
84
+ bar.style.width = `${value * 100}%`;
85
+ }
86
+ };
87
+
88
+ const hideSplash = () => {
89
+ document.getElementById('application-splash-wrapper').remove();
90
+ };
91
+
92
+ createCss();
93
+ showSplash();
94
+
95
+ app.on('preload:end', () => {
96
+ app.off('preload:progress');
97
+ });
98
+ app.on('preload:progress', setProgress);
99
+ app.on('start', hideSplash);
100
+ });