Codegen
Generate a manifest and per-icon import map for any icon set, for use in a custom adapter.
@10x-media/fields/icon/codegen generates the artifacts an adapter needs: a manifest (names, tags, categories for browsing and validation) and, optionally, a per-icon import map (for lazy loading) and bulk glyph node-data (for fast drawer rendering). Run it at build time in your project and commit the output next to your adapter.
import { generateIconManifest } from '@10x-media/fields/icon/codegen'
await generateIconManifest({
source: {
icons: [
{ name: 'rocket', tags: ['launch', 'ship'], categories: ['brand'] },
{ name: 'planet', tags: ['world'], categories: ['brand'] },
],
importFor: (icon) => ({ module: `@acme/icons/${icon.name}` }),
},
outDir: './src/icons/acme',
regenCommand: 'pnpm generate:acme-icons',
})You describe the icon set as a source: either a built-in library name ('lucide' | 'radix' | 'tabler') or a custom object with an icons array (each entry a { name, tags, categories }), an optional importFor that maps an icon to its dynamic import, and optional nodes bulk glyph data.
The run emits into outDir:
manifest.tsalways, exporting a lazily importablemanifest.imports.tswhenimportForis set, exporting aniconImportsmap of name to dynamic import.nodes.tswhennodesis set, for inline-SVG drawer rendering (omit it for libraries the drawer renders through the per-iconIconcomponent, like Radix).
Icon names are validated as kebab-case, de-duplicated, and sorted, so committed output is byte-stable across machines. allowMissingCategories lets a source that cannot fetch category metadata emit without categories (off by default, so a broken run fails loudly instead of dropping categories silently).
Wire the output into an adapter and its client components:
import { defineIconAdapter } from '@10x-media/fields/icon'
export const acmeIconsAdapter = defineIconAdapter({
slug: 'acme',
label: 'Acme Icons',
loadManifest: () => import('./manifest').then((m) => m.manifest),
Icon: '@/icons/acme/client#AcmeAdapterIcon',
Assets: '@/icons/acme/client#AcmeAdapterAssets',
})The Icon component resolves a name through the generated iconImports map (a React.lazy per entry for the client, an awaited import for RSC).
In-house SVG sets
For a custom set of SVG files, build the icons array yourself, deriving each name from a file (kebab-case) and categories from its folder, then pass it as the source with an importFor that points at your per-icon modules or a wrapper component. Codegen validates and emits; it does not crawl a directory for you.
Recipes
- Heroicons / Phosphor: build the
iconsarray from the package's icon list and pointimportForat each icon's module. Each style variant (outline, solid) works best as its own adapter slug so stored values stay unambiguous. - Full shadcn set: shadcn projects standardize on Lucide, so the first-party
lucideAdapter()already covers them; add codegen adapters only for the extra sets your design system pulls in. - Regeneration: re-run codegen when you bump the icon package. The manifest is data, so a new version adding icons is additive; removing icons turns stored values into gracefully degraded ones rather than errors.