diff --git a/lib/assets/assetManager.ts b/lib/assets/assetManager.ts index 3d5525b..18e1387 100644 --- a/lib/assets/assetManager.ts +++ b/lib/assets/assetManager.ts @@ -16,6 +16,7 @@ export type AssetRef = { export type AssetManagerOptions = { maxBytes: number allowedExts: string[] // e.g., ['png','jpg','jpeg','webp','svg'] + pathStrategy?: 'flat' | 'grouped' } function extOf(name: string): string { @@ -36,6 +37,7 @@ function hash8(bytes: Uint8Array): string { export class AssetManager { private readonly maxBytes: number private readonly allowed: Set + private readonly pathStrategy: 'flat' | 'grouped' private byHash = new Map() private byOriginal = new Map() // originalName -> path private referenced = new Set() // originalName referenced via rewrite @@ -43,6 +45,7 @@ export class AssetManager { constructor(opts: AssetManagerOptions) { this.maxBytes = opts.maxBytes this.allowed = new Set(opts.allowedExts.map((e) => e.toLowerCase())) + this.pathStrategy = opts.pathStrategy ?? 'flat' } async add(file: AssetInput): Promise { @@ -61,7 +64,7 @@ export class AssetManager { return existing } - const path = `assets/${h}.${ext}` + const path = this.makePath(h, ext, file.type) const ref: AssetRef = { originalName: file.name, mimeType: file.type, @@ -101,4 +104,14 @@ export class AssetManager { referencedList(): string[] { return Array.from(this.referenced) } + + private makePath(hash8: string, ext: string, mime: string): string { + if (this.pathStrategy === 'grouped') { + let group = 'other' + if (mime.startsWith('image/')) group = 'images' + else if (mime.startsWith('font/')) group = 'fonts' + return `assets/${group}/${hash8}.${ext}` + } + return `assets/${hash8}.${ext}` + } } diff --git a/lib/exporter/assets.index.test.ts b/lib/exporter/assets.index.test.ts new file mode 100644 index 0000000..fc278f9 --- /dev/null +++ b/lib/exporter/assets.index.test.ts @@ -0,0 +1,22 @@ +import { describe, it, expect } from 'vitest' +import { AssetManager } from '@/lib/assets/assetManager' +import { buildAssetsIndex } from '@/lib/exporter/assets.index' + +function u8(n: number): Uint8Array { const a = new Uint8Array(n); for (let i=0;i { + it('groups assets by mime into images/fonts/other and lists zipPath', async () => { + const am = new AssetManager({ maxBytes: 1024*1024, allowedExts: ['png','woff2','txt'] }) + await am.add({ name: 'a.png', type: 'image/png', bytes: u8(4) }) + await am.add({ name: 'b.woff2', type: 'font/woff2', bytes: u8(6) }) + await am.add({ name: 'c.txt', type: 'text/plain', bytes: u8(2) }) + const bytes = buildAssetsIndex(am) + const obj = JSON.parse(new TextDecoder().decode(bytes)) as { images: string[]; fonts: string[]; other: string[] } + expect(obj.images.length).toBe(1) + expect(obj.fonts.length).toBe(1) + expect(obj.other.length).toBe(1) + expect(obj.images[0]).toMatch(/^assets\/[0-9a-f]{8}\.png$/) + expect(obj.fonts[0]).toMatch(/^assets\/[0-9a-f]{8}\.woff2$/) + expect(obj.other[0]).toMatch(/^assets\/[0-9a-f]{8}\.txt$/) + }) +}) diff --git a/lib/exporter/assets.index.ts b/lib/exporter/assets.index.ts new file mode 100644 index 0000000..fb82246 --- /dev/null +++ b/lib/exporter/assets.index.ts @@ -0,0 +1,21 @@ +import type { AssetManager } from '@/lib/assets/assetManager' + +export type AssetsIndex = { + images: string[] + fonts: string[] + other: string[] +} + +export function buildAssetsIndex(am: AssetManager): Uint8Array { + const images: string[] = [] + const fonts: string[] = [] + const other: string[] = [] + for (const ref of am.list()) { + if (ref.mimeType.startsWith('image/')) images.push(ref.path) + else if (ref.mimeType.startsWith('font/')) fonts.push(ref.path) + else other.push(ref.path) + } + const obj: AssetsIndex = { images, fonts, other } + const json = JSON.stringify(obj, null, 2) + return new TextEncoder().encode(json) +}