diff --git a/plugins/tailwind/src/iconify.ts b/plugins/tailwind/src/iconify.ts index 3eb7f8e..c5f9662 100644 --- a/plugins/tailwind/src/iconify.ts +++ b/plugins/tailwind/src/iconify.ts @@ -1,45 +1,11 @@ -import { readFileSync } from 'fs'; -import type { IconifyJSON } from '@iconify/types'; import { getIconsCSSData } from '@iconify/utils/lib/css/icons'; import { matchIconName } from '@iconify/utils/lib/icon/name'; -import type { IconifyPluginFileOptions, IconifyPluginOptions } from './options'; +import { loadIconSet } from './loader'; +import type { IconifyPluginOptions } from './options'; const missingIconsListError = 'TailwindCSS cannot dynamically find all used icons. Need to pass list of used icons to Iconify plugin.'; -/** - * Locate icon set - */ -function locateIconSet( - prefix: string, - options: IconifyPluginFileOptions -): string | undefined { - if (options.files?.[prefix]) { - return options.files?.[prefix]; - } - try { - return require.resolve(`@iconify-json/${prefix}/icons.json`); - } catch {} - try { - return require.resolve(`@iconify/json/json/${prefix}.json`); - } catch {} -} - -/** - * Load icon set - */ -function loadIconSet( - prefix: string, - options: IconifyPluginFileOptions -): IconifyJSON | undefined { - const filename = locateIconSet(prefix, options); - if (filename) { - try { - return JSON.parse(readFileSync(filename, 'utf8')); - } catch {} - } -} - /** * Get icon names from list */ diff --git a/plugins/tailwind/src/loader.ts b/plugins/tailwind/src/loader.ts new file mode 100644 index 0000000..82f86e8 --- /dev/null +++ b/plugins/tailwind/src/loader.ts @@ -0,0 +1,82 @@ +import { readFileSync } from 'fs'; +import type { IconifyJSON } from '@iconify/types'; + +/** + * Callback for loading icon set + */ +type IconifyJSONLoaderCallback = () => IconifyJSON; + +/** + * Options for icon set loaders + */ +export interface IconifyPluginLoaderOptions { + // Location of icon set files. Key is icon set prefix + files?: Record; + + // Custom icon sets + // Value can be loaded icon set or callback that loads icon set + iconSets?: Record; +} + +/** + * Locate icon set + */ +export function locateIconSet( + prefix: string, + options: IconifyPluginLoaderOptions +): string | undefined { + if (options.files?.[prefix]) { + return options.files?.[prefix]; + } + try { + return require.resolve(`@iconify-json/${prefix}/icons.json`); + } catch {} + try { + return require.resolve(`@iconify/json/json/${prefix}.json`); + } catch {} +} + +/** + * Cache for loaded icon sets + * + * Tailwind CSS can send multiple separate requests to plugin, this will + * prevent same data from being loaded multiple times. + * + * Key is filename, not prefix! + */ +const cache = Object.create(null) as Record; + +/** + * Load icon set + */ +export function loadIconSet( + prefix: string, + options: IconifyPluginLoaderOptions +): IconifyJSON | undefined { + // Check for custom icon set + const customIconSet = options.iconSets?.[prefix]; + if (customIconSet) { + if (typeof customIconSet === 'function') { + // Callback. Store result in options to avoid loading it again + const result = customIconSet(); + options.iconSets[prefix] = result; + return result; + } + return customIconSet; + } + + const filename = options.files?.[prefix] || locateIconSet(prefix, options); + if (filename) { + // Check for cache + if (cache[filename]) { + return cache[filename]; + } + + // Attempt to load it + try { + const result = JSON.parse(readFileSync(filename, 'utf8')); + cache[filename] = result; + return result; + } catch {} + } +}