Skip to content

Commit

Permalink
feat(cachebuster): add cache buster plugins for atlas and texture pac…
Browse files Browse the repository at this point in the history
…ker (#14)

* - add spine cache buster
- add sprite sheet cache buster
- tests

* Update packages/spine/test/spineAtlasAll.test.ts

* remove only

* fix test and merge bits

* rename

* fix options

* fix options

* Chore/object-hash-options (#15)

* cache id is now takes into account all pipe options
add test

* docs

---------

Co-authored-by: Zyie <[email protected]>
  • Loading branch information
GoodBoyDigital and Zyie authored Apr 29, 2024
1 parent 332914d commit 87703d7
Show file tree
Hide file tree
Showing 15 changed files with 573 additions and 10 deletions.
2 changes: 1 addition & 1 deletion packages/cache-buster/src/cacheBuster.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,6 @@ export function cacheBuster(): AssetPipe
newAsset.buffer = asset.buffer;

return [newAsset];
}
},
};
}
7 changes: 4 additions & 3 deletions packages/core/src/AssetPack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ import type { AssetSettings } from './pipes/PipeSystem';
import { PipeSystem } from './pipes/PipeSystem';
import { finalCopyPipe } from './pipes/finalCopyPipe';
import type { AssetPackConfig } from './config';
import objectHash from 'object-hash';
import { Logger } from './logger/Logger';
import { promiseAllConcurrent } from './utils/promiseAllConcurrent';
import { path } from './utils/path';
import merge from 'merge';
import { generateCacheName } from './utils/generateCacheName';

export class AssetPack
{
Expand Down Expand Up @@ -41,10 +41,10 @@ export class AssetPack
level: this.config.logLevel || 'info',
});

const { pipes, cache, ...configWithoutPlugins } = this.config;
const { pipes, cache } = this.config;

// make a hash..
const cacheName = [objectHash(configWithoutPlugins), ...(pipes as AssetPipe[]).map((pipe) => pipe.name)].join('-');
const cacheName = generateCacheName(this.config);

let assetCacheData = null;
let assetCache: AssetCache | null = null;
Expand Down Expand Up @@ -243,3 +243,4 @@ function normalizePath(pth: string)

return pth;
}

32 changes: 32 additions & 0 deletions packages/core/src/utils/generateCacheName.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import type { AssetPackConfig } from '../config';
import objectHash from 'object-hash';

/**
* Returns a unique name based on the hash generated from the config
* this takes into account the following:
* - pipes and their options,
* - entry and output paths.
* - assetSettings options
* - ignore options
*
* @param options - The asset pack config
* @returns - A unique name based on the hash generated from the config
*/
export function generateCacheName(options: AssetPackConfig)
{
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { pipes, cache, logLevel, ...configWithoutPlugins } = options;

const optionsToHash: any = {
...configWithoutPlugins,
};

// get pipes
pipes?.flat().forEach((pipe) =>
{
optionsToHash[pipe.name] = pipe.defaultOptions;
});

// make a hash..
return objectHash(optionsToHash);
}
49 changes: 48 additions & 1 deletion packages/core/test/Utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { createFolder, createAssetPipe, getInputDir, getOutputDir } from '../../
import type { Asset } from '../src/Asset';
import { AssetPack } from '../src/AssetPack';
import { extractTagsFromFileName } from '../src/utils/extractTagsFromFileName';
import { generateCacheName } from '../src/utils/generateCacheName';

