auto: PR for feat/export-bundle-phase6 #62
@@ -16,6 +16,7 @@ export type AssetRef = {
|
|||||||
export type AssetManagerOptions = {
|
export type AssetManagerOptions = {
|
||||||
maxBytes: number
|
maxBytes: number
|
||||||
allowedExts: string[] // e.g., ['png','jpg','jpeg','webp','svg']
|
allowedExts: string[] // e.g., ['png','jpg','jpeg','webp','svg']
|
||||||
|
pathStrategy?: 'flat' | 'grouped'
|
||||||
}
|
}
|
||||||
|
|
||||||
function extOf(name: string): string {
|
function extOf(name: string): string {
|
||||||
@@ -36,6 +37,7 @@ function hash8(bytes: Uint8Array): string {
|
|||||||
export class AssetManager {
|
export class AssetManager {
|
||||||
private readonly maxBytes: number
|
private readonly maxBytes: number
|
||||||
private readonly allowed: Set<string>
|
private readonly allowed: Set<string>
|
||||||
|
private readonly pathStrategy: 'flat' | 'grouped'
|
||||||
private byHash = new Map<string, AssetRef>()
|
private byHash = new Map<string, AssetRef>()
|
||||||
private byOriginal = new Map<string, string>() // originalName -> path
|
private byOriginal = new Map<string, string>() // originalName -> path
|
||||||
private referenced = new Set<string>() // originalName referenced via rewrite
|
private referenced = new Set<string>() // originalName referenced via rewrite
|
||||||
@@ -43,6 +45,7 @@ export class AssetManager {
|
|||||||
constructor(opts: AssetManagerOptions) {
|
constructor(opts: AssetManagerOptions) {
|
||||||
this.maxBytes = opts.maxBytes
|
this.maxBytes = opts.maxBytes
|
||||||
this.allowed = new Set(opts.allowedExts.map((e) => e.toLowerCase()))
|
this.allowed = new Set(opts.allowedExts.map((e) => e.toLowerCase()))
|
||||||
|
this.pathStrategy = opts.pathStrategy ?? 'flat'
|
||||||
}
|
}
|
||||||
|
|
||||||
async add(file: AssetInput): Promise<AssetRef> {
|
async add(file: AssetInput): Promise<AssetRef> {
|
||||||
@@ -61,7 +64,7 @@ export class AssetManager {
|
|||||||
return existing
|
return existing
|
||||||
}
|
}
|
||||||
|
|
||||||
const path = `assets/${h}.${ext}`
|
const path = this.makePath(h, ext, file.type)
|
||||||
const ref: AssetRef = {
|
const ref: AssetRef = {
|
||||||
originalName: file.name,
|
originalName: file.name,
|
||||||
mimeType: file.type,
|
mimeType: file.type,
|
||||||
@@ -101,4 +104,14 @@ export class AssetManager {
|
|||||||
referencedList(): string[] {
|
referencedList(): string[] {
|
||||||
return Array.from(this.referenced)
|
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}`
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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<n;i++) a[i]=i&0xff; return a }
|
||||||
|
|
||||||
|
describe('Assets index builder', () => {
|
||||||
|
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$/)
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user