@playcraft/build 0.0.17 → 0.0.19
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 +259 -135
- package/dist/audio-optimizer.d.ts +70 -0
- package/dist/audio-optimizer.js +226 -0
- package/dist/base-builder.d.ts +25 -13
- package/dist/base-builder.js +69 -29
- package/dist/engines/engine-detector.d.ts +13 -4
- package/dist/engines/engine-detector.js +74 -10
- package/dist/engines/generic-adapter.d.ts +12 -6
- package/dist/engines/generic-adapter.js +46 -15
- package/dist/engines/index.d.ts +1 -0
- package/dist/engines/index.js +1 -0
- package/dist/engines/playable-scripts-adapter.d.ts +148 -0
- package/dist/engines/playable-scripts-adapter.js +1084 -0
- package/dist/engines/playcanvas-adapter.js +3 -0
- package/dist/generators/config-generator.js +10 -17
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -1
- package/dist/platforms/google.d.ts +9 -0
- package/dist/platforms/google.js +68 -7
- 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 +60 -13
- package/dist/utils/build-mode-detector.js +2 -0
- package/dist/vite/plugin-playcanvas.js +14 -19
- package/dist/vite/plugin-source-builder.js +383 -97
- package/package.json +7 -4
- package/dist/utils/obfuscate.d.ts +0 -42
- package/dist/utils/obfuscate.js +0 -216
- package/dist/vite/plugin-obfuscate.d.ts +0 -22
- package/dist/vite/plugin-obfuscate.js +0 -52
- package/dist/vite/plugin-template-minifier.d.ts +0 -20
- package/dist/vite/plugin-template-minifier.js +0 -392
|
@@ -259,6 +259,7 @@ function findAssetIds(obj, deps, assets, depth = 0) {
|
|
|
259
259
|
* 收集间接依赖(材质 → 纹理,模型 → 材质等)
|
|
260
260
|
*/
|
|
261
261
|
function collectIndirectDependencies(deps, assets) {
|
|
262
|
+
const scriptNameIndex = buildScriptNameIndex(assets);
|
|
262
263
|
const visited = new Set();
|
|
263
264
|
const queue = Array.from(deps.directAssets);
|
|
264
265
|
while (queue.length > 0) {
|
|
@@ -269,122 +270,105 @@ function collectIndirectDependencies(deps, assets) {
|
|
|
269
270
|
const asset = assets[assetId];
|
|
270
271
|
if (!asset)
|
|
271
272
|
continue;
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
273
|
+
const foundIds = collectAssetDeps(asset, assets, scriptNameIndex);
|
|
274
|
+
for (const id of foundIds) {
|
|
275
|
+
if (!visited.has(id) && !deps.directAssets.has(id) && !deps.indirectAssets.has(id)) {
|
|
276
|
+
deps.indirectAssets.add(id);
|
|
277
|
+
queue.push(id);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* 统一的单个资源依赖收集(核心 switch/case)
|
|
284
|
+
* 根据资源类型精确解析其依赖的其他资源 ID。
|
|
285
|
+
* 供 collectIndirectDependencies 和 collectAllTemplatesDependencies 共用。
|
|
286
|
+
*/
|
|
287
|
+
function collectAssetDeps(asset, assets, scriptNameIndex) {
|
|
288
|
+
const foundIds = new Set();
|
|
289
|
+
const data = asset.data;
|
|
290
|
+
switch (asset.type) {
|
|
291
|
+
case 'material':
|
|
292
|
+
// Material: precisely scan known texture/asset reference fields
|
|
293
|
+
if (data)
|
|
294
|
+
collectMaterialDeps(data, assets, foundIds);
|
|
295
|
+
break;
|
|
296
|
+
case 'model':
|
|
297
|
+
// Model: mapping of mesh → material ID
|
|
298
|
+
if (data?.mapping && typeof data.mapping === 'object') {
|
|
299
|
+
for (const val of Object.values(data.mapping)) {
|
|
300
|
+
if (typeof val === 'object' && val !== null) {
|
|
301
|
+
const matVal = val.material;
|
|
302
|
+
if (matVal !== null && matVal !== undefined) {
|
|
303
|
+
const id = String(matVal);
|
|
304
|
+
if (assets[id])
|
|
305
|
+
foundIds.add(id);
|
|
304
306
|
}
|
|
305
307
|
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
case 'texture':
|
|
311
|
-
// 纹理资源通常是叶子节点,但可能引用其他纹理(如合成纹理)
|
|
312
|
-
findAssetIdsInData(asset.data, deps, assets, queue, visited);
|
|
313
|
-
break;
|
|
314
|
-
case 'sprite':
|
|
315
|
-
// Sprite 可能引用纹理图集
|
|
316
|
-
if (asset.data?.textureAtlasAsset) {
|
|
317
|
-
const id = String(asset.data.textureAtlasAsset);
|
|
318
|
-
if (assets[id] && !visited.has(id) && !deps.directAssets.has(id)) {
|
|
319
|
-
deps.indirectAssets.add(id);
|
|
320
|
-
queue.push(id);
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
findAssetIdsInData(asset.data, deps, assets, queue, visited);
|
|
324
|
-
break;
|
|
325
|
-
case 'template':
|
|
326
|
-
// 模板资产内部的 entities 可能引用其他资产(字体、纹理、脚本等)
|
|
327
|
-
if (asset.data?.entities) {
|
|
328
|
-
for (const entity of Object.values(asset.data.entities)) {
|
|
329
|
-
findAssetIdsInData(entity, deps, assets, queue, visited);
|
|
308
|
+
else if (typeof val === 'number' || typeof val === 'string') {
|
|
309
|
+
const id = String(val);
|
|
310
|
+
if (assets[id])
|
|
311
|
+
foundIds.add(id);
|
|
330
312
|
}
|
|
331
313
|
}
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
314
|
+
}
|
|
315
|
+
// Also scan other data fields for additional references
|
|
316
|
+
if (data)
|
|
317
|
+
scanComponentForAssetIds(data, assets, foundIds, 0);
|
|
318
|
+
break;
|
|
319
|
+
case 'cubemap':
|
|
320
|
+
// Cubemap: textures array holds 6 face texture IDs
|
|
321
|
+
if (data?.textures && Array.isArray(data.textures)) {
|
|
322
|
+
for (const textureId of data.textures) {
|
|
323
|
+
if (textureId !== null && textureId !== undefined) {
|
|
324
|
+
const id = String(textureId);
|
|
325
|
+
if (assets[id])
|
|
326
|
+
foundIds.add(id);
|
|
340
327
|
}
|
|
341
328
|
}
|
|
342
|
-
findAssetIdsInData(asset.data, deps, assets, queue, visited);
|
|
343
|
-
break;
|
|
344
|
-
case 'container':
|
|
345
|
-
// Container 可能引用内部的 render 和 material 资源
|
|
346
|
-
// 主要是 GLB/GLTF 文件,它本身是独立的
|
|
347
|
-
findAssetIdsInData(asset.data, deps, assets, queue, visited);
|
|
348
|
-
break;
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
/**
|
|
353
|
-
* 在资源数据中查找资源 ID
|
|
354
|
-
*/
|
|
355
|
-
function findAssetIdsInData(data, deps, assets, queue, visited) {
|
|
356
|
-
if (!data)
|
|
357
|
-
return;
|
|
358
|
-
// 递归扫描 data 对象
|
|
359
|
-
const scan = (obj, depth = 0) => {
|
|
360
|
-
if (depth > 10)
|
|
361
|
-
return;
|
|
362
|
-
if (obj === null || obj === undefined)
|
|
363
|
-
return;
|
|
364
|
-
if (typeof obj === 'number' || typeof obj === 'string') {
|
|
365
|
-
const id = String(obj);
|
|
366
|
-
if (assets[id] && !visited.has(id) && !deps.directAssets.has(id)) {
|
|
367
|
-
deps.indirectAssets.add(id);
|
|
368
|
-
queue.push(id);
|
|
369
329
|
}
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
330
|
+
break;
|
|
331
|
+
case 'texture':
|
|
332
|
+
// Texture: usually a leaf node, no further deps
|
|
333
|
+
break;
|
|
334
|
+
case 'sprite':
|
|
335
|
+
// Sprite: textureAtlasAsset reference
|
|
336
|
+
if (data?.textureAtlasAsset) {
|
|
337
|
+
const id = String(data.textureAtlasAsset);
|
|
338
|
+
if (assets[id])
|
|
339
|
+
foundIds.add(id);
|
|
374
340
|
}
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
341
|
+
break;
|
|
342
|
+
case 'textureatlas':
|
|
343
|
+
// TextureAtlas: find associated texture by name + file hash
|
|
344
|
+
findAssociatedTexture(asset, assets, foundIds);
|
|
345
|
+
break;
|
|
346
|
+
case 'template':
|
|
347
|
+
// Template: precisely parse entities for component/script references
|
|
348
|
+
collectTemplateDeps(asset, assets, scriptNameIndex, foundIds);
|
|
349
|
+
break;
|
|
350
|
+
case 'render':
|
|
351
|
+
// Render: containerAsset (GLB/GLTF model)
|
|
352
|
+
if (data?.containerAsset) {
|
|
353
|
+
const id = String(data.containerAsset);
|
|
354
|
+
if (assets[id])
|
|
355
|
+
foundIds.add(id);
|
|
384
356
|
}
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
357
|
+
if (data)
|
|
358
|
+
scanComponentForAssetIds(data, assets, foundIds, 0);
|
|
359
|
+
break;
|
|
360
|
+
case 'container':
|
|
361
|
+
// Container (GLB/GLTF): may reference render and material assets
|
|
362
|
+
if (data)
|
|
363
|
+
scanComponentForAssetIds(data, assets, foundIds, 0);
|
|
364
|
+
break;
|
|
365
|
+
default:
|
|
366
|
+
// Other types: generic scan
|
|
367
|
+
if (data)
|
|
368
|
+
scanComponentForAssetIds(data, assets, foundIds, 0);
|
|
369
|
+
break;
|
|
370
|
+
}
|
|
371
|
+
return foundIds;
|
|
388
372
|
}
|
|
389
373
|
/**
|
|
390
374
|
* 分析多个场景的资源依赖并合并
|
|
@@ -413,7 +397,7 @@ export async function collectScenesAssets(scenes, assets, globalScriptIds) {
|
|
|
413
397
|
}
|
|
414
398
|
// 收集所有 template 资源及其依赖(含 preload=false 的插件模板如 _Guide、_ExplodeBall)
|
|
415
399
|
// template 通常由 GameplaySystem 等运行时通过 assets.find(name) 按名称动态加载
|
|
416
|
-
console.log(`\n📦 收集 Template
|
|
400
|
+
console.log(`\n📦 收集 Template 资源依赖!!!...`);
|
|
417
401
|
const templateDeps = collectAllTemplatesDependencies(assets);
|
|
418
402
|
let templateAssetsCount = 0;
|
|
419
403
|
for (const id of templateDeps) {
|
|
@@ -449,14 +433,17 @@ function collectAllTemplatesDependencies(assets) {
|
|
|
449
433
|
const result = new Set();
|
|
450
434
|
const visited = new Set();
|
|
451
435
|
const queue = [];
|
|
452
|
-
//
|
|
436
|
+
// Build script name index for resolving script component references
|
|
437
|
+
const scriptNameIndex = buildScriptNameIndex(assets);
|
|
438
|
+
// Collect all templates (no longer restricted to preload=true)
|
|
453
439
|
for (const [assetId, asset] of Object.entries(assets)) {
|
|
454
440
|
if (asset.type === 'template') {
|
|
455
441
|
result.add(assetId);
|
|
456
442
|
queue.push(assetId);
|
|
457
443
|
}
|
|
458
444
|
}
|
|
459
|
-
//
|
|
445
|
+
// BFS: recursively collect dependencies of all templates and their transitive deps
|
|
446
|
+
// Reuses the same collectAssetDeps switch/case as collectIndirectDependencies
|
|
460
447
|
while (queue.length > 0) {
|
|
461
448
|
const assetId = queue.shift();
|
|
462
449
|
if (visited.has(assetId))
|
|
@@ -465,58 +452,195 @@ function collectAllTemplatesDependencies(assets) {
|
|
|
465
452
|
const asset = assets[assetId];
|
|
466
453
|
if (!asset)
|
|
467
454
|
continue;
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
455
|
+
const foundIds = collectAssetDeps(asset, assets, scriptNameIndex);
|
|
456
|
+
// Only log for template-type assets (no nested logging for transitive deps)
|
|
457
|
+
if (asset.type === 'template' && foundIds.size > 0) {
|
|
458
|
+
const depDetails = [...foundIds].map(id => {
|
|
459
|
+
const dep = assets[id];
|
|
460
|
+
return dep ? `${id}(${dep.type}:${dep.name})` : id;
|
|
461
|
+
});
|
|
462
|
+
console.log(` 📦 template "${asset.name}" (${assetId}) → ${foundIds.size} deps: [${depDetails.join(', ')}]`);
|
|
463
|
+
}
|
|
471
464
|
for (const id of foundIds) {
|
|
472
465
|
if (!result.has(id)) {
|
|
473
466
|
result.add(id);
|
|
474
|
-
queue.push(id); //
|
|
467
|
+
queue.push(id); // continue BFS for transitive deps
|
|
475
468
|
}
|
|
476
469
|
}
|
|
477
470
|
}
|
|
478
471
|
return result;
|
|
479
472
|
}
|
|
480
473
|
/**
|
|
481
|
-
*
|
|
474
|
+
* Find the texture asset associated with a textureatlas.
|
|
475
|
+
* In PlayCanvas, a textureatlas and its source texture are separate assets
|
|
476
|
+
* linked by having the same name and the same file hash.
|
|
482
477
|
*/
|
|
483
|
-
function
|
|
484
|
-
|
|
478
|
+
function findAssociatedTexture(atlasAsset, assets, foundIds) {
|
|
479
|
+
const atlasName = atlasAsset.name;
|
|
480
|
+
const atlasHash = atlasAsset.file?.hash;
|
|
481
|
+
for (const [assetId, asset] of Object.entries(assets)) {
|
|
482
|
+
if (asset.type !== 'texture')
|
|
483
|
+
continue;
|
|
484
|
+
// Match by name and file hash (both should match)
|
|
485
|
+
if (atlasHash && asset.file?.hash === atlasHash) {
|
|
486
|
+
foundIds.add(assetId);
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
// Fallback: match by name if hash is not available
|
|
490
|
+
if (!atlasHash && asset.name === atlasName) {
|
|
491
|
+
foundIds.add(assetId);
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
/**
|
|
497
|
+
* Collect texture dependencies from a PlayCanvas material's data object.
|
|
498
|
+
* Specifically targets known texture/asset reference fields rather than blind scanning,
|
|
499
|
+
* to avoid false positives from color arrays, tiling vectors, etc.
|
|
500
|
+
*/
|
|
501
|
+
function collectMaterialDeps(data, assets, foundIds) {
|
|
502
|
+
// Known material fields that hold asset IDs (texture maps and cubemaps)
|
|
503
|
+
const textureFields = [
|
|
504
|
+
'diffuseMap', 'specularMap', 'metalnessMap', 'glossMap',
|
|
505
|
+
'emissiveMap', 'opacityMap', 'normalMap', 'heightMap',
|
|
506
|
+
'aoMap', 'lightMap', 'cubeMap', 'sphereMap',
|
|
507
|
+
'clearCoatMap', 'clearCoatGlossMap', 'clearCoatNormalMap',
|
|
508
|
+
'diffuseDetailMap', 'normalDetailMap',
|
|
509
|
+
'sheenMap', 'sheenGlossMap',
|
|
510
|
+
'msdfMap', // MSDF font atlas texture
|
|
511
|
+
];
|
|
512
|
+
for (const field of textureFields) {
|
|
513
|
+
const val = data[field];
|
|
514
|
+
if (val !== null && val !== undefined) {
|
|
515
|
+
const id = String(val);
|
|
516
|
+
if (assets[id])
|
|
517
|
+
foundIds.add(id);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
// Also check cubeMapProjection if it references an asset
|
|
521
|
+
if (data.cubeMapProjectionBox?.asset) {
|
|
522
|
+
const id = String(data.cubeMapProjectionBox.asset);
|
|
523
|
+
if (assets[id])
|
|
524
|
+
foundIds.add(id);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
/**
|
|
528
|
+
* Collect dependencies from a template asset by analyzing its entities structure.
|
|
529
|
+
* Uses precise parsing of PlayCanvas template data instead of blind recursive scanning.
|
|
530
|
+
*
|
|
531
|
+
* Template data structure:
|
|
532
|
+
* asset.data.entities = {
|
|
533
|
+
* "<resource_id>": {
|
|
534
|
+
* name, components: { script: { order, scripts }, element, model, ... },
|
|
535
|
+
* template_id, ...
|
|
536
|
+
* }
|
|
537
|
+
* }
|
|
538
|
+
*/
|
|
539
|
+
function collectTemplateDeps(asset, assets, scriptNameIndex, foundIds) {
|
|
540
|
+
const entities = asset.data?.entities;
|
|
541
|
+
if (!entities || typeof entities !== 'object')
|
|
542
|
+
return;
|
|
543
|
+
for (const entity of Object.values(entities)) {
|
|
544
|
+
if (!entity || typeof entity !== 'object')
|
|
545
|
+
continue;
|
|
546
|
+
// 1. Nested template reference (template_id)
|
|
547
|
+
if (entity.template_id) {
|
|
548
|
+
const tid = String(entity.template_id);
|
|
549
|
+
if (assets[tid])
|
|
550
|
+
foundIds.add(tid);
|
|
551
|
+
}
|
|
552
|
+
// 2. Parse components
|
|
553
|
+
if (entity.components && typeof entity.components === 'object') {
|
|
554
|
+
for (const [compName, compData] of Object.entries(entity.components)) {
|
|
555
|
+
if (!compData || typeof compData !== 'object')
|
|
556
|
+
continue;
|
|
557
|
+
if (compName === 'script') {
|
|
558
|
+
// 2a. Script component: resolve script names → asset IDs via index
|
|
559
|
+
collectScriptDepsFromComponent(compData, assets, scriptNameIndex, foundIds);
|
|
560
|
+
}
|
|
561
|
+
else {
|
|
562
|
+
// 2b. Other components (element, model, sound, sprite, etc.):
|
|
563
|
+
// scan for numeric/string asset IDs in their properties
|
|
564
|
+
scanComponentForAssetIds(compData, assets, foundIds, 0);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
/**
|
|
571
|
+
* Resolve script names in a script component to asset IDs,
|
|
572
|
+
* and also scan script attribute values for asset references.
|
|
573
|
+
*/
|
|
574
|
+
function collectScriptDepsFromComponent(scriptComponent, assets, scriptNameIndex, foundIds) {
|
|
575
|
+
// Collect script names from order + scripts keys
|
|
576
|
+
const scriptNames = new Set();
|
|
577
|
+
if (Array.isArray(scriptComponent.order)) {
|
|
578
|
+
for (const name of scriptComponent.order) {
|
|
579
|
+
if (typeof name === 'string')
|
|
580
|
+
scriptNames.add(name);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
if (scriptComponent.scripts && typeof scriptComponent.scripts === 'object') {
|
|
584
|
+
for (const name of Object.keys(scriptComponent.scripts)) {
|
|
585
|
+
scriptNames.add(name);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
// Map script names → asset IDs
|
|
589
|
+
for (const scriptName of scriptNames) {
|
|
590
|
+
const assetId = scriptNameIndex.get(scriptName);
|
|
591
|
+
if (assetId && assets[assetId]) {
|
|
592
|
+
foundIds.add(assetId);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
// Scan script attribute values for asset references
|
|
596
|
+
// (e.g. asset-type attributes like "fontAsset", "spriteAsset", etc.)
|
|
597
|
+
if (scriptComponent.scripts && typeof scriptComponent.scripts === 'object') {
|
|
598
|
+
for (const scriptConfig of Object.values(scriptComponent.scripts)) {
|
|
599
|
+
if (scriptConfig?.attributes && typeof scriptConfig.attributes === 'object') {
|
|
600
|
+
scanComponentForAssetIds(scriptConfig.attributes, assets, foundIds, 0);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
/**
|
|
606
|
+
* Recursively scan a component data object for numeric/string asset ID references.
|
|
607
|
+
* Skips known non-asset fields (position, rotation, scale, etc.).
|
|
608
|
+
*/
|
|
609
|
+
function scanComponentForAssetIds(obj, assets, foundIds, depth) {
|
|
610
|
+
if (depth > 10)
|
|
485
611
|
return;
|
|
486
612
|
if (obj === null || obj === undefined)
|
|
487
613
|
return;
|
|
488
614
|
if (typeof obj === 'number') {
|
|
489
615
|
const id = String(obj);
|
|
490
|
-
if (assets[id])
|
|
616
|
+
if (assets[id])
|
|
491
617
|
foundIds.add(id);
|
|
492
|
-
|
|
618
|
+
return;
|
|
493
619
|
}
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
if (assets[obj]) {
|
|
620
|
+
if (typeof obj === 'string') {
|
|
621
|
+
if (assets[obj])
|
|
497
622
|
foundIds.add(obj);
|
|
498
|
-
|
|
623
|
+
return;
|
|
499
624
|
}
|
|
500
|
-
|
|
501
|
-
//
|
|
502
|
-
|
|
503
|
-
if (obj.length <= 4 && obj.every(v => typeof v === 'number' && (!Number.isInteger(v) || // 浮点数(坐标/颜色)
|
|
504
|
-
(v >= 0 && v < 1000) // 小正整数(颜色分量等)
|
|
505
|
-
))) {
|
|
625
|
+
if (Array.isArray(obj)) {
|
|
626
|
+
// Skip small numeric arrays (likely vectors / colors, not asset IDs)
|
|
627
|
+
if (obj.length <= 4 && obj.every(v => typeof v === 'number' && (!Number.isInteger(v) || (v >= 0 && v < 1000)))) {
|
|
506
628
|
return;
|
|
507
629
|
}
|
|
508
630
|
for (const item of obj) {
|
|
509
|
-
|
|
631
|
+
scanComponentForAssetIds(item, assets, foundIds, depth + 1);
|
|
510
632
|
}
|
|
633
|
+
return;
|
|
511
634
|
}
|
|
512
|
-
|
|
635
|
+
if (typeof obj === 'object') {
|
|
513
636
|
for (const [key, value] of Object.entries(obj)) {
|
|
514
|
-
//
|
|
515
|
-
if (
|
|
516
|
-
'
|
|
637
|
+
// Skip known non-asset fields
|
|
638
|
+
if (key === 'name' || key === 'enabled' || key === 'position' ||
|
|
639
|
+
key === 'rotation' || key === 'scale' || key === 'tags' ||
|
|
640
|
+
key === 'children' || key === 'parent' || key === 'resource_id') {
|
|
517
641
|
continue;
|
|
518
642
|
}
|
|
519
|
-
|
|
643
|
+
scanComponentForAssetIds(value, assets, foundIds, depth + 1);
|
|
520
644
|
}
|
|
521
645
|
}
|
|
522
646
|
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
export interface AudioAssetInfo {
|
|
2
|
+
/** Absolute path to the file */
|
|
3
|
+
filePath: string;
|
|
4
|
+
/** Path relative to build directory */
|
|
5
|
+
relativePath: string;
|
|
6
|
+
/** File size in bytes */
|
|
7
|
+
size: number;
|
|
8
|
+
/** Whether a matching .spectral.json exists */
|
|
9
|
+
hasSpectral: boolean;
|
|
10
|
+
/** Spectral JSON size if available */
|
|
11
|
+
spectralSize?: number;
|
|
12
|
+
/** Savings by switching to spectral */
|
|
13
|
+
potentialSavings?: number;
|
|
14
|
+
}
|
|
15
|
+
export interface AudioOptimizationReport {
|
|
16
|
+
/** All audio files found */
|
|
17
|
+
audioFiles: AudioAssetInfo[];
|
|
18
|
+
/** All .spectral.json files found */
|
|
19
|
+
spectralFiles: string[];
|
|
20
|
+
/** Total size of all MP3/audio files */
|
|
21
|
+
totalAudioSize: number;
|
|
22
|
+
/** Total size if all with spectral were replaced */
|
|
23
|
+
totalSpectralSize: number;
|
|
24
|
+
/** Whether SpectralAudioEngine runtime was injected */
|
|
25
|
+
runtimeInjected: boolean;
|
|
26
|
+
/** Size of injected runtime (bytes) */
|
|
27
|
+
runtimeSize: number;
|
|
28
|
+
/** Net savings from spectral replacement (bytes) */
|
|
29
|
+
netSavings: number;
|
|
30
|
+
}
|
|
31
|
+
export interface AudioOptimizerOptions {
|
|
32
|
+
/** Build output directory to scan */
|
|
33
|
+
buildDir: string;
|
|
34
|
+
/**
|
|
35
|
+
* Whether to auto-inject SpectralAudioEngine runtime when spectral files are present.
|
|
36
|
+
* Defaults to true.
|
|
37
|
+
*/
|
|
38
|
+
injectRuntime?: boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Path to an HTML file to inject the runtime into (e.g. index.html).
|
|
41
|
+
* If not provided, runtime injection is skipped even if injectRuntime=true.
|
|
42
|
+
*/
|
|
43
|
+
htmlOutputPath?: string;
|
|
44
|
+
/** Verbose logging */
|
|
45
|
+
verbose?: boolean;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* AudioOptimizer — Build-time audio analysis and optimization.
|
|
49
|
+
*
|
|
50
|
+
* Scans a build directory for audio files and .spectral.json counterparts.
|
|
51
|
+
* When spectral files are present, optionally injects the SpectralAudioEngine
|
|
52
|
+
* runtime (< 3KB) into the output HTML so the browser can synthesize them.
|
|
53
|
+
*
|
|
54
|
+
* Usage:
|
|
55
|
+
* const optimizer = new AudioOptimizer({ buildDir: './dist', htmlOutputPath: './dist/index.html' });
|
|
56
|
+
* const report = await optimizer.optimize();
|
|
57
|
+
* console.log(report);
|
|
58
|
+
*/
|
|
59
|
+
export declare class AudioOptimizer {
|
|
60
|
+
private options;
|
|
61
|
+
constructor(options: AudioOptimizerOptions);
|
|
62
|
+
/**
|
|
63
|
+
* Run the optimizer. Returns a report with audio file details and optimization results.
|
|
64
|
+
*/
|
|
65
|
+
optimize(): Promise<AudioOptimizationReport>;
|
|
66
|
+
/**
|
|
67
|
+
* Print a human-readable summary of the optimization report.
|
|
68
|
+
*/
|
|
69
|
+
static printReport(report: AudioOptimizationReport): void;
|
|
70
|
+
}
|