mirror of
https://github.com/iconify/iconify.git
synced 2024-12-04 18:23:17 +00:00
chore: restructure tailwind plugin
This commit is contained in:
parent
879f21637b
commit
ca87add889
@ -1,7 +1,7 @@
|
||||
import { getIconsCSSData } from '@iconify/utils/lib/css/icons';
|
||||
import { loadIconSet } from './loader';
|
||||
import { getIconNames } from './names';
|
||||
import type { CleanIconifyPluginOptions } from './options';
|
||||
import { loadIconSet } from './helpers/loader';
|
||||
import { getIconNames } from './helpers/names';
|
||||
import type { CleanIconifyPluginOptions } from './helpers/options';
|
||||
|
||||
/**
|
||||
* Get CSS rules for icons list
|
||||
@ -17,7 +17,7 @@ export function getCSSRulesForIcons(
|
||||
|
||||
// Parse all icon sets
|
||||
for (const prefix in prefixes) {
|
||||
const iconSet = loadIconSet(prefix, options);
|
||||
const iconSet = loadIconSet(options.iconSets?.[prefix] || prefix);
|
||||
if (!iconSet) {
|
||||
throw new Error(
|
||||
`Cannot load icon set for "${prefix}". Install "@iconify-json/${prefix}" as dev dependency?`
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { getIconsCSSData } from '@iconify/utils/lib/css/icons';
|
||||
import { matchIconName } from '@iconify/utils/lib/icon/name';
|
||||
import { loadIconSet } from './loader';
|
||||
import type { DynamicIconifyPluginOptions } from './options';
|
||||
import { loadIconSet } from './helpers/loader';
|
||||
import type { DynamicIconifyPluginOptions } from './helpers/options';
|
||||
|
||||
/**
|
||||
* Get dynamic CSS rules
|
||||
@ -20,7 +20,7 @@ export function getDynamicCSSRules(
|
||||
throw new Error(`Invalid icon name: "${icon}"`);
|
||||
}
|
||||
|
||||
const iconSet = loadIconSet(prefix, options);
|
||||
const iconSet = loadIconSet(options.iconSets?.[prefix] || prefix);
|
||||
if (!iconSet) {
|
||||
throw new Error(
|
||||
`Cannot load icon set for "${prefix}". Install "@iconify-json/${prefix}" as dev dependency?`
|
||||
@ -35,11 +35,11 @@ export function getDynamicCSSRules(
|
||||
}
|
||||
|
||||
if (scale) {
|
||||
generated.common.rules.height = scale + 'em'
|
||||
generated.common.rules.width = scale + 'em'
|
||||
generated.common.rules.height = scale + 'em';
|
||||
generated.common.rules.width = scale + 'em';
|
||||
} else {
|
||||
delete generated.common.rules.height
|
||||
delete generated.common.rules.width
|
||||
delete generated.common.rules.height;
|
||||
delete generated.common.rules.width;
|
||||
}
|
||||
|
||||
return {
|
||||
|
112
plugins/tailwind/src/helpers/loader.ts
Normal file
112
plugins/tailwind/src/helpers/loader.ts
Normal file
@ -0,0 +1,112 @@
|
||||
import { readFileSync } from 'node:fs';
|
||||
import type { IconifyJSON } from '@iconify/types';
|
||||
import { IconifyIconSetSource } from './options';
|
||||
import { matchIconName } from '@iconify/utils/lib/icon/name';
|
||||
|
||||
/**
|
||||
* Locate icon set
|
||||
*/
|
||||
interface LocatedIconSet {
|
||||
main: string;
|
||||
info?: string;
|
||||
}
|
||||
export function locateIconSet(prefix: string): LocatedIconSet | undefined {
|
||||
// Try `@iconify-json/{$prefix}`
|
||||
try {
|
||||
const main = require.resolve(`@iconify-json/${prefix}/icons.json`);
|
||||
const info = require.resolve(`@iconify-json/${prefix}/info.json`);
|
||||
return {
|
||||
main,
|
||||
info,
|
||||
};
|
||||
} catch {}
|
||||
|
||||
// Try `@iconify/json`
|
||||
try {
|
||||
const main = require.resolve(`@iconify/json/json/${prefix}.json`);
|
||||
return {
|
||||
main,
|
||||
};
|
||||
} 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<string, IconifyJSON>;
|
||||
|
||||
/**
|
||||
* Load icon set from file
|
||||
*/
|
||||
function loadIconSetFromFile(source: LocatedIconSet): IconifyJSON | undefined {
|
||||
try {
|
||||
const result = JSON.parse(readFileSync(source.main, 'utf8'));
|
||||
if (!result.info && source.info) {
|
||||
// Load info from a separate file
|
||||
result.info = JSON.parse(readFileSync(source.info, 'utf8'));
|
||||
}
|
||||
return result;
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load icon set from source
|
||||
*/
|
||||
export function loadIconSet(
|
||||
source: IconifyIconSetSource
|
||||
): IconifyJSON | undefined {
|
||||
let filename: LocatedIconSet;
|
||||
|
||||
if (typeof source === 'function') {
|
||||
// Callback
|
||||
return source();
|
||||
}
|
||||
|
||||
if (typeof source === 'object') {
|
||||
// IconifyJSON
|
||||
return source;
|
||||
}
|
||||
|
||||
// String
|
||||
|
||||
// Try to parse JSON
|
||||
if (source.startsWith('{')) {
|
||||
try {
|
||||
return JSON.parse(source);
|
||||
} catch {
|
||||
// Invalid JSON
|
||||
}
|
||||
}
|
||||
|
||||
// Check for cache
|
||||
if (cache[source]) {
|
||||
return cache[source];
|
||||
}
|
||||
|
||||
// Icon set prefix
|
||||
if (source.match(matchIconName)) {
|
||||
const filename = locateIconSet(source);
|
||||
if (filename) {
|
||||
// Load icon set
|
||||
const result = loadIconSetFromFile(filename);
|
||||
if (result) {
|
||||
cache[source] = result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// Filename
|
||||
const result = loadIconSetFromFile({
|
||||
main: source,
|
||||
});
|
||||
if (result) {
|
||||
cache[source] = result;
|
||||
}
|
||||
return result;
|
||||
}
|
96
plugins/tailwind/src/helpers/options.ts
Normal file
96
plugins/tailwind/src/helpers/options.ts
Normal file
@ -0,0 +1,96 @@
|
||||
import type { IconCSSIconSetOptions } from '@iconify/utils/lib/css/types';
|
||||
import type { IconifyJSON } from '@iconify/types';
|
||||
|
||||
// Callback for loading icon set
|
||||
type IconifyJSONLoaderCallback = () => IconifyJSON;
|
||||
|
||||
// Source for icon set: icon set, filename, or callback that loads icon set
|
||||
export type IconifyIconSetSource =
|
||||
| IconifyJSON
|
||||
| string
|
||||
| IconifyJSONLoaderCallback;
|
||||
|
||||
/**
|
||||
* Common options
|
||||
*/
|
||||
export interface CommonIconifyPluginOptions {
|
||||
// Custom icon sets
|
||||
// Value can be loaded icon set or callback that loads icon set
|
||||
iconSets?: Record<string, IconifyIconSetSource>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for clean class names
|
||||
*/
|
||||
export interface CleanIconifyPluginOptions
|
||||
extends CommonIconifyPluginOptions,
|
||||
IconCSSIconSetOptions {
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for dynamic class names
|
||||
*/
|
||||
export interface DynamicIconifyPluginOptions
|
||||
extends CommonIconifyPluginOptions {
|
||||
// Class prefix
|
||||
prefix?: string;
|
||||
|
||||
// Include icon-specific selectors only
|
||||
overrideOnly?: true;
|
||||
|
||||
// Sets the default height/width value (ex. scale: 2 = 2em)
|
||||
scale?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for reusable selectors
|
||||
*/
|
||||
export interface ReusableIconifyPluginOptions {
|
||||
// Selector for mask, defaults to ".iconify"
|
||||
mask?: string;
|
||||
|
||||
// Selector for background, defaults to ".iconify-color"
|
||||
background?: string;
|
||||
|
||||
// Variable name, defaults to "svg"
|
||||
varName?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for main plugin
|
||||
*/
|
||||
|
||||
// Icons to include: array of names or callback
|
||||
type IconsListOption = string[] | ((name: string) => boolean);
|
||||
|
||||
// Source filename or icon set
|
||||
type IconSetSource = string | IconifyJSON;
|
||||
|
||||
// Full icon set options
|
||||
interface IconSetOptions {
|
||||
// Prefix, required if `source` is not set
|
||||
// If both `source` and `prefix` are set, `prefix` will be used
|
||||
prefix?: string;
|
||||
|
||||
// Source
|
||||
source?: IconSetSource;
|
||||
|
||||
// Icons to load
|
||||
icons?: IconsListOption;
|
||||
}
|
||||
|
||||
// Array of icon sets to load
|
||||
type IconifyPluginListOptions = (string | IconSetOptions)[];
|
||||
|
||||
// Full object
|
||||
export interface IconifyPluginOptionsObject
|
||||
extends ReusableIconifyPluginOptions {
|
||||
// Prefixes to load
|
||||
prefixes: IconifyPluginListOptions;
|
||||
}
|
||||
|
||||
// Full options
|
||||
export type IconifyPluginOptions =
|
||||
| IconifyPluginOptionsObject
|
||||
| IconifyPluginListOptions;
|
@ -1,109 +0,0 @@
|
||||
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 {
|
||||
// Custom icon sets
|
||||
// Value can be loaded icon set or callback that loads icon set
|
||||
iconSets?: Record<string, IconifyJSON | string | IconifyJSONLoaderCallback>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Locate icon set
|
||||
*/
|
||||
interface LocatedIconSet {
|
||||
main: string;
|
||||
info?: string;
|
||||
}
|
||||
export function locateIconSet(prefix: string): LocatedIconSet | undefined {
|
||||
try {
|
||||
const main = require.resolve(`@iconify-json/${prefix}/icons.json`);
|
||||
const info = require.resolve(`@iconify-json/${prefix}/info.json`);
|
||||
return {
|
||||
main,
|
||||
info,
|
||||
};
|
||||
} catch {}
|
||||
try {
|
||||
const main = require.resolve(`@iconify/json/json/${prefix}.json`);
|
||||
return {
|
||||
main,
|
||||
};
|
||||
} 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<string, IconifyJSON>;
|
||||
|
||||
/**
|
||||
* Load icon set
|
||||
*/
|
||||
export function loadIconSet(
|
||||
prefix: string,
|
||||
options: IconifyPluginLoaderOptions
|
||||
): IconifyJSON | undefined {
|
||||
let filename: LocatedIconSet;
|
||||
|
||||
// Check for custom icon set
|
||||
const customIconSet = options.iconSets?.[prefix];
|
||||
if (customIconSet) {
|
||||
switch (typeof customIconSet) {
|
||||
case 'function': {
|
||||
// Callback. Store result in options to avoid loading it again
|
||||
const result = customIconSet();
|
||||
options.iconSets[prefix] = result;
|
||||
return result;
|
||||
}
|
||||
|
||||
case 'string': {
|
||||
// Filename to load it from
|
||||
filename = {
|
||||
main: customIconSet,
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
return customIconSet;
|
||||
}
|
||||
} else {
|
||||
// Find icon set
|
||||
filename = locateIconSet(prefix);
|
||||
}
|
||||
|
||||
if (!filename) {
|
||||
return;
|
||||
}
|
||||
|
||||
const main = typeof filename === 'string' ? filename : filename.main;
|
||||
|
||||
// Check for cache
|
||||
if (cache[main]) {
|
||||
return cache[main];
|
||||
}
|
||||
|
||||
// Attempt to load it
|
||||
try {
|
||||
const result = JSON.parse(readFileSync(main, 'utf8'));
|
||||
if (!result.info && typeof filename === 'object' && filename.info) {
|
||||
// Load info from a separate file
|
||||
result.info = JSON.parse(readFileSync(filename.info, 'utf8'));
|
||||
}
|
||||
cache[main] = result;
|
||||
return result;
|
||||
} catch {}
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
import type { IconCSSIconSetOptions } from '@iconify/utils/lib/css/types';
|
||||
import type { IconifyPluginLoaderOptions } from './loader';
|
||||
|
||||
/**
|
||||
* Common options
|
||||
*/
|
||||
export interface CommonIconifyPluginOptions extends IconifyPluginLoaderOptions {
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for clean class names
|
||||
*/
|
||||
export interface CleanIconifyPluginOptions
|
||||
extends CommonIconifyPluginOptions,
|
||||
IconCSSIconSetOptions {
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for dynamic class names
|
||||
*/
|
||||
export interface DynamicIconifyPluginOptions
|
||||
extends CommonIconifyPluginOptions {
|
||||
// Class prefix
|
||||
prefix?: string;
|
||||
|
||||
// Include icon-specific selectors only
|
||||
overrideOnly?: true;
|
||||
|
||||
// Sets the default height/width value (ex. scale: 2 = 2em)
|
||||
scale?: number
|
||||
}
|
@ -4,7 +4,10 @@ import { getDynamicCSSRules } from './dynamic';
|
||||
import type {
|
||||
CleanIconifyPluginOptions,
|
||||
DynamicIconifyPluginOptions,
|
||||
} from './options';
|
||||
IconifyPluginOptions,
|
||||
IconifyPluginOptionsObject,
|
||||
} from './helpers/options';
|
||||
import { getCommonCSSRules } from '@iconify/utils/lib/css/common';
|
||||
|
||||
/**
|
||||
* Generate styles for dynamic selector: class="icon-[mdi-light--home]"
|
||||
@ -38,7 +41,54 @@ export function addCleanIconSelectors(
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Iconify plugin
|
||||
*
|
||||
* TODO: export it when ready
|
||||
*/
|
||||
function iconifyPlugin(options: IconifyPluginOptions) {
|
||||
return plugin(({ addUtilities }) => {
|
||||
const rules = Object.create(null) as Record<
|
||||
string,
|
||||
Record<string, string>
|
||||
>;
|
||||
|
||||
// Convert options to object
|
||||
const fullOptions: IconifyPluginOptionsObject = Array.isArray(options)
|
||||
? {
|
||||
prefixes: options,
|
||||
}
|
||||
: options;
|
||||
|
||||
// Variable name, default to 'svg' (cannot be empty string)
|
||||
const varName = fullOptions.varName || 'svg';
|
||||
|
||||
// Add common rules
|
||||
const mask = fullOptions.mask ?? '.iconify';
|
||||
const background = fullOptions.background ?? '.iconify-color';
|
||||
if (mask) {
|
||||
rules[mask] = getCommonCSSRules({
|
||||
mode: 'mask',
|
||||
varName,
|
||||
});
|
||||
}
|
||||
if (background) {
|
||||
rules[background] = getCommonCSSRules({
|
||||
mode: 'background',
|
||||
varName,
|
||||
});
|
||||
}
|
||||
addUtilities(rules);
|
||||
|
||||
// TODO: add icon sets
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Export types
|
||||
*/
|
||||
export type { CleanIconifyPluginOptions, DynamicIconifyPluginOptions };
|
||||
export type {
|
||||
CleanIconifyPluginOptions,
|
||||
DynamicIconifyPluginOptions,
|
||||
IconifyPluginOptions,
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user