defineAssetSource
Keep authored asset names at call sites while a planner selects the optimized output.
Demo
authored path
common/textures/noise.pngresolved URLhttps://r2.joyco.studio/jam/common/textures/compressed/noise.webproutingoptimized'use client'
import { defineAssetSource } from '@joycostudio/xyz'
import { useMemo, useState } from 'react'
import { Button } from '@/components/ui/button'
import { Cluster } from '@/components/ui/cluster'
import { DemoPanel, ValueRow, ValueRows } from '@/components/demos/demo-ui'
export default function AssetDemo() {
const [optimized, setOptimized] = useState(true)
const resolve = useMemo(
() =>
defineAssetSource({
url: new URL('https://r2.joyco.studio/jam/'),
optimize: optimized,
planner: (input) => ({
ok: true,
data: input.endsWith('.png') ? [{ output: input.replace(/([^/]+)\.png$/, 'compressed/$1.webp') }] : [],
}),
segments: { common: 'common' },
}),
[optimized]
)
return (
<DemoPanel>
<ValueRows>
<ValueRow label="authored path">common/textures/noise.png</ValueRow>
<ValueRow label="resolved URL">{resolve.common('textures/noise.png')}</ValueRow>
<ValueRow label="routing">{optimized ? 'optimized' : 'source'}</ValueRow>
</ValueRows>
<Cluster className="mt-5">
<Button onClick={() => setOptimized((value) => !value)}>Use {optimized ? 'source' : 'optimized'} asset</Button>
</Cluster>
</DemoPanel>
)
}
Usage
import { defineAssetSource } from '@joycostudio/xyz'
const asset = defineAssetSource({
url: new URL('https://cdn.example.com/'),
segments: { textures: 'textures', models: 'models' },
})
asset.textures('noise.png') // https://cdn.example.com/textures/noise.png
asset.models('scene.glb') // https://cdn.example.com/models/scene.glb
API
defineAssetSource
defineAssetSource<const Segments extends Record<string, string> = Record<never, never>>(
definition: AssetSourceDefinition<Segments>
): AssetSource<Segments>
Creates a callable URL resolver with typed builders for each configured segment. Absolute URLs pass through unchanged. Relative paths keep their query and hash suffixes, including when routed to a planned output.
| Argument | Description |
|---|---|
definition.url | Required URL for the CDN source root. Its pathname is also the prefix passed to the planner. |
definition.planner? | Receives a source path without its query or hash. Return { ok: true, data: [{ output: 'path' }] } to replace it. Unsuccessful results or results with zero or multiple outputs keep the source path. |
definition.segments? | Map of names to path prefixes, exposed as methods on the resolver, such as { textures: 'textures' }. |
definition.optimize? | Boolean or getter controlling planned outputs. Defaults to true; a getter is evaluated on each relative-path resolution. |
const asset = defineAssetSource({
url: new URL('https://cdn.example.com/assets/'),
segments: { textures: 'textures' },
optimize: true,
planner: (input) => ({
ok: true,
data: [{ output: input.replace(/\.png$/, '.webp') }],
}),
})
asset.textures('noise.png') // https://cdn.example.com/assets/textures/noise.webp
asset
asset(file: string): string
The returned resolver builds a URL from the configured root and optional planner.
| Argument | Description |
|---|---|
file | An authored relative asset path or an absolute URL to pass through unchanged. |
asset('noise.png') // https://cdn.example.com/noise.png
asset[segment]
asset.textures(file: string): string
Each name in definition.segments becomes a method. It adds the configured prefix before resolving the URL; absolute URLs still pass through unchanged.
| Argument | Description |
|---|---|
file | Asset path relative to the segment, or an absolute URL. |
asset.textures('noise.png') // https://cdn.example.com/textures/noise.png