@playcraft/build 0.0.13 → 0.0.14
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/dist/analyzers/scene-asset-collector.js +99 -9
- package/dist/base-builder.d.ts +15 -78
- package/dist/base-builder.js +34 -741
- package/dist/engines/engine-detector.d.ts +38 -0
- package/dist/engines/engine-detector.js +201 -0
- package/dist/engines/generic-adapter.d.ts +71 -0
- package/dist/engines/generic-adapter.js +378 -0
- package/dist/engines/index.d.ts +7 -0
- package/dist/engines/index.js +7 -0
- package/dist/engines/playcanvas-adapter.d.ts +85 -0
- package/dist/engines/playcanvas-adapter.js +813 -0
- package/dist/generators/config-generator.js +59 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/loaders/playcraft-loader.js +240 -5
- package/dist/platforms/adikteev.d.ts +1 -1
- package/dist/platforms/adikteev.js +30 -36
- package/dist/platforms/applovin.d.ts +1 -1
- package/dist/platforms/applovin.js +31 -36
- package/dist/platforms/base.d.ts +27 -5
- package/dist/platforms/base.js +79 -181
- package/dist/platforms/bigo.d.ts +1 -1
- package/dist/platforms/bigo.js +28 -28
- package/dist/platforms/facebook.d.ts +1 -1
- package/dist/platforms/facebook.js +21 -10
- package/dist/platforms/google.d.ts +1 -1
- package/dist/platforms/google.js +28 -21
- package/dist/platforms/index.d.ts +1 -0
- package/dist/platforms/index.js +4 -0
- package/dist/platforms/inmobi.d.ts +1 -1
- package/dist/platforms/inmobi.js +27 -34
- package/dist/platforms/ironsource.d.ts +1 -1
- package/dist/platforms/ironsource.js +37 -40
- package/dist/platforms/liftoff.d.ts +1 -1
- package/dist/platforms/liftoff.js +22 -30
- package/dist/platforms/mintegral.d.ts +10 -0
- package/dist/platforms/mintegral.js +65 -0
- package/dist/platforms/moloco.d.ts +1 -1
- package/dist/platforms/moloco.js +18 -20
- package/dist/platforms/playcraft.d.ts +1 -1
- package/dist/platforms/playcraft.js +2 -2
- package/dist/platforms/remerge.d.ts +1 -1
- package/dist/platforms/remerge.js +19 -20
- package/dist/platforms/snapchat.d.ts +1 -1
- package/dist/platforms/snapchat.js +32 -26
- package/dist/platforms/tiktok.d.ts +1 -1
- package/dist/platforms/tiktok.js +28 -24
- package/dist/platforms/unity.d.ts +1 -1
- package/dist/platforms/unity.js +30 -36
- package/dist/playable-builder.d.ts +1 -0
- package/dist/playable-builder.js +16 -2
- package/dist/templates/__loading__.js +100 -0
- package/dist/templates/__modules__.js +47 -0
- package/dist/templates/__settings__.template.js +20 -0
- package/dist/templates/__start__.js +332 -0
- package/dist/templates/index.html +18 -0
- package/dist/templates/logo.png +0 -0
- package/dist/templates/manifest.json +1 -0
- package/dist/templates/patches/cannon.min.js +28 -0
- package/dist/templates/patches/lz4.js +10 -0
- package/dist/templates/patches/one-page-http-get.js +20 -0
- package/dist/templates/patches/one-page-inline-game-scripts.js +52 -0
- package/dist/templates/patches/one-page-mraid-resize-canvas.js +46 -0
- package/dist/templates/patches/p2.min.js +27 -0
- package/dist/templates/patches/playcraft-no-xhr.js +76 -0
- package/dist/templates/playcanvas-stable.min.js +16363 -0
- package/dist/templates/styles.css +43 -0
- package/dist/types.d.ts +113 -1
- package/dist/types.js +77 -1
- package/dist/utils/ammo-detector.d.ts +9 -0
- package/dist/utils/ammo-detector.js +76 -0
- package/dist/utils/build-mode-detector.js +2 -0
- package/dist/utils/minify.d.ts +32 -0
- package/dist/utils/minify.js +82 -0
- package/dist/vite/config-builder-generic.d.ts +70 -0
- package/dist/vite/config-builder-generic.js +251 -0
- package/dist/vite/config-builder.d.ts +8 -0
- package/dist/vite/config-builder.js +53 -16
- package/dist/vite/platform-configs.js +29 -1
- package/dist/vite/plugin-compress-js.d.ts +21 -0
- package/dist/vite/plugin-compress-js.js +213 -0
- package/dist/vite/plugin-esm-html-generator.js +5 -1
- package/dist/vite/plugin-platform.d.ts +5 -0
- package/dist/vite/plugin-platform.js +499 -35
- package/dist/vite/plugin-playcanvas.js +21 -68
- package/dist/vite/plugin-source-builder.js +102 -21
- package/dist/vite-builder.d.ts +25 -7
- package/dist/vite-builder.js +141 -52
- package/package.json +4 -2
- package/physics/cannon-rigidbody-adapter.js +243 -22
- package/templates/__loading__.js +0 -12
- package/templates/index.esm.mjs +0 -11
- package/templates/patches/playcraft-cta-adapter.js +129 -31
- package/templates/patches/scene-physics-defaults.js +49 -0
- package/dist/vite/plugin-template-minifier.d.ts +0 -20
- package/dist/vite/plugin-template-minifier.js +0 -392
|
@@ -2,6 +2,7 @@ import fs from 'fs/promises';
|
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
4
|
import { createRequire } from 'module';
|
|
5
|
+
import { minifyPatchCode } from '../utils/minify.js';
|
|
5
6
|
/**
|
|
6
7
|
* PlayCanvas Vite 插件
|
|
7
8
|
* 处理 PlayCanvas 特定的资源转换和内联
|
|
@@ -302,42 +303,7 @@ async function inlineESMBundleAssets(html, baseBuildDir, options, pluginContext)
|
|
|
302
303
|
sceneDataUrl = sceneUrl || '';
|
|
303
304
|
}
|
|
304
305
|
}
|
|
305
|
-
// 7.
|
|
306
|
-
// index.esm.mjs 中使用 `${ASSET_PREFIX}logo.png` 引用 logo
|
|
307
|
-
// 需要将 ASSET_PREFIX 替换为 data URL 或直接替换 logo 引用
|
|
308
|
-
if (options.convertDataUrls) {
|
|
309
|
-
const logoPath = path.join(baseBuildDir, 'logo.png');
|
|
310
|
-
try {
|
|
311
|
-
const logoBuffer = await fs.readFile(logoPath);
|
|
312
|
-
const logoDataUrl = `data:image/png;base64,${logoBuffer.toString('base64')}`;
|
|
313
|
-
// 替换 `${ASSET_PREFIX}logo.png` 模式
|
|
314
|
-
// 由于代码被打包为 IIFE,模板字符串可能被转换为字符串拼接
|
|
315
|
-
// 格式1: `${ASSET_PREFIX}logo.png`
|
|
316
|
-
// 格式2: ASSET_PREFIX + "logo.png"
|
|
317
|
-
// 格式3: "".concat(ASSET_PREFIX, "logo.png") (Vite 打包后)
|
|
318
|
-
const escapedLogoDataUrl = logoDataUrl.replace(/\$/g, '$$$$');
|
|
319
|
-
// 替换模板字符串格式(原始格式)
|
|
320
|
-
html = html.replace(/\$\{ASSET_PREFIX\}logo\.png/g, escapedLogoDataUrl);
|
|
321
|
-
// 替换字符串拼接格式
|
|
322
|
-
html = html.replace(/ASSET_PREFIX\s*\+\s*["']logo\.png["']/g, `"${escapedLogoDataUrl}"`);
|
|
323
|
-
// 替换 concat 格式 (Vite/Rollup 打包后常见格式)
|
|
324
|
-
html = html.replace(/["']?["']?\.concat\(ASSET_PREFIX,\s*["']logo\.png["']\)/g, `"${escapedLogoDataUrl}"`);
|
|
325
|
-
// 替换反引号模板字符串在 IIFE 中的编译结果
|
|
326
|
-
// `${ASSET_PREFIX}logo.png` 可能被编译为 ASSET_PREFIX+"logo.png"
|
|
327
|
-
html = html.replace(/`\$\{ASSET_PREFIX\}logo\.png`/g, `"${escapedLogoDataUrl}"`);
|
|
328
|
-
// ⚠️ 重要:Vite 压缩后,直接查找 "logo.png" 字符串
|
|
329
|
-
// 格式: .src="logo.png" 或 src:"logo.png" 或 ="logo.png"
|
|
330
|
-
const logoReplaceCount = (html.match(/["']logo\.png["']/g) || []).length;
|
|
331
|
-
if (logoReplaceCount > 0) {
|
|
332
|
-
html = html.replace(/["']logo\.png["']/g, `"${escapedLogoDataUrl}"`);
|
|
333
|
-
console.log(`[PlayCanvasPlugin] ESM Bundle: 替换了 ${logoReplaceCount} 处 "logo.png" 字符串`);
|
|
334
|
-
}
|
|
335
|
-
console.log(`[PlayCanvasPlugin] ESM Bundle: logo.png 已内联`);
|
|
336
|
-
}
|
|
337
|
-
catch (error) {
|
|
338
|
-
console.warn(`[PlayCanvasPlugin] ESM Bundle: logo.png 不存在,跳过内联`);
|
|
339
|
-
}
|
|
340
|
-
}
|
|
306
|
+
// 7. (Logo removed - no longer needed)
|
|
341
307
|
// 8. 在 HTML 中查找并替换 ESM Bundle IIFE 代码中的配置值
|
|
342
308
|
// 方案1+3:直接将 CONFIG_FILENAME = "config.json" 替换为 data URL
|
|
343
309
|
// 8.1 替换 CONFIG_FILENAME
|
|
@@ -425,28 +391,22 @@ async function inlineESMBundleAssets(html, baseBuildDir, options, pluginContext)
|
|
|
425
391
|
// 这个补丁覆盖 pc.ScriptHandler.prototype._loadScript,使其能够:
|
|
426
392
|
// 1. 正确处理 data URL 格式的脚本
|
|
427
393
|
// 2. 对于空脚本(已打包到 IIFE 中的),直接跳过执行
|
|
428
|
-
const
|
|
429
|
-
<script>
|
|
430
|
-
// ScriptHandler patch for one-page build (empty scripts are already bundled in IIFE)
|
|
394
|
+
const scriptHandlerPatchCode = `
|
|
431
395
|
(function() {
|
|
432
396
|
if (typeof pc === 'undefined' || !pc.ScriptHandler) return;
|
|
433
397
|
|
|
434
398
|
var originalLoadScript = pc.ScriptHandler.prototype._loadScript;
|
|
435
399
|
pc.ScriptHandler.prototype._loadScript = function(url, callback) {
|
|
436
|
-
// Check if this is a data URL
|
|
437
400
|
if (url && url.startsWith('data:text/javascript;base64,')) {
|
|
438
401
|
var head = document.head;
|
|
439
402
|
var element = document.createElement('script');
|
|
440
403
|
this._cache[url] = element;
|
|
441
404
|
element.async = false;
|
|
442
405
|
|
|
443
|
-
// Decode base64 content
|
|
444
406
|
var index = url.indexOf(',');
|
|
445
407
|
var base64 = url.slice(index + 1);
|
|
446
408
|
|
|
447
|
-
// If empty content (scripts already bundled), skip execution
|
|
448
409
|
if (!base64 || base64.length === 0) {
|
|
449
|
-
// Still create the element for cache consistency
|
|
450
410
|
callback(null, url, element);
|
|
451
411
|
return;
|
|
452
412
|
}
|
|
@@ -463,14 +423,13 @@ async function inlineESMBundleAssets(html, baseBuildDir, options, pluginContext)
|
|
|
463
423
|
return;
|
|
464
424
|
}
|
|
465
425
|
|
|
466
|
-
// Fall back to original implementation for non-data URLs
|
|
467
426
|
return originalLoadScript.call(this, url, callback);
|
|
468
427
|
};
|
|
469
|
-
})()
|
|
470
|
-
|
|
428
|
+
})();`;
|
|
429
|
+
const minifiedScriptHandlerPatch = await minifyPatchCode(scriptHandlerPatchCode, 'scriptHandlerPatch');
|
|
471
430
|
// 将补丁注入到 </head> 之前,确保在引擎之后、脚本加载之前执行
|
|
472
431
|
if (html.includes('</head>')) {
|
|
473
|
-
html = html.replace('</head>',
|
|
432
|
+
html = html.replace('</head>', `<script>${minifiedScriptHandlerPatch}</script>\n</head>`);
|
|
474
433
|
}
|
|
475
434
|
console.log('[PlayCanvasPlugin] ESM Bundle 模式:资源内联完成');
|
|
476
435
|
return html;
|
|
@@ -759,9 +718,6 @@ async function inlineGameScripts(html, baseBuildDir) {
|
|
|
759
718
|
async function inlineStartScript(html, baseBuildDir, options) {
|
|
760
719
|
const startPath = path.join(baseBuildDir, '__start__.js');
|
|
761
720
|
let startCode = await fs.readFile(startPath, 'utf-8');
|
|
762
|
-
if (options.convertDataUrls) {
|
|
763
|
-
startCode = await inlineLogoInStartScript(startCode, baseBuildDir);
|
|
764
|
-
}
|
|
765
721
|
// 替换 script 标签
|
|
766
722
|
const scriptPattern = /<script[^>]*src=["']__start__\.js["'][^>]*><\/script>/i;
|
|
767
723
|
if (scriptPattern.test(html)) {
|
|
@@ -783,9 +739,6 @@ async function inlineLoadingScript(html, baseBuildDir) {
|
|
|
783
739
|
await fs.access(loadingPath);
|
|
784
740
|
// 文件存在,内联它
|
|
785
741
|
let loadingCode = await fs.readFile(loadingPath, 'utf-8');
|
|
786
|
-
if (loadingCode.includes('${ASSET_PREFIX}logo.png')) {
|
|
787
|
-
loadingCode = await inlineLogoInStartScript(loadingCode, baseBuildDir);
|
|
788
|
-
}
|
|
789
742
|
if (scriptPattern.test(html)) {
|
|
790
743
|
html = html.replace(scriptPattern, `<script>${loadingCode}</script>`);
|
|
791
744
|
}
|
|
@@ -1177,17 +1130,6 @@ async function inlineConfigAssetUrls(configJson, baseBuildDir, pluginContext) {
|
|
|
1177
1130
|
}
|
|
1178
1131
|
return configJson;
|
|
1179
1132
|
}
|
|
1180
|
-
async function inlineLogoInStartScript(startCode, baseBuildDir) {
|
|
1181
|
-
const logoPath = path.join(baseBuildDir, 'logo.png');
|
|
1182
|
-
try {
|
|
1183
|
-
const buffer = await fs.readFile(logoPath);
|
|
1184
|
-
const dataUrl = `data:image/png;base64,${buffer.toString('base64')}`;
|
|
1185
|
-
return startCode.replace(/\$\{ASSET_PREFIX\}logo\.png/g, dataUrl);
|
|
1186
|
-
}
|
|
1187
|
-
catch (error) {
|
|
1188
|
-
return startCode;
|
|
1189
|
-
}
|
|
1190
|
-
}
|
|
1191
1133
|
/**
|
|
1192
1134
|
* 为跳过的资源生成有效的占位符 data URL
|
|
1193
1135
|
*/
|
|
@@ -1387,19 +1329,30 @@ async function getEnginePatchScripts(options) {
|
|
|
1387
1329
|
const scripts = [];
|
|
1388
1330
|
if (options.patchXhrOut) {
|
|
1389
1331
|
const patchCode = await readPatchFile('playcraft-no-xhr.js');
|
|
1390
|
-
|
|
1332
|
+
const minified = await minifyPatchCode(patchCode, 'playcraft-no-xhr.js');
|
|
1333
|
+
scripts.push(`<script>${minified}</script>`);
|
|
1391
1334
|
}
|
|
1392
1335
|
else if (options.configJsonInline) {
|
|
1393
1336
|
const patchCode = await readPatchFile('one-page-http-get.js');
|
|
1394
|
-
|
|
1337
|
+
const minified = await minifyPatchCode(patchCode, 'one-page-http-get.js');
|
|
1338
|
+
scripts.push(`<script>${minified}</script>`);
|
|
1395
1339
|
}
|
|
1396
1340
|
if (options.inlineGameScripts) {
|
|
1397
1341
|
const patchCode = await readPatchFile('one-page-inline-game-scripts.js');
|
|
1398
|
-
scripts.
|
|
1342
|
+
const minified = await minifyPatchCode(patchCode, 'one-page-inline-game-scripts.js');
|
|
1343
|
+
scripts.push(`<script>${minified}</script>`);
|
|
1399
1344
|
}
|
|
1400
1345
|
if (options.mraidSupport) {
|
|
1401
1346
|
const patchCode = await readPatchFile('one-page-mraid-resize-canvas.js');
|
|
1402
|
-
|
|
1347
|
+
const minified = await minifyPatchCode(patchCode, 'one-page-mraid-resize-canvas.js');
|
|
1348
|
+
scripts.push(`<script>${minified}</script>`);
|
|
1349
|
+
}
|
|
1350
|
+
// 注入 Scene applySettings 补丁:确保 settings.physics/render 存在,避免 "Cannot read properties of undefined (reading 'gravity')" 报错
|
|
1351
|
+
// 场景:ammoReplacement 或部分场景 JSON 缺少 physics 配置
|
|
1352
|
+
const scenePhysicsPatch = await readPatchFile('scene-physics-defaults.js');
|
|
1353
|
+
if (scenePhysicsPatch) {
|
|
1354
|
+
const minified = await minifyPatchCode(scenePhysicsPatch, 'scene-physics-defaults.js');
|
|
1355
|
+
scripts.push(`<script>${minified}</script>`);
|
|
1403
1356
|
}
|
|
1404
1357
|
// 如果启用了 config.json 压缩,需要注入 LZ4 解压运行时代码
|
|
1405
1358
|
// 注意:必须在 compressEngine 之前检查,因为 compressEngine 也会注入 lz4.js
|
|
@@ -52,6 +52,39 @@ export function viteSourceBuilderPlugin(options) {
|
|
|
52
52
|
if (scriptPathMap.has(id)) {
|
|
53
53
|
return scriptPathMap.get(id);
|
|
54
54
|
}
|
|
55
|
+
// 通过 Import Map 解析裸模块说明符(如 gameRule、planck 等)
|
|
56
|
+
// Import Map 中的路径是相对于 assets/ 目录的(Import Map 文件本身在 assets/Import Map.json 中)
|
|
57
|
+
if (options.importMap?.content?.imports && !id.startsWith('./') && !id.startsWith('../') && !id.startsWith('/') && !id.startsWith('\0')) {
|
|
58
|
+
const imports = options.importMap.content.imports;
|
|
59
|
+
const assetsDir = path.join(options.projectDir, 'assets');
|
|
60
|
+
// 精确匹配(如 "gameRule" -> "./Generated/GameRule/BreakshotPoolRule.mjs")
|
|
61
|
+
if (imports[id]) {
|
|
62
|
+
let resolved = imports[id];
|
|
63
|
+
// 去掉开头的 ./
|
|
64
|
+
if (resolved.startsWith('./')) {
|
|
65
|
+
resolved = resolved.slice(2);
|
|
66
|
+
}
|
|
67
|
+
const absPath = path.join(assetsDir, resolved);
|
|
68
|
+
console.log(`[SourceBuilder] Import Map 解析: ${id} -> ${absPath}`);
|
|
69
|
+
return absPath;
|
|
70
|
+
}
|
|
71
|
+
// 前缀匹配(如 "Gameplay/" -> "./LiteCreator/Gameplay/")
|
|
72
|
+
for (const [prefix, target] of Object.entries(imports)) {
|
|
73
|
+
if (prefix.endsWith('/') && id.startsWith(prefix)) {
|
|
74
|
+
const remainder = id.slice(prefix.length);
|
|
75
|
+
let targetBase = target;
|
|
76
|
+
if (targetBase.startsWith('./')) {
|
|
77
|
+
targetBase = targetBase.slice(2);
|
|
78
|
+
}
|
|
79
|
+
if (targetBase.endsWith('/')) {
|
|
80
|
+
targetBase = targetBase.slice(0, -1);
|
|
81
|
+
}
|
|
82
|
+
const absPath = path.join(assetsDir, targetBase, remainder);
|
|
83
|
+
console.log(`[SourceBuilder] Import Map 前缀解析: ${id} -> ${absPath}`);
|
|
84
|
+
return absPath;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
55
88
|
// 处理相对导入(../xxx 或 ./xxx)
|
|
56
89
|
if (importer && (id.startsWith('./') || id.startsWith('../'))) {
|
|
57
90
|
// 获取导入者的模块路径
|
|
@@ -277,7 +310,6 @@ async function copyTemplates(outputDir) {
|
|
|
277
310
|
'__loading__.js',
|
|
278
311
|
'playcanvas-stable.min.js',
|
|
279
312
|
'styles.css',
|
|
280
|
-
'logo.png',
|
|
281
313
|
'manifest.json',
|
|
282
314
|
];
|
|
283
315
|
for (const file of templateFiles) {
|
|
@@ -373,11 +405,21 @@ function collectRegisteredScriptNames(assets) {
|
|
|
373
405
|
if (scriptName) {
|
|
374
406
|
scriptNames.add(scriptName);
|
|
375
407
|
}
|
|
376
|
-
//
|
|
377
|
-
if (asset.data?.scripts
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
408
|
+
// Classic 格式的 data.scripts 可能是对象或数组
|
|
409
|
+
if (asset.data?.scripts) {
|
|
410
|
+
if (Array.isArray(asset.data.scripts)) {
|
|
411
|
+
// 数组格式: [{ name: 'xxx' }, ...]
|
|
412
|
+
for (const s of asset.data.scripts) {
|
|
413
|
+
if (s?.name)
|
|
414
|
+
scriptNames.add(s.name);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
else if (typeof asset.data.scripts === 'object') {
|
|
418
|
+
// 对象格式: { "followCamera": { attributes: {...} }, ... }
|
|
419
|
+
// 这是 PlayCanvas/PlayCraft 中 Classic 脚本的标准格式
|
|
420
|
+
for (const scriptKey of Object.keys(asset.data.scripts)) {
|
|
421
|
+
scriptNames.add(scriptKey);
|
|
422
|
+
}
|
|
381
423
|
}
|
|
382
424
|
}
|
|
383
425
|
}
|
|
@@ -542,8 +584,28 @@ async function normalizeAssetUrls(projectConfig, projectDir) {
|
|
|
542
584
|
let notFoundCount = 0;
|
|
543
585
|
for (const [assetId, asset] of Object.entries(assets)) {
|
|
544
586
|
const assetData = asset;
|
|
545
|
-
//
|
|
546
|
-
if (!assetData.file
|
|
587
|
+
// 跳过没有 file 的资源
|
|
588
|
+
if (!assetData.file) {
|
|
589
|
+
continue;
|
|
590
|
+
}
|
|
591
|
+
// 如果已有有效的本地 url(非 API URL),验证文件是否存在
|
|
592
|
+
if (assetData.file.url && !assetData.file.url.startsWith('/api/') && !assetData.file.url.startsWith('http')) {
|
|
593
|
+
// 已有有效的本地 url,验证文件存在性
|
|
594
|
+
try {
|
|
595
|
+
await fs.access(path.join(projectDir, assetData.file.url));
|
|
596
|
+
continue; // 文件存在,跳过
|
|
597
|
+
}
|
|
598
|
+
catch {
|
|
599
|
+
// 文件不存在,清除 url 继续搜索
|
|
600
|
+
delete assetData.file.url;
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
// 如果 url 是 API URL(PlayCraft 格式残留),清除它
|
|
604
|
+
if (assetData.file.url && (assetData.file.url.startsWith('/api/') || assetData.file.url.startsWith('http'))) {
|
|
605
|
+
delete assetData.file.url;
|
|
606
|
+
}
|
|
607
|
+
// 如果已经有有效 url 了(由之前的 loader 设置的),跳过
|
|
608
|
+
if (assetData.file.url) {
|
|
547
609
|
continue;
|
|
548
610
|
}
|
|
549
611
|
const filename = assetData.file.filename;
|
|
@@ -552,11 +614,19 @@ async function normalizeAssetUrls(projectConfig, projectDir) {
|
|
|
552
614
|
}
|
|
553
615
|
const revision = assetData.revision ?? assetData.file.revision ?? 1;
|
|
554
616
|
const candidates = [
|
|
617
|
+
// PlayCanvas 标准结构
|
|
555
618
|
path.join('files', 'assets', String(assetId), String(revision), filename),
|
|
556
619
|
path.join('files', 'assets', String(assetId), '1', filename),
|
|
557
620
|
path.join('files', 'assets', String(assetId), filename),
|
|
558
621
|
path.join('files', filename),
|
|
559
622
|
];
|
|
623
|
+
// PlayCraft 结构:使用 treePath 或 path 字段推断文件位置
|
|
624
|
+
if (assetData.treePath) {
|
|
625
|
+
candidates.push(path.join(assetData.treePath, filename));
|
|
626
|
+
}
|
|
627
|
+
if (assetData.path && typeof assetData.path === 'string') {
|
|
628
|
+
candidates.push(assetData.path);
|
|
629
|
+
}
|
|
560
630
|
let found = false;
|
|
561
631
|
for (const rel of candidates) {
|
|
562
632
|
const abs = path.join(projectDir, rel);
|
|
@@ -699,7 +769,6 @@ async function generateESMTemplate(projectConfig, options, config) {
|
|
|
699
769
|
'__modules__.js',
|
|
700
770
|
'__loading__.js',
|
|
701
771
|
'styles.css',
|
|
702
|
-
'logo.png',
|
|
703
772
|
'manifest.json',
|
|
704
773
|
];
|
|
705
774
|
for (const file of templateFiles) {
|
|
@@ -737,12 +806,19 @@ function fixImportMapPaths(importMap) {
|
|
|
737
806
|
* 收集 ESM 脚本的导入路径
|
|
738
807
|
*/
|
|
739
808
|
async function collectESMScriptImports(projectConfig, options) {
|
|
740
|
-
|
|
741
|
-
|
|
809
|
+
// 获取脚本 ID 列表和资产(支持 PlayCanvas 和 PlayCraft 两种格式)
|
|
810
|
+
let scriptIds;
|
|
811
|
+
let assets;
|
|
812
|
+
if (projectConfig.format === 'playcanvas') {
|
|
813
|
+
const pcProject = projectConfig;
|
|
814
|
+
scriptIds = pcProject.project.settings?.scripts || [];
|
|
815
|
+
assets = pcProject.assets;
|
|
816
|
+
}
|
|
817
|
+
else {
|
|
818
|
+
const pcProject = projectConfig;
|
|
819
|
+
scriptIds = pcProject.manifest.settings?.scripts || [];
|
|
820
|
+
assets = pcProject.assets;
|
|
742
821
|
}
|
|
743
|
-
const pcProject = projectConfig;
|
|
744
|
-
const scriptIds = pcProject.project.settings?.scripts || [];
|
|
745
|
-
const assets = pcProject.assets;
|
|
746
822
|
const importPaths = [];
|
|
747
823
|
// 构建文件夹 ID 到名称的映射
|
|
748
824
|
const folderMap = new Map();
|
|
@@ -868,14 +944,19 @@ async function generateESMEntry(projectConfig, outputDir, scriptImportPaths = []
|
|
|
868
944
|
async function copyUserScriptsForESM(projectConfig, projectDir, outputDir, importMap) {
|
|
869
945
|
const scriptUrlMap = new Map();
|
|
870
946
|
console.log(`[copyUserScriptsForESM] 项目格式: ${projectConfig.format}`);
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
947
|
+
// 获取脚本 ID 列表和资产(支持 PlayCanvas 和 PlayCraft 两种格式)
|
|
948
|
+
let scriptIds;
|
|
949
|
+
let assets;
|
|
950
|
+
if (projectConfig.format === 'playcanvas') {
|
|
951
|
+
const pcProject = projectConfig;
|
|
952
|
+
scriptIds = pcProject.project.settings?.scripts || [];
|
|
953
|
+
assets = pcProject.assets;
|
|
954
|
+
}
|
|
955
|
+
else {
|
|
956
|
+
const pcProject = projectConfig;
|
|
957
|
+
scriptIds = pcProject.manifest.settings?.scripts || [];
|
|
958
|
+
assets = pcProject.assets;
|
|
875
959
|
}
|
|
876
|
-
const pcProject = projectConfig;
|
|
877
|
-
const scriptIds = pcProject.project.settings?.scripts || [];
|
|
878
|
-
const assets = pcProject.assets;
|
|
879
960
|
console.log(`[copyUserScriptsForESM] 脚本数量: ${scriptIds.length}`);
|
|
880
961
|
// 构建文件夹 ID 到名称的映射
|
|
881
962
|
const folderMap = new Map();
|
package/dist/vite-builder.d.ts
CHANGED
|
@@ -10,37 +10,55 @@ export interface ViteBuildOutput {
|
|
|
10
10
|
*
|
|
11
11
|
* 职责:
|
|
12
12
|
* 1. 验证输入是有效的基础构建
|
|
13
|
-
* 2.
|
|
13
|
+
* 2. 根据引擎类型创建对应的 Vite 配置
|
|
14
14
|
* 3. 执行 Vite 构建
|
|
15
15
|
* 4. 将输出复制到最终目录
|
|
16
16
|
* 5. 验证输出大小
|
|
17
17
|
* 6. 生成报告
|
|
18
|
+
*
|
|
19
|
+
* 引擎路由:
|
|
20
|
+
* - PlayCanvas:使用 ViteConfigBuilder(处理 config.json、__start__.js 等)
|
|
21
|
+
* - 其他引擎:使用 GenericViteConfigBuilder(通用 HTML 内联 + 平台 SDK 注入)
|
|
18
22
|
*/
|
|
19
23
|
export declare class ViteBuilder {
|
|
20
24
|
private baseBuildDir;
|
|
21
25
|
private options;
|
|
22
26
|
private sizeReport;
|
|
27
|
+
private engine;
|
|
23
28
|
constructor(baseBuildDir: string, options: BuildOptions);
|
|
24
29
|
/**
|
|
25
30
|
* 执行构建
|
|
26
31
|
*/
|
|
27
32
|
build(): Promise<string>;
|
|
28
33
|
/**
|
|
29
|
-
*
|
|
34
|
+
* 读取构建元数据
|
|
30
35
|
*/
|
|
31
|
-
private
|
|
36
|
+
private readBuildMetadata;
|
|
32
37
|
/**
|
|
33
|
-
*
|
|
38
|
+
* 验证基础构建
|
|
39
|
+
* 根据引擎类型使用不同的验证规则
|
|
34
40
|
*/
|
|
35
|
-
private
|
|
41
|
+
private validateBaseBuild;
|
|
42
|
+
/**
|
|
43
|
+
* 验证 PlayCanvas 构建
|
|
44
|
+
*/
|
|
45
|
+
private validatePlayCanvasBuild;
|
|
46
|
+
/**
|
|
47
|
+
* 验证通用构建(外部引擎)
|
|
48
|
+
*/
|
|
49
|
+
private validateGenericBuild;
|
|
36
50
|
/**
|
|
37
51
|
* 检测是否为 ESM 格式
|
|
38
52
|
*/
|
|
39
53
|
private detectESMFormat;
|
|
40
54
|
/**
|
|
41
|
-
*
|
|
55
|
+
* 将 Vite 输出复制到最终目录
|
|
42
56
|
*/
|
|
43
|
-
private
|
|
57
|
+
private copyOutputToFinalDir;
|
|
58
|
+
/**
|
|
59
|
+
* 递归复制所有文件
|
|
60
|
+
*/
|
|
61
|
+
private copyAllFiles;
|
|
44
62
|
/**
|
|
45
63
|
* 获取输出路径
|
|
46
64
|
*/
|
package/dist/vite-builder.js
CHANGED
|
@@ -2,20 +2,26 @@ import { build as viteBuild } from 'vite';
|
|
|
2
2
|
import fs from 'fs/promises';
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import { ViteConfigBuilder } from './vite/config-builder.js';
|
|
5
|
+
import { GenericViteConfigBuilder } from './vite/config-builder-generic.js';
|
|
5
6
|
import { PLATFORM_CONFIGS } from './vite/platform-configs.js';
|
|
6
7
|
/**
|
|
7
8
|
* Vite 构建器 - 使用 Vite 构建 Playable Ads
|
|
8
9
|
*
|
|
9
10
|
* 职责:
|
|
10
11
|
* 1. 验证输入是有效的基础构建
|
|
11
|
-
* 2.
|
|
12
|
+
* 2. 根据引擎类型创建对应的 Vite 配置
|
|
12
13
|
* 3. 执行 Vite 构建
|
|
13
14
|
* 4. 将输出复制到最终目录
|
|
14
15
|
* 5. 验证输出大小
|
|
15
16
|
* 6. 生成报告
|
|
17
|
+
*
|
|
18
|
+
* 引擎路由:
|
|
19
|
+
* - PlayCanvas:使用 ViteConfigBuilder(处理 config.json、__start__.js 等)
|
|
20
|
+
* - 其他引擎:使用 GenericViteConfigBuilder(通用 HTML 内联 + 平台 SDK 注入)
|
|
16
21
|
*/
|
|
17
22
|
export class ViteBuilder {
|
|
18
23
|
constructor(baseBuildDir, options) {
|
|
24
|
+
this.engine = 'playcanvas';
|
|
19
25
|
this.baseBuildDir = baseBuildDir;
|
|
20
26
|
this.options = options;
|
|
21
27
|
const platformConfig = PLATFORM_CONFIGS[options.platform];
|
|
@@ -30,26 +36,151 @@ export class ViteBuilder {
|
|
|
30
36
|
* 执行构建
|
|
31
37
|
*/
|
|
32
38
|
async build() {
|
|
33
|
-
// 1.
|
|
34
|
-
await this.
|
|
35
|
-
|
|
36
|
-
|
|
39
|
+
// 1. 读取构建元数据获取引擎类型
|
|
40
|
+
const metadata = await this.readBuildMetadata();
|
|
41
|
+
this.engine = this.options.engine ?? metadata?.engine ?? 'playcanvas';
|
|
42
|
+
console.log(`[ViteBuilder] 使用引擎: ${this.engine}`);
|
|
43
|
+
// 2. 验证输入
|
|
44
|
+
await this.validateBaseBuild(metadata);
|
|
45
|
+
// 3. 根据引擎类型创建对应的配置构建器
|
|
46
|
+
let configBuilder;
|
|
47
|
+
if (this.engine === 'playcanvas') {
|
|
48
|
+
console.log('[ViteBuilder] 使用 PlayCanvas 配置构建器');
|
|
49
|
+
configBuilder = new ViteConfigBuilder(this.baseBuildDir, this.options.platform, this.options);
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
console.log('[ViteBuilder] 使用通用配置构建器(外部引擎)');
|
|
53
|
+
configBuilder = new GenericViteConfigBuilder(this.baseBuildDir, this.options.platform, this.options, metadata);
|
|
54
|
+
}
|
|
37
55
|
const viteConfig = await configBuilder.create();
|
|
38
|
-
//
|
|
39
|
-
|
|
40
|
-
|
|
56
|
+
// 4. 执行 Vite 构建
|
|
57
|
+
try {
|
|
58
|
+
await viteBuild(viteConfig);
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
// 打印完整的 Vite/Rollup 错误信息,便于定位构建失败原因
|
|
62
|
+
console.error('[ViteBuilder] Vite 构建失败:');
|
|
63
|
+
if (error.id) {
|
|
64
|
+
console.error(` - 问题文件: ${error.id}`);
|
|
65
|
+
}
|
|
66
|
+
if (error.loc) {
|
|
67
|
+
console.error(` - 位置: 行 ${error.loc.line}, 列 ${error.loc.column}`);
|
|
68
|
+
}
|
|
69
|
+
if (error.frame) {
|
|
70
|
+
console.error(` - 代码片段:\n${error.frame}`);
|
|
71
|
+
}
|
|
72
|
+
if (error.plugin) {
|
|
73
|
+
console.error(` - 插件: ${error.plugin}`);
|
|
74
|
+
}
|
|
75
|
+
if (error.pluginCode) {
|
|
76
|
+
console.error(` - 插件错误码: ${error.pluginCode}`);
|
|
77
|
+
}
|
|
78
|
+
if (error.cause) {
|
|
79
|
+
console.error(` - 原因:`, error.cause);
|
|
80
|
+
}
|
|
81
|
+
console.error(` - 完整错误:`, error.stack || error.message || error);
|
|
82
|
+
throw error;
|
|
83
|
+
}
|
|
84
|
+
// 5. 将输出从临时目录复制到最终目录
|
|
41
85
|
const viteOutputDir = configBuilder.getViteOutputDir();
|
|
42
86
|
const finalOutputDir = configBuilder.getFinalOutputDir();
|
|
43
87
|
if (viteOutputDir && finalOutputDir && viteOutputDir !== finalOutputDir) {
|
|
44
88
|
await this.copyOutputToFinalDir(viteOutputDir, finalOutputDir);
|
|
45
89
|
}
|
|
46
|
-
//
|
|
90
|
+
// 6. 验证输出大小
|
|
47
91
|
const outputPath = this.getOutputPath();
|
|
48
92
|
await this.validateSize(outputPath);
|
|
49
|
-
//
|
|
93
|
+
// 7. 生成报告
|
|
50
94
|
this.generateReport(outputPath);
|
|
51
95
|
return outputPath;
|
|
52
96
|
}
|
|
97
|
+
/**
|
|
98
|
+
* 读取构建元数据
|
|
99
|
+
*/
|
|
100
|
+
async readBuildMetadata() {
|
|
101
|
+
const metadataPath = path.join(this.baseBuildDir, '.build-metadata.json');
|
|
102
|
+
try {
|
|
103
|
+
const content = await fs.readFile(metadataPath, 'utf-8');
|
|
104
|
+
const metadata = JSON.parse(content);
|
|
105
|
+
console.log(`[ViteBuilder] 读取构建元数据: engine=${metadata.engine}, mode=${metadata.mode}`);
|
|
106
|
+
return metadata;
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
console.warn('[ViteBuilder] 无法读取构建元数据,假设为 PlayCanvas');
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* 验证基础构建
|
|
115
|
+
* 根据引擎类型使用不同的验证规则
|
|
116
|
+
*/
|
|
117
|
+
async validateBaseBuild(metadata) {
|
|
118
|
+
const engine = this.options.engine ?? metadata?.engine ?? 'playcanvas';
|
|
119
|
+
if (engine === 'playcanvas') {
|
|
120
|
+
await this.validatePlayCanvasBuild();
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
await this.validateGenericBuild();
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* 验证 PlayCanvas 构建
|
|
128
|
+
*/
|
|
129
|
+
async validatePlayCanvasBuild() {
|
|
130
|
+
const isESM = await this.detectESMFormat();
|
|
131
|
+
const requiredFiles = isESM
|
|
132
|
+
? ['index.html', 'config.json']
|
|
133
|
+
: ['index.html', 'config.json', '__start__.js'];
|
|
134
|
+
const missingFiles = [];
|
|
135
|
+
for (const file of requiredFiles) {
|
|
136
|
+
try {
|
|
137
|
+
await fs.access(path.join(this.baseBuildDir, file));
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
missingFiles.push(file);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
if (missingFiles.length > 0) {
|
|
144
|
+
const formatType = isESM ? 'ESM' : 'Classic';
|
|
145
|
+
throw new Error(`PlayCanvas 基础构建产物缺少必需文件: ${missingFiles.join(', ')}\n` +
|
|
146
|
+
`检测到 ${formatType} 格式,请确保输入目录包含完整的构建产物。`);
|
|
147
|
+
}
|
|
148
|
+
console.log('[ViteBuilder] PlayCanvas 构建验证通过');
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* 验证通用构建(外部引擎)
|
|
152
|
+
*/
|
|
153
|
+
async validateGenericBuild() {
|
|
154
|
+
// 外部引擎只需要 index.html
|
|
155
|
+
try {
|
|
156
|
+
await fs.access(path.join(this.baseBuildDir, 'index.html'));
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
throw new Error(`外部引擎构建产物缺少 index.html\n` +
|
|
160
|
+
`构建目录: ${this.baseBuildDir}\n` +
|
|
161
|
+
`请确保 Base Build 成功生成了有效的 HTML 入口文件。`);
|
|
162
|
+
}
|
|
163
|
+
console.log('[ViteBuilder] 外部引擎构建验证通过');
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* 检测是否为 ESM 格式
|
|
167
|
+
*/
|
|
168
|
+
async detectESMFormat() {
|
|
169
|
+
const esmIndicators = [
|
|
170
|
+
path.join(this.baseBuildDir, 'js/index.mjs'),
|
|
171
|
+
path.join(this.baseBuildDir, 'esm-scripts'),
|
|
172
|
+
];
|
|
173
|
+
for (const indicator of esmIndicators) {
|
|
174
|
+
try {
|
|
175
|
+
await fs.access(indicator);
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
// 继续检查
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
53
184
|
/**
|
|
54
185
|
* 将 Vite 输出复制到最终目录
|
|
55
186
|
*/
|
|
@@ -116,48 +247,6 @@ export class ViteBuilder {
|
|
|
116
247
|
}
|
|
117
248
|
console.log(`[ViteBuilder] 已将输出复制到: ${destDir}`);
|
|
118
249
|
}
|
|
119
|
-
/**
|
|
120
|
-
* 检测是否为 ESM 格式
|
|
121
|
-
*/
|
|
122
|
-
async detectESMFormat() {
|
|
123
|
-
const esmIndicators = [
|
|
124
|
-
path.join(this.baseBuildDir, 'js/index.mjs'),
|
|
125
|
-
path.join(this.baseBuildDir, 'esm-scripts'),
|
|
126
|
-
];
|
|
127
|
-
for (const indicator of esmIndicators) {
|
|
128
|
-
try {
|
|
129
|
-
await fs.access(indicator);
|
|
130
|
-
return true;
|
|
131
|
-
}
|
|
132
|
-
catch {
|
|
133
|
-
// 继续检查
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
return false;
|
|
137
|
-
}
|
|
138
|
-
/**
|
|
139
|
-
* 验证基础构建
|
|
140
|
-
*/
|
|
141
|
-
async validateBaseBuild() {
|
|
142
|
-
const isESM = await this.detectESMFormat();
|
|
143
|
-
const requiredFiles = isESM
|
|
144
|
-
? ['index.html', 'config.json']
|
|
145
|
-
: ['index.html', 'config.json', '__start__.js'];
|
|
146
|
-
const missingFiles = [];
|
|
147
|
-
for (const file of requiredFiles) {
|
|
148
|
-
try {
|
|
149
|
-
await fs.access(path.join(this.baseBuildDir, file));
|
|
150
|
-
}
|
|
151
|
-
catch (error) {
|
|
152
|
-
missingFiles.push(file);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
if (missingFiles.length > 0) {
|
|
156
|
-
const formatType = isESM ? 'ESM' : 'Classic';
|
|
157
|
-
throw new Error(`基础构建产物缺少必需文件: ${missingFiles.join(', ')}\n` +
|
|
158
|
-
`检测到 ${formatType} 格式,请确保输入目录包含完整的构建产物。`);
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
250
|
/**
|
|
162
251
|
* 获取输出路径
|
|
163
252
|
*/
|