mirror of
https://github.com/iconify/iconify.git
synced 2024-11-16 09:37:09 +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 { getIconsCSSData } from '@iconify/utils/lib/css/icons';
|
||||||
import { loadIconSet } from './loader';
|
import { loadIconSet } from './helpers/loader';
|
||||||
import { getIconNames } from './names';
|
import { getIconNames } from './helpers/names';
|
||||||
import type { CleanIconifyPluginOptions } from './options';
|
import type { CleanIconifyPluginOptions } from './helpers/options';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get CSS rules for icons list
|
* Get CSS rules for icons list
|
||||||
@ -17,7 +17,7 @@ export function getCSSRulesForIcons(
|
|||||||
|
|
||||||
// Parse all icon sets
|
// Parse all icon sets
|
||||||
for (const prefix in prefixes) {
|
for (const prefix in prefixes) {
|
||||||
const iconSet = loadIconSet(prefix, options);
|
const iconSet = loadIconSet(options.iconSets?.[prefix] || prefix);
|
||||||
if (!iconSet) {
|
if (!iconSet) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Cannot load icon set for "${prefix}". Install "@iconify-json/${prefix}" as dev dependency?`
|
`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 { getIconsCSSData } from '@iconify/utils/lib/css/icons';
|
||||||
import { matchIconName } from '@iconify/utils/lib/icon/name';
|
import { matchIconName } from '@iconify/utils/lib/icon/name';
|
||||||
import { loadIconSet } from './loader';
|
import { loadIconSet } from './helpers/loader';
|
||||||
import type { DynamicIconifyPluginOptions } from './options';
|
import type { DynamicIconifyPluginOptions } from './helpers/options';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get dynamic CSS rules
|
* Get dynamic CSS rules
|
||||||
@ -20,7 +20,7 @@ export function getDynamicCSSRules(
|
|||||||
throw new Error(`Invalid icon name: "${icon}"`);
|
throw new Error(`Invalid icon name: "${icon}"`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const iconSet = loadIconSet(prefix, options);
|
const iconSet = loadIconSet(options.iconSets?.[prefix] || prefix);
|
||||||
if (!iconSet) {
|
if (!iconSet) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Cannot load icon set for "${prefix}". Install "@iconify-json/${prefix}" as dev dependency?`
|
`Cannot load icon set for "${prefix}". Install "@iconify-json/${prefix}" as dev dependency?`
|
||||||
@ -35,11 +35,11 @@ export function getDynamicCSSRules(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (scale) {
|
if (scale) {
|
||||||
generated.common.rules.height = scale + 'em'
|
generated.common.rules.height = scale + 'em';
|
||||||
generated.common.rules.width = scale + 'em'
|
generated.common.rules.width = scale + 'em';
|
||||||
} else {
|
} else {
|
||||||
delete generated.common.rules.height
|
delete generated.common.rules.height;
|
||||||
delete generated.common.rules.width
|
delete generated.common.rules.width;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
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 {
|
import type {
|
||||||
CleanIconifyPluginOptions,
|
CleanIconifyPluginOptions,
|
||||||
DynamicIconifyPluginOptions,
|
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]"
|
* 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 types
|
||||||
*/
|
*/
|
||||||
export type { CleanIconifyPluginOptions, DynamicIconifyPluginOptions };
|
export type {
|
||||||
|
CleanIconifyPluginOptions,
|
||||||
|
DynamicIconifyPluginOptions,
|
||||||
|
IconifyPluginOptions,
|
||||||
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user