Skip to content

Commit

Permalink
feat: Added support for nameStyle option in Manifest (#95)
Browse files Browse the repository at this point in the history
Co-authored-by: Zyie <24736175+Zyie@users.noreply.github.com>
  • Loading branch information
CatchABus and Zyie authored Oct 14, 2024
1 parent 8c64a16 commit 586f6e6
Show file tree
Hide file tree
Showing 3 changed files with 305 additions and 11 deletions.
72 changes: 67 additions & 5 deletions packages/assetpack/src/manifest/pixiManifest.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import fs from 'fs-extra';
import { path, stripTags } from '../core/index.js';
import { Logger, path, stripTags } from '../core/index.js';

import type {
Asset,
Expand All @@ -11,6 +11,7 @@ export interface PixiBundle
{
name: string;
assets: PixiManifestEntry[];
relativeName?: string;
}

export interface PixiManifest
Expand Down Expand Up @@ -46,6 +47,11 @@ export interface PixiManifestOptions extends PluginOptions
* if true, the metaData will be outputted in the data field of the manifest.
*/
includeMetaData?: boolean;
/**
* The name style for asset bundles in the manifest file.
* When set to relative, asset bundles will use their relative paths as names.
*/
nameStyle?: 'short' | 'relative';
/**
* if true, the all tags will be outputted in the data.tags field of the manifest.
* If false, only internal tags will be outputted to the data.tags field. All other tags will be outputted to the data field directly.
Expand Down Expand Up @@ -85,6 +91,7 @@ export function pixiManifest(_options: PixiManifestOptions = {}): AssetPipe<Pixi
trimExtensions: false,
includeMetaData: true,
legacyMetaDataOutput: true,
nameStyle: 'short',
..._options,
},
tags: {
Expand Down Expand Up @@ -115,25 +122,52 @@ export function pixiManifest(_options: PixiManifestOptions = {}): AssetPipe<Pixi
this.tags!,
pipeSystem.internalMetaData
);
filterUniqueNames(manifest);
filterUniqueNames(manifest, options);
await fs.writeJSON(newFileName, manifest, { spaces: 2 });
}
};
}

function filterUniqueNames(manifest: PixiManifest)
function filterUniqueNames(manifest: PixiManifest, options: PixiManifestOptions)
{
const nameMap = new Map<PixiManifestEntry, string[]>();
const isNameStyleShort = options.nameStyle !== 'relative';
const bundleNames = new Set<string>();
const duplicateBundleNames = new Set<string>();

manifest.bundles.forEach((bundle) =>
bundle.assets.forEach((asset) => nameMap.set(asset, asset.alias as string[])));
{
if (isNameStyleShort)
{
if (bundleNames.has(bundle.name))
{
duplicateBundleNames.add(bundle.name);
Logger.warn(`[AssetPack][manifest] Duplicate bundle name '${bundle.name}'. All bundles with that name will be renamed to their relative name instead.`);
}
else
{
bundleNames.add(bundle.name);
}
}

bundle.assets.forEach((asset) => nameMap.set(asset, asset.alias as string[]));
});

const arrays = Array.from(nameMap.values());
const sets = arrays.map((arr) => new Set(arr));
const uniqueArrays = arrays.map((arr, i) => arr.filter((x) => sets.every((set, j) => j === i || !set.has(x))));

manifest.bundles.forEach((bundle) =>
{
if (isNameStyleShort)
{
// Switch to relative bundle name to avoid duplications
if (duplicateBundleNames.has(bundle.name))
{
bundle.name = bundle.relativeName ?? bundle.name;
}
}

bundle.assets.forEach((asset) =>
{
const names = nameMap.get(asset) as string[];
Expand All @@ -143,6 +177,21 @@ function filterUniqueNames(manifest: PixiManifest)
});
}

function getRelativeBundleName(asset: Asset, entryPath: string): string
{
let name = asset.filename;
let parent = asset.parent;

// Exclude assets the paths of which equal to the entry path
while (parent && parent.path !== entryPath)
{
name = `${parent.filename}/${name}`;
parent = parent.parent;
}

return stripTags(name);
}

function collectAssets(
asset: Asset,
options: PixiManifestOptions,
Expand All @@ -163,10 +212,23 @@ function collectAssets(
if (asset.metaData[tags!.manifest!])
{
localBundle = {
name: stripTags(asset.filename),
name: options.nameStyle === 'relative' ? getRelativeBundleName(asset, entryPath) : stripTags(asset.filename),
assets: []
};

// This property helps rename duplicate bundle declarations
// Also, mark it as non-enumerable to prevent fs from including it into output
if (options.nameStyle !== 'relative')
{
Object.defineProperty(localBundle, 'relativeName', {
enumerable: false,
get()
{
return getRelativeBundleName(asset, entryPath);
}
});
}

bundles.push(localBundle);
}

Expand Down
230 changes: 230 additions & 0 deletions packages/assetpack/test/manifest/Manifest.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1038,6 +1038,236 @@ describe('Manifest', () =>
});
});

it('should ensure sub-manifests are created correctly with short names', async () =>
{
const testName = 'manifest-sub-manifest-short';
const inputDir = getInputDir(pkg, testName);
const outputDir = getOutputDir(pkg, testName);

createFolder(pkg, {
name: testName,
files: [],
folders: [
{
name: 'sound{m}',
files: [
{
name: '1.mp3',
content: assetPath('audio/1.mp3'),
},
],
folders: [],
},
{
name: 'sound2{m}',
files: [
{
name: '2.mp3',
content: assetPath('audio/1.mp3'),
},
],
folders: [],
},
{
name: 'sound3{m}',
files: [
{
name: '3.mp3',
content: assetPath('audio/1.mp3'),
},
],
folders: [
{
name: 'sound2{m}',
files: [
{
name: '2.mp3',
content: assetPath('audio/1.mp3'),
},
],
folders: [],
},
],
},
],
});

const assetpack = new AssetPack({
entry: inputDir,
cacheLocation: getCacheDir(pkg, testName),
output: outputDir,
cache: false,
pipes: [
pixiManifest({
includeMetaData: false,
}),
],
});

await assetpack.run();

expect(fs.readJSONSync(`${outputDir}/manifest.json`)).toEqual({
bundles: [
{
name: 'default',
assets: [],
},
{
name: 'sound2',
assets: [
{
alias: ['sound2/2.mp3'],
src: ['sound2/2.mp3'],
},
],
},
{
name: 'sound3',
assets: [
{
alias: ['sound3/3.mp3'],
src: ['sound3/3.mp3'],
},
],
},
{
name: 'sound3/sound2',
assets: [
{
alias: ['sound3/sound2/2.mp3'],
src: ['sound3/sound2/2.mp3'],
},
],
},
{
name: 'sound',
assets: [
{
alias: ['sound/1.mp3'],
src: ['sound/1.mp3'],
},
],
},
],
});
});

it('should ensure sub-manifests are created correctly with relative names', async () =>
{
const testName = 'manifest-sub-manifest-relative';
const inputDir = getInputDir(pkg, testName);
const outputDir = getOutputDir(pkg, testName);

createFolder(pkg, {
name: testName,
files: [],
folders: [
{
name: 'sound{m}',
files: [
{
name: '1.mp3',
content: assetPath('audio/1.mp3'),
},
],
folders: [
{
name: 'sound2{m}',
files: [
{
name: '2.mp3',
content: assetPath('audio/1.mp3'),
},
],
folders: [],
},
],
},
{
name: 'sound3{m}',
files: [
{
name: '3.mp3',
content: assetPath('audio/1.mp3'),
},
],
folders: [
{
name: 'sound2{m}',
files: [
{
name: '2.mp3',
content: assetPath('audio/1.mp3'),
},
],
folders: [],
},
],
},
],
});

const assetpack = new AssetPack({
entry: inputDir,
cacheLocation: getCacheDir(pkg, testName),
output: outputDir,
cache: false,
pipes: [
pixiManifest({
includeMetaData: false,
nameStyle: 'relative',
}),
],
});

await assetpack.run();

expect(fs.readJSONSync(`${outputDir}/manifest.json`)).toEqual({
bundles: [
{
name: 'default',
assets: [],
},
{
name: 'sound3',
assets: [
{
alias: ['sound3/3.mp3'],
src: ['sound3/3.mp3'],
},
],
},
{
name: 'sound3/sound2',
assets: [
{
alias: ['sound3/sound2/2.mp3'],
src: ['sound3/sound2/2.mp3'],
},
],
},
{
name: 'sound',
assets: [
{
alias: ['sound/1.mp3'],
src: ['sound/1.mp3'],
},
],
},
{
name: 'sound/sound2',
assets: [
{
alias: ['sound/sound2/2.mp3'],
src: ['sound/sound2/2.mp3'],
},
],
},
],
});
});

it('should ignore files with the mIgnore tag', async () =>
{
const testName = 'manifest-ignore';
Expand Down
Loading

0 comments on commit 586f6e6

Please sign in to comment.