figma-icon-getter
Version:
A simple solution to get component set icons from Figma
99 lines (98 loc) • 3.6 kB
JavaScript
import { z } from "zod";
function parseIconName(iconName, acc = []) {
const match = iconName.match(/^,?\s*([^=]+)=([^,]+)(.*)/);
if (match) {
const [_, k, v, tail] = match;
return parseIconName(tail, acc.concat([[k, v]]));
}
return Object.fromEntries(acc);
}
export async function getFigmaIcons({ figmaAccessToken, fileKey, nodeIds, }) {
function figmaFetch(url, requestInit, nodeIds) {
const baseUrl = new URL(`https://api.figma.com/v1${url}`);
if (nodeIds) {
baseUrl.searchParams.set('ids', nodeIds.join(','));
}
return fetch(baseUrl.toString(), {
...requestInit,
headers: {
...requestInit?.headers,
"X-Figma-Token": figmaAccessToken,
},
});
}
const fileRes = await figmaFetch(`/files/${fileKey}`, undefined, nodeIds);
if (!fileRes.ok) {
const details = await fileRes.text();
throw new Error(`An error occurred while fetching Figma file with key ${fileKey}. Unexpected ${fileRes.status} response: ${details}`);
}
const iconMetadata = z
.object({
componentSets: z.record(z.string(), z.object({
name: z.string(),
})),
components: z
.record(z.string(), z
.object({
name: z.string(),
componentSetId: z.string(),
})
.transform(({ name, ...rest }) => ({
...rest,
properties: parseIconName(name),
})))
.transform((components) => Object.entries(components).map(([id, iconMetadata]) => ({
id,
...iconMetadata,
}))),
})
.transform(({ componentSets, ...file }, ctx) => {
const components = file.components.map(({ componentSetId, ...rest }) => ({
componentSetId,
name: componentSets[componentSetId]?.name,
...rest,
}));
for (const { componentSetId, name } of components) {
if (!name) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: `Unable to get icon name: Component set with id ${componentSetId} was not found.`,
});
return z.NEVER;
}
}
return components.map(({ componentSetId: _, ...rest }) => rest);
})
.parse(await fileRes.json());
const iconRefsRes = await figmaFetch(`/images/${fileKey}?format=svg`, undefined, iconMetadata
.map(({ id }) => id));
return await z
.object({
images: z.record(z.string(), z.string().url()),
})
.transform(({ images }, ctx) => Promise.all(Object.entries(images).map(async ([id, url]) => {
const iconMeta = iconMetadata.find((meta) => meta.id === id);
if (!iconMeta) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: `Unable to find icon metadata for id ${id}.`,
});
return z.NEVER;
}
const fileRes = await fetch(url);
if (!fileRes.ok) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: `Unexpected ${fileRes.status} (${fileRes.statusText}) response while fetching image with id ${id} from ${url}.`,
});
return z.NEVER;
}
const { name, properties } = iconMeta;
return {
name,
properties,
svg: await fileRes.text(),
};
})))
.parseAsync(await iconRefsRes.json());
}