Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Added support for nameStyle option in Manifest #95

Merged
merged 3 commits into from
Oct 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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