mirror of
https://github.com/iconify/iconify.git
synced 2024-11-10 07:11:00 +00:00
Merge pull request #104 from iconify/userquin/refactor-modern-loader
feat: refactor modern loader
This commit is contained in:
commit
2b10edef28
4
package-lock.json
generated
4
package-lock.json
generated
@ -4460,7 +4460,6 @@
|
|||||||
"minimist": "^1.2.5",
|
"minimist": "^1.2.5",
|
||||||
"neo-async": "^2.6.0",
|
"neo-async": "^2.6.0",
|
||||||
"source-map": "^0.6.1",
|
"source-map": "^0.6.1",
|
||||||
"uglify-js": "^3.1.4",
|
|
||||||
"wordwrap": "^1.0.0"
|
"wordwrap": "^1.0.0"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
@ -5245,9 +5244,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
|
||||||
"integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
|
"integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
|
||||||
"graceful-fs": "^4.1.6"
|
|
||||||
},
|
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"graceful-fs": "^4.1.6"
|
"graceful-fs": "^4.1.6"
|
||||||
}
|
}
|
||||||
|
@ -90,14 +90,6 @@
|
|||||||
"require": "./lib/icon/sort.js",
|
"require": "./lib/icon/sort.js",
|
||||||
"import": "./lib/icon/sort.mjs"
|
"import": "./lib/icon/sort.mjs"
|
||||||
},
|
},
|
||||||
"./lib/modern": {
|
|
||||||
"require": "./lib/modern/index.js",
|
|
||||||
"import": "./lib/modern/index.mjs"
|
|
||||||
},
|
|
||||||
"./lib/modern/index": {
|
|
||||||
"require": "./lib/modern/index.js",
|
|
||||||
"import": "./lib/modern/index.mjs"
|
|
||||||
},
|
|
||||||
"./lib/storage/functions": {
|
"./lib/storage/functions": {
|
||||||
"require": "./lib/storage/functions.js",
|
"require": "./lib/storage/functions.js",
|
||||||
"import": "./lib/storage/functions.mjs"
|
"import": "./lib/storage/functions.mjs"
|
||||||
@ -108,17 +100,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@antfu/utils": "^0.3.0",
|
|
||||||
"@iconify/api-redundancy": "^1.0.2",
|
"@iconify/api-redundancy": "^1.0.2",
|
||||||
"@iconify/types": "^1.0.10",
|
"@iconify/types": "^1.0.10",
|
||||||
"@iconify/utils": "^1.0.16",
|
"@iconify/utils": "^1.0.16",
|
||||||
"cross-fetch": "^3.1.4",
|
"cross-fetch": "^3.1.4"
|
||||||
"debug": "^4.3.3",
|
|
||||||
"local-pkg": "^0.4.0"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@iconify/library-builder": "^1.0.3",
|
"@iconify/library-builder": "^1.0.3",
|
||||||
"@types/debug": "^4.1.7",
|
|
||||||
"@types/jest": "^27.0.2",
|
"@types/jest": "^27.0.2",
|
||||||
"@types/node": "^15.3.0",
|
"@types/node": "^15.3.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^4.31.1",
|
"@typescript-eslint/eslint-plugin": "^4.31.1",
|
||||||
|
19
packages/utils/package-lock.json
generated
19
packages/utils/package-lock.json
generated
@ -13,7 +13,8 @@
|
|||||||
"@antfu/utils": "^0.3.0",
|
"@antfu/utils": "^0.3.0",
|
||||||
"@iconify/types": "^1.0.12",
|
"@iconify/types": "^1.0.12",
|
||||||
"debug": "^4.3.3",
|
"debug": "^4.3.3",
|
||||||
"kolorist": "^1.5.0"
|
"kolorist": "^1.5.0",
|
||||||
|
"local-pkg": "^0.4.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@iconify/library-builder": "^1.0.4",
|
"@iconify/library-builder": "^1.0.4",
|
||||||
@ -3972,6 +3973,17 @@
|
|||||||
"node": ">= 0.8.0"
|
"node": ">= 0.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/local-pkg": {
|
||||||
|
"version": "0.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.4.1.tgz",
|
||||||
|
"integrity": "sha512-lL87ytIGP2FU5PWwNDo0w3WhIo2gopIAxPg9RxDYF7m4rr5ahuZxP22xnJHIvaLTe4Z9P6uKKY2UHiwyB4pcrw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/antfu"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/locate-path": {
|
"node_modules/locate-path": {
|
||||||
"version": "6.0.0",
|
"version": "6.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
|
||||||
@ -8412,6 +8424,11 @@
|
|||||||
"type-check": "~0.4.0"
|
"type-check": "~0.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"local-pkg": {
|
||||||
|
"version": "0.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.4.1.tgz",
|
||||||
|
"integrity": "sha512-lL87ytIGP2FU5PWwNDo0w3WhIo2gopIAxPg9RxDYF7m4rr5ahuZxP22xnJHIvaLTe4Z9P6uKKY2UHiwyB4pcrw=="
|
||||||
|
},
|
||||||
"locate-path": {
|
"locate-path": {
|
||||||
"version": "6.0.0",
|
"version": "6.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
|
||||||
|
@ -128,6 +128,10 @@
|
|||||||
"require": "./lib/loader/loaders.js",
|
"require": "./lib/loader/loaders.js",
|
||||||
"import": "./lib/loader/loaders.mjs"
|
"import": "./lib/loader/loaders.mjs"
|
||||||
},
|
},
|
||||||
|
"./lib/loader/modern": {
|
||||||
|
"require": "./lib/loader/modern.js",
|
||||||
|
"import": "./lib/loader/modern.mjs"
|
||||||
|
},
|
||||||
"./lib/loader/types": {
|
"./lib/loader/types": {
|
||||||
"require": "./lib/loader/types.js",
|
"require": "./lib/loader/types.js",
|
||||||
"import": "./lib/loader/types.mjs"
|
"import": "./lib/loader/types.mjs"
|
||||||
@ -158,7 +162,8 @@
|
|||||||
"@antfu/utils": "^0.3.0",
|
"@antfu/utils": "^0.3.0",
|
||||||
"@iconify/types": "^1.0.12",
|
"@iconify/types": "^1.0.12",
|
||||||
"debug": "^4.3.3",
|
"debug": "^4.3.3",
|
||||||
"kolorist": "^1.5.0"
|
"kolorist": "^1.5.0",
|
||||||
|
"local-pkg": "^0.4.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@iconify/library-builder": "^1.0.4",
|
"@iconify/library-builder": "^1.0.4",
|
||||||
|
@ -48,11 +48,14 @@ export { stringToColor, compareColors, colorToString } from './colors/index';
|
|||||||
export type {
|
export type {
|
||||||
CustomIconLoader,
|
CustomIconLoader,
|
||||||
CustomCollections,
|
CustomCollections,
|
||||||
|
IconCustomizer,
|
||||||
|
IconCustomizations,
|
||||||
InlineCollection,
|
InlineCollection,
|
||||||
} from './loader/types';
|
} from './loader/types';
|
||||||
export { tryInstallPkg } from './loader/utils';
|
export { tryInstallPkg, mergeIconProps } from './loader/utils';
|
||||||
export { FileSystemIconLoader } from './loader/loaders';
|
export { FileSystemIconLoader } from './loader/loaders';
|
||||||
export { getCustomIcon } from './loader/custom';
|
export { getCustomIcon } from './loader/custom';
|
||||||
|
export { loadCollection, searchForIcon } from './loader/modern';
|
||||||
|
|
||||||
// Misc
|
// Misc
|
||||||
export { camelize, camelToKebab, pascalize } from './misc/strings';
|
export { camelize, camelToKebab, pascalize } from './misc/strings';
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import type { Awaitable } from '@antfu/utils';
|
|
||||||
import createDebugger from 'debug';
|
import createDebugger from 'debug';
|
||||||
import type { CustomIconLoader, InlineCollection } from './types';
|
import type { CustomIconLoader, IconCustomizations, InlineCollection } from './types';
|
||||||
|
import { mergeIconProps } from './utils';
|
||||||
|
|
||||||
const debug = createDebugger('@iconify-loader:custom');
|
const debug = createDebugger('@iconify-loader:custom');
|
||||||
|
|
||||||
@ -11,7 +11,7 @@ export async function getCustomIcon(
|
|||||||
custom: CustomIconLoader | InlineCollection,
|
custom: CustomIconLoader | InlineCollection,
|
||||||
collection: string,
|
collection: string,
|
||||||
icon: string,
|
icon: string,
|
||||||
transform?: (svg: string) => Awaitable<string>
|
iconsCustomizations?: IconCustomizations,
|
||||||
): Promise<string | undefined> {
|
): Promise<string | undefined> {
|
||||||
let result: string | undefined | null;
|
let result: string | undefined | null;
|
||||||
|
|
||||||
@ -26,10 +26,18 @@ export async function getCustomIcon(
|
|||||||
|
|
||||||
if (result) {
|
if (result) {
|
||||||
if (!result.startsWith('<svg ')) {
|
if (!result.startsWith('<svg ')) {
|
||||||
console.warn(
|
console.warn(`Custom icon "${icon}" in "${collection}" is not a valid SVG`)
|
||||||
`Custom icon "${icon}" in "${collection}" is not a valid SVG`
|
return result
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return transform ? await transform(result) : result;
|
const { transform, additionalProps = {}, iconCustomizer } = iconsCustomizations || {}
|
||||||
|
return await mergeIconProps(
|
||||||
|
transform ? await transform(result) : result,
|
||||||
|
collection,
|
||||||
|
icon,
|
||||||
|
additionalProps,
|
||||||
|
undefined,
|
||||||
|
iconCustomizer
|
||||||
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,8 +16,8 @@ export function FileSystemIconLoader(
|
|||||||
`${dir}/${camelize(name)}.svg`,
|
`${dir}/${camelize(name)}.svg`,
|
||||||
`${dir}/${pascalize(name)}.svg`,
|
`${dir}/${pascalize(name)}.svg`,
|
||||||
];
|
];
|
||||||
|
let stat: Stats;
|
||||||
for (const path of paths) {
|
for (const path of paths) {
|
||||||
let stat: Stats;
|
|
||||||
try {
|
try {
|
||||||
stat = await fs.lstat(path);
|
stat = await fs.lstat(path);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
@ -1,14 +1,17 @@
|
|||||||
import { promises as fs } from 'fs';
|
import { promises as fs } from 'fs';
|
||||||
import type { IconifyJSON } from '@iconify/types';
|
import type { IconifyJSON } from '@iconify/types';
|
||||||
import type { FullIconifyIcon } from '@iconify/utils/lib/icon';
|
import type { FullIconifyIcon } from '../icon';
|
||||||
import { defaultCustomisations as DefaultIconCustomizations, iconToSVG, getIconData, tryInstallPkg } from '@iconify/utils';
|
import { iconToSVG } from '../svg/build';
|
||||||
|
import { getIconData } from '../icon-set/get-icon';
|
||||||
|
import { mergeIconProps, tryInstallPkg } from './utils';
|
||||||
import createDebugger from 'debug';
|
import createDebugger from 'debug';
|
||||||
import { isPackageExists, resolveModule } from 'local-pkg';
|
import { isPackageExists, resolveModule } from 'local-pkg';
|
||||||
import type { FullIconCustomisations } from '@iconify/utils/lib/customisations';
|
import { defaults as DefaultIconCustomizations } from '../customisations';
|
||||||
|
import type { IconCustomizations } from './types';
|
||||||
|
|
||||||
const debug = createDebugger('@iconify-core:icon');
|
const debug = createDebugger('@iconify-loader:icon');
|
||||||
const debugModern = createDebugger('@iconify-core:modern');
|
const debugModern = createDebugger('@iconify-loader:modern');
|
||||||
const debugLegacy = createDebugger('@iconify-core:legacy');
|
const debugLegacy = createDebugger('@iconify-loader:legacy');
|
||||||
|
|
||||||
const _collections: Record<string, Promise<IconifyJSON | undefined>> = {};
|
const _collections: Record<string, Promise<IconifyJSON | undefined>> = {};
|
||||||
const isLegacyExists = isPackageExists('@iconify/json');
|
const isLegacyExists = isPackageExists('@iconify/json');
|
||||||
@ -48,13 +51,14 @@ export async function loadCollection(name: string, autoInstall = false): Promise
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function searchForIcon(
|
export async function searchForIcon(
|
||||||
iconSet: IconifyJSON,
|
iconSet: IconifyJSON,
|
||||||
collection: string,
|
collection: string,
|
||||||
ids: string[],
|
ids: string[],
|
||||||
customize?: (defaultCustomizations: FullIconCustomisations) => FullIconCustomisations
|
iconCustomizations?: IconCustomizations,
|
||||||
): string | null {
|
): Promise<string | undefined> {
|
||||||
let iconData: FullIconifyIcon | null;
|
let iconData: FullIconifyIcon | null;
|
||||||
|
const { customize, additionalProps = {}, iconCustomizer } = iconCustomizations || {}
|
||||||
for (const id of ids) {
|
for (const id of ids) {
|
||||||
iconData = getIconData(iconSet, id, true);
|
iconData = getIconData(iconSet, id, true);
|
||||||
if (iconData) {
|
if (iconData) {
|
||||||
@ -64,8 +68,14 @@ export function searchForIcon(
|
|||||||
iconData,
|
iconData,
|
||||||
typeof customize === 'function' ? customize(defaultCustomizations) : defaultCustomizations
|
typeof customize === 'function' ? customize(defaultCustomizations) : defaultCustomizations
|
||||||
);
|
);
|
||||||
return `<svg ${Object.entries(attributes).map(i => `${i[0]}="${i[1]}"`).join(' ')}>${body}</svg>`;
|
return await mergeIconProps(
|
||||||
|
`<svg>${body}</svg>`,
|
||||||
|
collection,
|
||||||
|
id,
|
||||||
|
additionalProps,
|
||||||
|
() => attributes,
|
||||||
|
iconCustomizer,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
}
|
}
|
@ -1,10 +1,54 @@
|
|||||||
import type { Awaitable } from '@antfu/utils';
|
import type { Awaitable } from '@antfu/utils';
|
||||||
|
import type { FullIconCustomisations } from '../customisations';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom icon loader, used by getCustomIcon()
|
* Custom icon loader, used by `getCustomIcon`.
|
||||||
*/
|
*/
|
||||||
export type CustomIconLoader = (name: string) => Awaitable<string | undefined>;
|
export type CustomIconLoader = (name: string) => Awaitable<string | undefined>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom icon customizer, it will allow to customize all icons on a collection or individual icons.
|
||||||
|
*/
|
||||||
|
export type IconCustomizer = (collection: string, icon: string, props: Record<string, string>) => Awaitable<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Icon customizations: will be applied to all resolved icons.
|
||||||
|
*
|
||||||
|
* For each loaded icon, the customizations will be applied in this order:
|
||||||
|
* - apply `transform` to raw `svg`, if provided and using custom icon collection
|
||||||
|
* - apply `customize` with default customizations, if provided
|
||||||
|
* - apply `iconCustomizer` with `customize` customizations, if provided
|
||||||
|
* - apply `additionalProps` with `iconCustomizer` customizations, if provided
|
||||||
|
*/
|
||||||
|
export type IconCustomizations = {
|
||||||
|
/**
|
||||||
|
* Transform raw `svg`.
|
||||||
|
*
|
||||||
|
* **WARNING**: `transform` will be only applied when using `custom` icon collection: it will be applied only when using `getCustomIcon` and excluded when using `searchForIcon`.
|
||||||
|
*
|
||||||
|
* @param svg The loaded `svg`
|
||||||
|
* @return The transformed `svg`.
|
||||||
|
*/
|
||||||
|
transform?: (svg: string) => Awaitable<string>
|
||||||
|
/**
|
||||||
|
* Change default icon customizations values.
|
||||||
|
*
|
||||||
|
* @param defaultCustomizations Default icon's customizations values.
|
||||||
|
* @return The modified icon's customizations values.
|
||||||
|
*/
|
||||||
|
customize?: (defaultCustomizations: FullIconCustomisations) => FullIconCustomisations
|
||||||
|
/**
|
||||||
|
* Custom icon customizer.
|
||||||
|
*/
|
||||||
|
iconCustomizer?: IconCustomizer
|
||||||
|
/**
|
||||||
|
* Additional icon properties.
|
||||||
|
*
|
||||||
|
* All properties without value will not be applied.
|
||||||
|
*/
|
||||||
|
additionalProps?: Record<string, string | undefined>
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List of icons as object. Key is icon name, value is icon data or callback (can be async) to get icon data
|
* List of icons as object. Key is icon name, value is icon data or callback (can be async) to get icon data
|
||||||
*/
|
*/
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { installPackage } from '@antfu/install-pkg';
|
import { installPackage } from '@antfu/install-pkg';
|
||||||
import { sleep } from '@antfu/utils';
|
import { Awaitable, sleep } from '@antfu/utils';
|
||||||
import { cyan, yellow } from 'kolorist';
|
import { cyan, yellow } from 'kolorist';
|
||||||
|
import type { IconCustomizer } from './types';
|
||||||
|
|
||||||
const warned = new Set<string>();
|
const warned = new Set<string>();
|
||||||
|
|
||||||
@ -14,6 +15,25 @@ export function warnOnce(msg: string): void {
|
|||||||
let pending: Promise<void> | undefined;
|
let pending: Promise<void> | undefined;
|
||||||
const tasks: Record<string, Promise<void> | undefined> = {};
|
const tasks: Record<string, Promise<void> | undefined> = {};
|
||||||
|
|
||||||
|
export async function mergeIconProps(
|
||||||
|
svg: string,
|
||||||
|
collection: string,
|
||||||
|
icon: string,
|
||||||
|
additionalProps: Record<string, string | undefined>,
|
||||||
|
propsProvider?: () => Awaitable<Record<string, string>>,
|
||||||
|
iconCustomizer?: IconCustomizer,
|
||||||
|
): Promise<string> {
|
||||||
|
const props: Record<string, string> = await propsProvider?.() ?? {}
|
||||||
|
await iconCustomizer?.(collection, icon, props)
|
||||||
|
Object.keys(additionalProps).forEach((p) => {
|
||||||
|
const v = additionalProps[p]
|
||||||
|
if (v !== undefined && v !== null)
|
||||||
|
props[p] = v
|
||||||
|
})
|
||||||
|
const replacement = svg.startsWith('<svg ') ? '<svg ' : '<svg'
|
||||||
|
return svg.replace(replacement, `${replacement}${Object.keys(props).map(p => `${p}="${props[p]}"`).join(' ')}`)
|
||||||
|
}
|
||||||
|
|
||||||
export async function tryInstallPkg(name: string): Promise<void | undefined> {
|
export async function tryInstallPkg(name: string): Promise<void | undefined> {
|
||||||
if (pending) {
|
if (pending) {
|
||||||
await pending;
|
await pending;
|
||||||
|
@ -16,9 +16,12 @@ describe('Testing getCustomIcon', () => {
|
|||||||
() => svg,
|
() => svg,
|
||||||
'a',
|
'a',
|
||||||
'b',
|
'b',
|
||||||
(icon) => {
|
{
|
||||||
return icon.replace('<svg ', '<svg width="1em" height="1em" ');
|
transform(icon) {
|
||||||
|
return icon.replace('<svg ', '<svg width="1em" height="1em" ');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
);
|
);
|
||||||
expect(result && result.indexOf('width="1em"') > -1).toBeTruthy();
|
expect(result && result.indexOf('width="1em"') > -1).toBeTruthy();
|
||||||
expect(result && result.indexOf('height="1em"') > -1).toBeTruthy();
|
expect(result && result.indexOf('height="1em"') > -1).toBeTruthy();
|
||||||
|
Loading…
Reference in New Issue
Block a user