This commit is contained in:
Brett Jephson
2026-04-30 10:45:25 +01:00
parent 07be0e224c
commit 72cffdd14b
5 changed files with 26 additions and 9 deletions
@@ -15,6 +15,9 @@ function escapeAttribute(value: string): string {
.replaceAll('>', '>');
}
/**
* Load and memoize the generated symbol manifest for a Font Awesome style.
*/
export async function getIconStyleManifest(style: string): Promise<StyleIconSymbolManifest | null> {
const load = loaders[style as SupportedSymbolStyle];
if (!load) {
@@ -34,6 +37,10 @@ export async function getIconStyleManifest(style: string): Promise<StyleIconSymb
return manifestPromise;
}
/**
* Resolve one icon entry from the generated manifest and serialize it for sprite injection or
* lazy-loading through the symbol route.
*/
export async function getIconSymbol(style: string, icon: string, symbolId: string) {
const manifest = await getIconStyleManifest(style);
const entry = manifest?.[icon];
+1 -1
View File
@@ -54,7 +54,7 @@ export const Icon = React.forwardRef(function Icon(
const [iconStyle, icon] = getIconStyle(propIconStyle, propIcon);
const maskId = React.useId();
const iconInstanceId = React.useId();
const symbolId = getIconSymbolId(context.symbolIdPrefix, iconStyle, icon);
const symbolId = getIconSymbolId(iconStyle, icon);
if (context.renderMode === 'symbol') {
registerServerIconSymbol({
+4
View File
@@ -118,6 +118,10 @@ function setIconFallbackState(instanceId: string, failed: boolean) {
icon.setAttribute('data-gb-icon-symbol-state', failed ? 'failed' : 'loaded');
}
/**
* Ensure a symbol referenced by an inline `<use>` exists after hydration, fetching it from the
* internal symbol route only when the SSR sprite did not already include it.
*/
export function IconSymbolLoader(props: {
instanceId: string;
symbolId: string;
-4
View File
@@ -20,8 +20,6 @@ export type IconsContextType = Partial<IconsAssetsLocation> & {
iconStyle: IconStyle;
/** Rendering strategy for icons */
renderMode: IconRenderMode;
/** Prefix used for inline SVG symbol ids */
symbolIdPrefix?: string;
/** Internal route used to lazily load symbols introduced after hydration */
symbolLoaderURL?: string;
};
@@ -43,7 +41,6 @@ export function IconsProvider(props: React.PropsWithChildren<Partial<IconsContex
iconStyle = parent.iconStyle,
assetsByStyles = parent.assetsByStyles,
renderMode = parent.renderMode,
symbolIdPrefix = parent.symbolIdPrefix,
symbolLoaderURL = parent.symbolLoaderURL,
} = props;
const value = {
@@ -52,7 +49,6 @@ export function IconsProvider(props: React.PropsWithChildren<Partial<IconsContex
iconStyle,
assetsByStyles,
renderMode,
symbolIdPrefix,
symbolLoaderURL,
};
+14 -4
View File
@@ -12,10 +12,11 @@ function sanitizeSymbolFragment(fragment: string): string {
return fragment.replace(/[^a-zA-Z0-9_-]/g, '-');
}
export function getIconSymbolId(prefix: string | undefined, style: string, icon: string): string {
const basePrefix = prefix ?? 'gb-icon-';
return `${sanitizeSymbolFragment(basePrefix)}${sanitizeSymbolFragment(style)}-${sanitizeSymbolFragment(icon)}`;
/**
* Build the DOM id used by both SSR-emitted symbols and the lazy symbol loader.
*/
export function getIconSymbolId(style: string, icon: string): string {
return `gb-icon-${sanitizeSymbolFragment(style)}-${sanitizeSymbolFragment(icon)}`;
}
function getRegisteredSymbolsStore(): Map<string, RegisteredIconSymbol> {
@@ -36,6 +37,9 @@ function shouldTrackSymbolRegistrations() {
return typeof window === 'undefined' || typeof runtime.Bun !== 'undefined';
}
/**
* Record a symbol used during server rendering so the app can emit a deduplicated sprite subset.
*/
export function registerServerIconSymbol(symbol: RegisteredIconSymbol): void {
if (!shouldTrackSymbolRegistrations()) {
return;
@@ -44,10 +48,16 @@ export function registerServerIconSymbol(symbol: RegisteredIconSymbol): void {
getRegisteredSymbolsStore().set(`${symbol.style}/${symbol.icon}`, symbol);
}
/**
* Return the currently registered server-rendered symbols in insertion order.
*/
export function getRegisteredServerIconSymbols(): RegisteredIconSymbol[] {
return [...getRegisteredSymbolsStore().values()];
}
/**
* Reset the per-request symbol registry after the sprite subset has been emitted.
*/
export function clearRegisteredServerIconSymbols(): void {
getRegisteredSymbolsStore().clear();
}