describe('Utils', () =>
{
Expand All @@ -24,7 +25,7 @@ describe('Utils', () =>
expect(extractTagsFromFileName('test{tag1}{tag2=1&2}.json')).toEqual({ tag1: true, tag2: [1, 2] });
});

it.only('should allow for tags to be overridden', async () =>
it('should allow for tags to be overridden', async () =>
{
const testName = 'tag-override';
const inputDir = getInputDir(pkg, testName);
Expand Down Expand Up @@ -82,4 +83,50 @@ describe('Utils', () =>

await assetpack.run();
});

it('should create a unique cache name', async () =>
{
const cacheName = generateCacheName({
entry: 'test',
output: 'out',
pipes: [
{
name: 'test',
defaultOptions: { hi: 'there' }
},
],
});

expect(cacheName).toEqual('9782a5400ded95c60849cf955508938b7efdc8a0');

// change the settings:

const cacheName2 = generateCacheName({
entry: 'test',
output: 'out',
pipes: [
{
name: 'test-2',
defaultOptions: { hi: 'there' }
},
],
});

expect(cacheName2).toEqual('abdf0d02db2c221346e31f61331e5880deff6f4e');

// change the settings:

const cacheName3 = generateCacheName({
entry: 'test',
output: 'out',
pipes: [
{
name: 'test-2',
defaultOptions: { hi: 'bye!' }
},
],
});

expect(cacheName3).toEqual('ab900fa81d7121ea46bd2eafe9e826633c1c48a0');
});
});
2 changes: 1 addition & 1 deletion packages/image/src/mipmap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export function mipmap(_options: MipmapOptions = {}): AssetPipe<MipmapOptions>

return {
folder: true,
name: 'mip-compress',
name: 'mipmap',
defaultOptions,
test(asset: Asset, options)
{
Expand Down
2 changes: 1 addition & 1 deletion packages/image/test/Compress.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ describe('Compress', () =>
expect(existsSync(`${outputDir}/testJpg.png`)).toBe(false);
});

it.only('should compress png with 1 plugin', async () =>
it('should compress png with 1 plugin', async () =>
{
const testName = 'compress-png-1-plugin';
const inputDir = getInputDir(pkg, testName);
Expand Down
2 changes: 1 addition & 1 deletion packages/spine/src/AtlasView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export class AtlasView

getTextures(): string[]
{
const regex = /^.+?(?:\.png|\.jpg|\.jpeg)$/gm;
const regex = /^.+?(?:\.png|\.jpg|\.jpeg|\.webp|\.avif)$/gm;

const matches = this.rawAtlas.match(regex);

Expand Down
1 change: 1 addition & 0 deletions packages/spine/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './spineAtlasMipmap';
export * from './spineAtlasCompress';
export * from './spineAtlasCacheBuster';
83 changes: 83 additions & 0 deletions packages/spine/src/spineAtlasCacheBuster.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import type { Asset } from '@play-co/assetpack-core';
import { checkExt, type AssetPipe, findAssetsWithFileName } from '@play-co/assetpack-core';
import { AtlasView } from './AtlasView';
import { removeSync, writeFileSync } from 'fs-extra';

/**
* This should be used after the cache buster plugin in the pipes.
* As it relies on the cache buster plugin to have already cache busted all files.
* This corrects the atlas files to point to the new cache busted textures.
* At the same time it updates the hash of the files.
*
* As this pipe needs to know about all the textures in the atlas files most of the work is done
* in the finish method.
*
* Kind of like applying a patch at the end of the transform process.
*
* @param _options
* @returns
*/
export function spineAtlasCacheBuster(): AssetPipe
{
const defaultOptions = {};

const atlasFileToFix: Asset[] = [];

return {
folder: false,
name: 'spine-cache-buster',
defaultOptions,
test(asset: Asset, _options)
{
return checkExt(asset.path, '.atlas');
},

async transform(asset: Asset, _options)
{
atlasFileToFix.push(asset);

return [asset];
},

async finish(asset: Asset)
{
// first we retrieve the final transformed children - so the atlas files that have been copied
// to the output folder.
const atlasAssets = atlasFileToFix.map((asset) => asset.getFinalTransformedChildren()[0]);

atlasAssets.forEach((atlasAsset) =>
{
// we are going to replace the textures in the atlas file with the new cache busted textures
// as we do this, the hash of the atlas file will change, so we need to update the path
// and also remove the original file.

const originalHash = atlasAsset.hash;
const originalPath = atlasAsset.path;

const atlasView = new AtlasView(atlasAsset.buffer);

atlasView.getTextures().forEach((texture) =>
{
const textureAssets = findAssetsWithFileName((asset) =>
asset.filename === texture, asset, true);

// last transformed child is the renamed texture
const cacheBustedTexture = textureAssets[0].getFinalTransformedChildren()[0];

atlasView.replaceTexture(texture, cacheBustedTexture.filename);
});

atlasAsset.buffer = atlasView.buffer;

atlasAsset.path = atlasAsset.path.replace(originalHash, atlasAsset.hash);

removeSync(originalPath);

// rewrite..
writeFileSync(atlasAsset.path, atlasAsset.buffer);
});

atlasFileToFix.length = 0;
}
};
}
94 changes: 92 additions & 2 deletions packages/spine/test/spineAtlasAll.test.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import { AssetPack } from '@play-co/assetpack-core';
import { compress, mipmap } from '@play-co/assetpack-plugin-image';
import { readFileSync } from 'fs-extra';
import { existsSync, readFileSync } from 'fs-extra';
import { assetPath, createFolder, getInputDir, getOutputDir } from '../../../shared/test';
import { spineAtlasCompress } from '../src/spineAtlasCompress';
import { spineAtlasMipmap } from '../src/spineAtlasMipmap';
import { cacheBuster } from '@play-co/assetpack-plugin-cache-buster';
import { spineAtlasCacheBuster } from '../src/spineAtlasCacheBuster';

const pkg = 'spine';

describe('Spine Atlas All', () =>
{
it.only('should correctly create files when Mipmap and Compress are used', async () =>
it('should correctly create files when Mipmap and Compress are used', async () =>
{
const testName = 'spine-atlas-compress-mip';
const inputDir = getInputDir(pkg, testName);
Expand Down Expand Up @@ -71,4 +73,92 @@ describe('Spine Atlas All', () =>
expect(rawAtlasWebpHalf.includes('[email protected]')).toBeTruthy();
expect(rawAtlasWebpHalf.includes('[email protected]')).toBeTruthy();
});

it('should correctly create files when Mipmap and CacheBuster are used', async () =>
{
const testName = 'spine-atlas-mip-cache-buster';
const inputDir = getInputDir(pkg, testName);
const outputDir = getOutputDir(pkg, testName);

createFolder(
pkg,
{
name: testName,
files: [
{
name: 'dragon{spine}.atlas',
content: assetPath(pkg, 'dragon.atlas'),
},
{
name: 'dragon.png',
content: assetPath(pkg, 'dragon.png'),
},
{
name: 'dragon2.png',
content: assetPath(pkg, 'dragon2.png'),
},
],
folders: [],
});

const assetpack = new AssetPack({
entry: inputDir,
output: outputDir,
cache: false,
pipes: [
mipmap({
resolutions: { default: 1, low: 0.5 },
}),
compress({
png: true,
webp: true,
jpg: true,
}),
spineAtlasMipmap({
resolutions: { default: 1, low: 0.5 },
}),
spineAtlasCompress({
png: true,
jpg: true,
webp: true,
}),
cacheBuster(),
spineAtlasCacheBuster()
]
});

await assetpack.run();

[
{
atlas: `[email protected]`,
png1: `[email protected]`,
png2: `[email protected]_22pw.webp`
},
{
atlas: `dragon.webp-spj8.atlas`,
png1: `dragon-rSwKOg.webp`,
png2: `dragon2-ws3uhw.webp`
},
{
atlas: `[email protected]`,
png1: `[email protected]`,
png2: `[email protected]`
},
{
atlas: `dragon.png-O471eg.atlas`,
png1: `dragon-vezElA.png`,
png2: `dragon2-3UnJNw.png`
}
].forEach(({ atlas, png1, png2 }) =>
{
const rawAtlas = readFileSync(`${outputDir}/${atlas}`);

expect(rawAtlas.includes(png1)).toBeTruthy();
expect(rawAtlas.includes(png2)).toBeTruthy();

expect(existsSync(`${outputDir}/${png1}`)).toBeTruthy();
expect(existsSync(`${outputDir}/${png2}`)).toBeTruthy();
});
});
});
Loading

0 comments on commit 87703d7

Please sign in to comment.