From 87703d7e8e1655552c3ecd6bc13aff4e422e4bbc Mon Sep 17 00:00:00 2001 From: Mat Groves Date: Mon, 29 Apr 2024 12:45:34 +0100 Subject: [PATCH] feat(cachebuster): add cache buster plugins for atlas and texture packer (#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 <24736175+Zyie@users.noreply.github.com> --- packages/cache-buster/src/cacheBuster.ts | 2 +- packages/core/src/AssetPack.ts | 7 +- packages/core/src/utils/generateCacheName.ts | 32 ++++++ packages/core/test/Utils.test.ts | 49 ++++++++- packages/image/src/mipmap.ts | 2 +- packages/image/test/Compress.test.ts | 2 +- packages/spine/src/AtlasView.ts | 2 +- packages/spine/src/index.ts | 1 + packages/spine/src/spineAtlasCacheBuster.ts | 83 ++++++++++++++ packages/spine/test/spineAtlasAll.test.ts | 94 +++++++++++++++- .../spine/test/spineAtlasCacheBuster.test.ts | 55 +++++++++ packages/texture-packer/src/index.ts | 1 + .../src/texturePackerCacheBuster.ts | 81 ++++++++++++++ .../test/texturePackerAll.test.ts | 104 ++++++++++++++++++ .../test/texturePackerCacheBuster.test.ts | 68 ++++++++++++ 15 files changed, 573 insertions(+), 10 deletions(-) create mode 100644 packages/core/src/utils/generateCacheName.ts create mode 100644 packages/spine/src/spineAtlasCacheBuster.ts create mode 100644 packages/spine/test/spineAtlasCacheBuster.test.ts create mode 100644 packages/texture-packer/src/texturePackerCacheBuster.ts create mode 100644 packages/texture-packer/test/texturePackerAll.test.ts create mode 100644 packages/texture-packer/test/texturePackerCacheBuster.test.ts diff --git a/packages/cache-buster/src/cacheBuster.ts b/packages/cache-buster/src/cacheBuster.ts index 8e3446d..592099a 100644 --- a/packages/cache-buster/src/cacheBuster.ts +++ b/packages/cache-buster/src/cacheBuster.ts @@ -33,6 +33,6 @@ export function cacheBuster(): AssetPipe newAsset.buffer = asset.buffer; return [newAsset]; - } + }, }; } diff --git a/packages/core/src/AssetPack.ts b/packages/core/src/AssetPack.ts index e8487d6..4d5bd23 100644 --- a/packages/core/src/AssetPack.ts +++ b/packages/core/src/AssetPack.ts @@ -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 { @@ -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; @@ -243,3 +243,4 @@ function normalizePath(pth: string) return pth; } + diff --git a/packages/core/src/utils/generateCacheName.ts b/packages/core/src/utils/generateCacheName.ts new file mode 100644 index 0000000..1e8ed69 --- /dev/null +++ b/packages/core/src/utils/generateCacheName.ts @@ -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); +} diff --git a/packages/core/test/Utils.test.ts b/packages/core/test/Utils.test.ts index 56eb82d..63fe755 100644 --- a/packages/core/test/Utils.test.ts +++ b/packages/core/test/Utils.test.ts @@ -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', () => { @@ -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); @@ -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'); + }); }); diff --git a/packages/image/src/mipmap.ts b/packages/image/src/mipmap.ts index 7895807..e16a8d5 100644 --- a/packages/image/src/mipmap.ts +++ b/packages/image/src/mipmap.ts @@ -38,7 +38,7 @@ export function mipmap(_options: MipmapOptions = {}): AssetPipe return { folder: true, - name: 'mip-compress', + name: 'mipmap', defaultOptions, test(asset: Asset, options) { diff --git a/packages/image/test/Compress.test.ts b/packages/image/test/Compress.test.ts index 3b3944f..4862f90 100644 --- a/packages/image/test/Compress.test.ts +++ b/packages/image/test/Compress.test.ts @@ -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); diff --git a/packages/spine/src/AtlasView.ts b/packages/spine/src/AtlasView.ts index 2a3775a..6786fb1 100644 --- a/packages/spine/src/AtlasView.ts +++ b/packages/spine/src/AtlasView.ts @@ -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); diff --git a/packages/spine/src/index.ts b/packages/spine/src/index.ts index 46c83ce..2283377 100644 --- a/packages/spine/src/index.ts +++ b/packages/spine/src/index.ts @@ -1,2 +1,3 @@ export * from './spineAtlasMipmap'; export * from './spineAtlasCompress'; +export * from './spineAtlasCacheBuster'; diff --git a/packages/spine/src/spineAtlasCacheBuster.ts b/packages/spine/src/spineAtlasCacheBuster.ts new file mode 100644 index 0000000..7b8d309 --- /dev/null +++ b/packages/spine/src/spineAtlasCacheBuster.ts @@ -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; + } + }; +} diff --git a/packages/spine/test/spineAtlasAll.test.ts b/packages/spine/test/spineAtlasAll.test.ts index 4ef2034..901b46a 100644 --- a/packages/spine/test/spineAtlasAll.test.ts +++ b/packages/spine/test/spineAtlasAll.test.ts @@ -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); @@ -71,4 +73,92 @@ describe('Spine Atlas All', () => expect(rawAtlasWebpHalf.includes('dragon@0.5x.webp')).toBeTruthy(); expect(rawAtlasWebpHalf.includes('dragon2@0.5x.webp')).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: `dragon@0.5x.webp-gWXF6w.atlas`, + png1: `dragon@0.5x-7mmX8g.webp`, + png2: `dragon2@0.5x-k_22pw.webp` + }, + { + atlas: `dragon.webp-spj8.atlas`, + png1: `dragon-rSwKOg.webp`, + png2: `dragon2-ws3uhw.webp` + }, + { + atlas: `dragon@0.5x.png-jg5ydg.atlas`, + png1: `dragon@0.5x-3--s.png`, + png2: `dragon2@0.5x-vflfww.png` + }, + { + 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(); + }); + }); }); diff --git a/packages/spine/test/spineAtlasCacheBuster.test.ts b/packages/spine/test/spineAtlasCacheBuster.test.ts new file mode 100644 index 0000000..f5d873e --- /dev/null +++ b/packages/spine/test/spineAtlasCacheBuster.test.ts @@ -0,0 +1,55 @@ +import { AssetPack } from '@play-co/assetpack-core'; +import { cacheBuster } from '@play-co/assetpack-plugin-cache-buster'; +import { readFileSync } from 'fs-extra'; +import { assetPath, createFolder, getInputDir, getOutputDir } from '../../../shared/test'; +import { spineAtlasCacheBuster } from '../src/spineAtlasCacheBuster'; + +const pkg = 'spine'; + +describe('Spine Atlas Cache Buster', () => +{ + it('should modify the atlas to include the correct file names when cache busting applied', async () => + { + const testName = 'spine-atlas-cache-bust'; + 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: [ + cacheBuster(), + spineAtlasCacheBuster(), + ] + }); + + await assetpack.run(); + + const rawAtlas = readFileSync(`${outputDir}/dragon-qmTByg.atlas`); + + expect(rawAtlas.includes('dragon-iSqGPQ')).toBeTruthy(); + expect(rawAtlas.includes('dragon2-6ebkeA')).toBeTruthy(); + }); +}); diff --git a/packages/texture-packer/src/index.ts b/packages/texture-packer/src/index.ts index c5a284f..4975515 100644 --- a/packages/texture-packer/src/index.ts +++ b/packages/texture-packer/src/index.ts @@ -1,2 +1,3 @@ export * from './texturePacker'; export * from './texturePackerCompress'; +export * from './texturePackerCacheBuster'; diff --git a/packages/texture-packer/src/texturePackerCacheBuster.ts b/packages/texture-packer/src/texturePackerCacheBuster.ts new file mode 100644 index 0000000..be334eb --- /dev/null +++ b/packages/texture-packer/src/texturePackerCacheBuster.ts @@ -0,0 +1,81 @@ +import type { Asset } from '@play-co/assetpack-core'; +import { checkExt, type AssetPipe, findAssetsWithFileName } from '@play-co/assetpack-core'; +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 sprite sheet 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 texture 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 texturePackerCacheBuster(): AssetPipe +{ + const defaultOptions = {}; + + const textureJsonFilesToFix: Asset[] = []; + + return { + folder: false, + name: 'texture-packer-cache-buster', + defaultOptions, + test(asset: Asset, _options) + { + return asset.allMetaData.tps && checkExt(asset.path, '.json'); + }, + + async transform(asset: Asset, _options) + { + textureJsonFilesToFix.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 jsonAssets = textureJsonFilesToFix.map((asset) => asset.getFinalTransformedChildren()[0]); + + jsonAssets.forEach((jsonAsset) => + { + // 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 = jsonAsset.hash; + const originalPath = jsonAsset.path; + + const json = JSON.parse(jsonAsset.buffer.toString()); + + const texture = json.meta.image; + + const textureAssets = findAssetsWithFileName((asset) => + asset.filename === texture, asset, true); + + // last transformed child is the renamed texture + const cacheBustedTexture = textureAssets[0].getFinalTransformedChildren()[0]; + + json.meta.image = cacheBustedTexture.filename; + + jsonAsset.buffer = Buffer.from(JSON.stringify(json)); + + jsonAsset.path = jsonAsset.path.replace(originalHash, jsonAsset.hash); + + removeSync(originalPath); + + // rewrite.. + writeFileSync(jsonAsset.path, jsonAsset.buffer); + }); + + textureJsonFilesToFix.length = 0; + } + }; +} diff --git a/packages/texture-packer/test/texturePackerAll.test.ts b/packages/texture-packer/test/texturePackerAll.test.ts new file mode 100644 index 0000000..18eda2f --- /dev/null +++ b/packages/texture-packer/test/texturePackerAll.test.ts @@ -0,0 +1,104 @@ +import { AssetPack } from '@play-co/assetpack-core'; +import { texturePackerCacheBuster } from '../src/texturePackerCacheBuster'; +import { texturePacker } from '../src/texturePacker'; +import { cacheBuster } from '@play-co/assetpack-plugin-cache-buster'; +import { readJSONSync } from 'fs-extra'; +import type { File } from 'shared/test'; +import { assetPath, createFolder, getInputDir, getOutputDir } from '../../../shared/test/index'; +import { texturePackerCompress } from '../src/texturePackerCompress'; +import { compress, mipmap } from '@play-co/assetpack-plugin-image'; + +const pkg = 'texture-packer'; + +function genFolder(testName: string) +{ + const sprites: File[] = []; + + for (let i = 0; i < 10; i++) + { + sprites.push({ + name: `sprite${i}.png`, + content: assetPath(pkg, `sp-${i + 1}.png`), + }); + } + createFolder( + pkg, + { + name: testName, + files: [], + folders: [ + { + name: 'sprites{tps}', + files: sprites, + folders: [], + }, + ], + }); +} + +describe('Texture Packer All', () => +{ + it('should create a sprite sheet mip, compress and cache bust', async () => + { + const testName = 'tp-all'; + const inputDir = getInputDir(pkg, testName); + const outputDir = getOutputDir(pkg, testName); + + genFolder(testName); + + const assetpack = new AssetPack({ + entry: inputDir, + output: outputDir, + cache: false, + pipes: [ + texturePacker({ + resolutionOptions: { + resolutions: { default: 1, low: 0.5 }, + }, + }), + mipmap({ + resolutions: { default: 1, low: 0.5 }, + }), + compress({ + png: true, + jpg: true, + webp: true, + }), + texturePackerCompress({ + png: true, + jpg: true, + webp: true, + }), + cacheBuster(), + texturePackerCacheBuster() + ] + }); + + await assetpack.run(); + + [ + { + json: `sprites@0.5x.webp-1it4Qw.json`, + image: `sprites@0.5x-g_W8Sw.webp`, + }, + { + json: `sprites.webp-RCqjNQ.json`, + image: `sprites-wXEUjA.webp`, + }, + { + json: `sprites@0.5x.png--5BuTA.json`, + image: `sprites@0.5x-TV3-Lg.png`, + + }, + { + json: `sprites.png-FYLGeg.json`, + image: `sprites-Ef_oOA.png`, + } + ].forEach(({ json, image }) => + { + const jsonData = readJSONSync(`${outputDir}/${json}`); + + expect(jsonData.meta.image).toEqual(image); + }); + }); +}); diff --git a/packages/texture-packer/test/texturePackerCacheBuster.test.ts b/packages/texture-packer/test/texturePackerCacheBuster.test.ts new file mode 100644 index 0000000..8d60ef7 --- /dev/null +++ b/packages/texture-packer/test/texturePackerCacheBuster.test.ts @@ -0,0 +1,68 @@ +import { AssetPack } from '@play-co/assetpack-core'; +import { texturePackerCacheBuster } from '../src/texturePackerCacheBuster'; +import { texturePacker } from '../src/texturePacker'; +import { cacheBuster } from '@play-co/assetpack-plugin-cache-buster'; +import { readJSONSync } from 'fs-extra'; +import type { File } from 'shared/test'; +import { assetPath, createFolder, getInputDir, getOutputDir } from '../../../shared/test/index'; + +const pkg = 'texture-packer'; + +function genFolder(testName: string) +{ + const sprites: File[] = []; + + for (let i = 0; i < 10; i++) + { + sprites.push({ + name: `sprite${i}.png`, + content: assetPath(pkg, `sp-${i + 1}.png`), + }); + } + createFolder( + pkg, + { + name: testName, + files: [], + folders: [ + { + name: 'sprites{tps}', + files: sprites, + folders: [], + }, + ], + }); +} + +describe('Texture Packer Cache Buster', () => +{ + it('should create a sprite sheet and correctly update json', async () => + { + const testName = 'tp-cache-bust'; + const inputDir = getInputDir(pkg, testName); + const outputDir = getOutputDir(pkg, testName); + + genFolder(testName); + + const assetpack = new AssetPack({ + entry: inputDir, + output: outputDir, + cache: false, + pipes: [ + texturePacker({ + resolutionOptions: { + resolutions: { default: 1 }, + }, + }), + cacheBuster(), + texturePackerCacheBuster() + ] + }); + + await assetpack.run(); + + const sheet1 = readJSONSync(`${outputDir}/sprites-y1nb-g.json`); + + expect(sheet1.meta.image).toEqual('sprites-mG8rgA.png'); + }); +});