mirror of
https://github.com/iconify/iconify.git
synced 2024-11-09 23:00:56 +00:00
feat: allow external collections packages
This commit is contained in:
parent
0d3e578b45
commit
a14a3c4b0e
@ -277,6 +277,11 @@
|
||||
"require": "./lib/loader/custom.cjs",
|
||||
"import": "./lib/loader/custom.mjs"
|
||||
},
|
||||
"./lib/loader/external-pkg": {
|
||||
"types": "./lib/loader/external-pkg.d.ts",
|
||||
"require": "./lib/loader/external-pkg.cjs",
|
||||
"import": "./lib/loader/external-pkg.mjs"
|
||||
},
|
||||
"./lib/loader/fs": {
|
||||
"types": "./lib/loader/fs.d.ts",
|
||||
"require": "./lib/loader/fs.cjs",
|
||||
@ -412,6 +417,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@iconify-json/flat-color-icons": "^1.1.6",
|
||||
"@test-scope/test-color-icons": "file:./tests/fixtures/@test-scope/test-color-icons",
|
||||
"@types/debug": "^4.1.8",
|
||||
"@types/jest": "^29.5.3",
|
||||
"@types/node": "^18.17.1",
|
||||
@ -419,6 +425,7 @@
|
||||
"eslint": "^8.46.0",
|
||||
"eslint-config-prettier": "^8.9.0",
|
||||
"eslint-plugin-prettier": "^5.0.0",
|
||||
"plain-color-icons": "file:./tests/fixtures/plain-color-icons",
|
||||
"rimraf": "^5.0.1",
|
||||
"typescript": "^5.1.6",
|
||||
"unbuild": "^1.2.1",
|
||||
|
@ -79,6 +79,7 @@ export { getIconsCSS, getIconsContentCSS } from './css/icons';
|
||||
export type {
|
||||
CustomIconLoader,
|
||||
CustomCollections,
|
||||
ExternalPkgInfo,
|
||||
IconCustomizer,
|
||||
IconCustomizations,
|
||||
IconifyLoaderOptions,
|
||||
|
71
packages/utils/src/loader/external-pkg.ts
Normal file
71
packages/utils/src/loader/external-pkg.ts
Normal file
@ -0,0 +1,71 @@
|
||||
import type { AutoInstall, CustomIconLoader, ExternalPkgInfo } from './types';
|
||||
import { loadCollectionFromFS } from './fs';
|
||||
import { searchForIcon } from './modern';
|
||||
import { warnOnce } from './warn';
|
||||
|
||||
/**
|
||||
* Creates a CustomIconLoader collection from an external scoped package collection.
|
||||
*
|
||||
* @param packageName The scoped package name.
|
||||
* @param autoInstall {AutoInstall} [autoInstall=false] - whether to automatically install
|
||||
*/
|
||||
export function createExternalPackageIconLoader(
|
||||
packageName: ExternalPkgInfo,
|
||||
autoInstall: AutoInstall = false
|
||||
) {
|
||||
let scope: string;
|
||||
let collection: string;
|
||||
const collections: Record<string, CustomIconLoader> = {};
|
||||
if (typeof packageName === 'string') {
|
||||
if (packageName.length === 0) {
|
||||
warnOnce(`invalid package name, it is empty`);
|
||||
return collections;
|
||||
}
|
||||
if (packageName[0] === '@') {
|
||||
if (packageName.indexOf('/') === -1) {
|
||||
warnOnce(`invalid scoped package name "${packageName}"`);
|
||||
return collections;
|
||||
}
|
||||
[scope, collection] = packageName.split('/');
|
||||
} else {
|
||||
scope = '';
|
||||
collection = packageName;
|
||||
}
|
||||
} else {
|
||||
[scope, collection] = packageName;
|
||||
}
|
||||
|
||||
collections[collection] = createCustomIconLoader(
|
||||
scope,
|
||||
collection,
|
||||
autoInstall
|
||||
);
|
||||
|
||||
return collections;
|
||||
}
|
||||
|
||||
function createCustomIconLoader(
|
||||
scope: string,
|
||||
collection: string,
|
||||
autoInstall: AutoInstall
|
||||
) {
|
||||
// create the custom collection loader
|
||||
const iconSetPromise = loadCollectionFromFS(collection, autoInstall, scope);
|
||||
return <CustomIconLoader>(async (icon) => {
|
||||
// await until the collection is loaded
|
||||
const iconSet = await iconSetPromise;
|
||||
// copy/paste from ./node-loader.ts
|
||||
let result: string | undefined;
|
||||
if (iconSet) {
|
||||
// possible icon names
|
||||
const ids = [
|
||||
icon,
|
||||
icon.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(),
|
||||
icon.replace(/([a-z])(\d+)/g, '$1-$2'),
|
||||
];
|
||||
result = await searchForIcon(iconSet, collection, ids);
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
import { promises as fs, Stats } from 'fs';
|
||||
import { isPackageExists, resolveModule } from 'local-pkg';
|
||||
import { isPackageExists, resolveModule, importModule } from 'local-pkg';
|
||||
import type { IconifyJSON } from '@iconify/types';
|
||||
import { tryInstallPkg } from './install-pkg';
|
||||
import type { AutoInstall } from './types';
|
||||
@ -7,9 +7,18 @@ import type { AutoInstall } from './types';
|
||||
const _collections: Record<string, Promise<IconifyJSON | undefined>> = {};
|
||||
const isLegacyExists = isPackageExists('@iconify/json');
|
||||
|
||||
/**
|
||||
* Asynchronously loads a collection from the file system.
|
||||
*
|
||||
* @param name {string} the name of the collection, e.g. 'mdi'
|
||||
* @param autoInstall {AutoInstall} [autoInstall=false] - whether to automatically install
|
||||
* @param scope {string} [scope='@iconify-json'] - the scope of the collection, e.g. '@my-company-json'
|
||||
* @return {Promise<IconifyJSON | undefined>} the loaded IconifyJSON or undefined
|
||||
*/
|
||||
export async function loadCollectionFromFS(
|
||||
name: string,
|
||||
autoInstall: AutoInstall = false
|
||||
autoInstall: AutoInstall = false,
|
||||
scope = '@iconify-json'
|
||||
): Promise<IconifyJSON | undefined> {
|
||||
if (!(await _collections[name])) {
|
||||
_collections[name] = task();
|
||||
@ -17,16 +26,40 @@ export async function loadCollectionFromFS(
|
||||
return _collections[name];
|
||||
|
||||
async function task() {
|
||||
let jsonPath = resolveModule(`@iconify-json/${name}/icons.json`);
|
||||
const packageName = scope.length === 0 ? name : `${scope}/${name}`;
|
||||
let jsonPath = resolveModule(`${packageName}/icons.json`);
|
||||
|
||||
// Legacy support for @iconify/json
|
||||
if (scope === '@iconify-json') {
|
||||
if (!jsonPath && isLegacyExists) {
|
||||
jsonPath = resolveModule(`@iconify/json/json/${name}.json`);
|
||||
}
|
||||
|
||||
// Try to install the package if it doesn't exist
|
||||
if (!jsonPath && !isLegacyExists && autoInstall) {
|
||||
await tryInstallPkg(`@iconify-json/${name}`, autoInstall);
|
||||
jsonPath = resolveModule(`@iconify-json/${name}/icons.json`);
|
||||
await tryInstallPkg(packageName, autoInstall);
|
||||
jsonPath = resolveModule(`${packageName}/icons.json`);
|
||||
}
|
||||
} else if (!jsonPath && autoInstall) {
|
||||
await tryInstallPkg(packageName, autoInstall);
|
||||
jsonPath = resolveModule(`${packageName}/icons.json`);
|
||||
}
|
||||
|
||||
// Try to import module if it exists
|
||||
if (!jsonPath) {
|
||||
let packagePath = resolveModule(packageName);
|
||||
if (packagePath?.match(/^[a-z]:/i)) {
|
||||
packagePath = `file:///${packagePath}`.replace(/\\/g, '/');
|
||||
}
|
||||
if (packagePath) {
|
||||
const { icons }: { icons?: IconifyJSON } = await importModule(
|
||||
packagePath
|
||||
);
|
||||
if (icons) return icons;
|
||||
}
|
||||
}
|
||||
|
||||
// Load from file
|
||||
let stat: Stats | undefined;
|
||||
try {
|
||||
stat = jsonPath ? await fs.lstat(jsonPath) : undefined;
|
||||
|
@ -2,6 +2,11 @@ import type { Awaitable } from '@antfu/utils';
|
||||
import type { FullIconCustomisations } from '../customisations/defaults';
|
||||
import type { IconifyJSON } from '@iconify/types';
|
||||
|
||||
/**
|
||||
* The external scoped package name: e.g. @my-collections/collection-a.
|
||||
*/
|
||||
export type ExternalPkgInfo = string | [name: string, collection: string];
|
||||
|
||||
/**
|
||||
* Type for universal icon loader.
|
||||
*/
|
||||
|
21
packages/utils/tests/external-pkg-test.ts
Normal file
21
packages/utils/tests/external-pkg-test.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { describe } from 'vitest';
|
||||
import { loadNodeIcon } from '../lib/loader/node-loader';
|
||||
import { createExternalPackageIconLoader } from '../lib/loader/external-pkg';
|
||||
|
||||
describe('external-pkg', () => {
|
||||
test('loadNodeIcon works with importModule and plain package name', async () => {
|
||||
const result = await loadNodeIcon('plain-color-icons', 'about', {
|
||||
customCollections:
|
||||
createExternalPackageIconLoader('plain-color-icons'),
|
||||
});
|
||||
expect(result).toBeTruthy();
|
||||
});
|
||||
test('loadNodeIcon works with importModule and scoped package name', async () => {
|
||||
const result = await loadNodeIcon('test-color-icons', 'about', {
|
||||
customCollections: createExternalPackageIconLoader(
|
||||
'@test-scope/test-color-icons'
|
||||
),
|
||||
});
|
||||
expect(result).toBeTruthy();
|
||||
});
|
||||
});
|
13
packages/utils/tests/fixtures/@test-scope/test-color-icons/index.d.ts
vendored
Normal file
13
packages/utils/tests/fixtures/@test-scope/test-color-icons/index.d.ts
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
import type {
|
||||
IconifyJSON,
|
||||
IconifyInfo,
|
||||
IconifyMetaData,
|
||||
IconifyChars,
|
||||
} from '@iconify/types';
|
||||
|
||||
export { IconifyJSON, IconifyInfo, IconifyMetaData, IconifyChars };
|
||||
|
||||
export declare const icons: IconifyJSON;
|
||||
export declare const info: IconifyInfo;
|
||||
export declare const metadata: IconifyMetaData;
|
||||
export declare const chars: IconifyChars;
|
28
packages/utils/tests/fixtures/@test-scope/test-color-icons/index.js
vendored
Normal file
28
packages/utils/tests/fixtures/@test-scope/test-color-icons/index.js
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
/* eslint-disable prettier/prettier */
|
||||
exports.icons = {
|
||||
prefix: 'test-color-icons',
|
||||
icons: {
|
||||
about: {
|
||||
body: '<path fill="#2196F3" d="M37 40H11l-6 6V12c0-3.3 2.7-6 6-6h26c3.3 0 6 2.7 6 6v22c0 3.3-2.7 6-6 6z"/><g fill="#fff"><path d="M22 20h4v11h-4z"/><circle cx="24" cy="15" r="2"/></g>',
|
||||
},
|
||||
},
|
||||
lastModified: 1672652184,
|
||||
width: 48,
|
||||
height: 48,
|
||||
};
|
||||
exports.info = {
|
||||
prefix: 'test-color-icons',
|
||||
name: 'Test Color Icons',
|
||||
total: 1,
|
||||
author: {
|
||||
name: 'test',
|
||||
},
|
||||
license: {
|
||||
title: 'MIT',
|
||||
},
|
||||
samples: ['about'],
|
||||
height: 32,
|
||||
displayHeight: 16,
|
||||
};
|
||||
exports.metadata = {};
|
||||
exports.chars = {};
|
31
packages/utils/tests/fixtures/@test-scope/test-color-icons/index.mjs
vendored
Normal file
31
packages/utils/tests/fixtures/@test-scope/test-color-icons/index.mjs
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
/* eslint-disable prettier/prettier */
|
||||
const icons = {
|
||||
prefix: 'test-color-icons',
|
||||
icons: {
|
||||
about: {
|
||||
body: '<path fill="#2196F3" d="M37 40H11l-6 6V12c0-3.3 2.7-6 6-6h26c3.3 0 6 2.7 6 6v22c0 3.3-2.7 6-6 6z"/><g fill="#fff"><path d="M22 20h4v11h-4z"/><circle cx="24" cy="15" r="2"/></g>'
|
||||
}
|
||||
},
|
||||
lastModified: 1672652184,
|
||||
width: 48,
|
||||
height: 48
|
||||
}
|
||||
|
||||
const info = {
|
||||
prefix: 'test-color-icons',
|
||||
name: 'Test Color Icons',
|
||||
total: 1,
|
||||
author: {
|
||||
name: 'test'
|
||||
},
|
||||
license: {
|
||||
title: 'MIT'
|
||||
},
|
||||
samples: ['about'],
|
||||
height: 32,
|
||||
displayHeight: 16
|
||||
}
|
||||
|
||||
const metadata = {}
|
||||
const chars = {}
|
||||
export { icons, info, metadata, chars }
|
13
packages/utils/tests/fixtures/@test-scope/test-color-icons/package.json
vendored
Normal file
13
packages/utils/tests/fixtures/@test-scope/test-color-icons/package.json
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "@test-scope/test-color-icons",
|
||||
"main": "index.js",
|
||||
"module": "index.mjs",
|
||||
"types": "index.d.ts",
|
||||
"exports": {
|
||||
"./*": "./*",
|
||||
".": {
|
||||
"require": "./index.js",
|
||||
"import": "./index.mjs"
|
||||
}
|
||||
}
|
||||
}
|
13
packages/utils/tests/fixtures/plain-color-icons/index.d.ts
vendored
Normal file
13
packages/utils/tests/fixtures/plain-color-icons/index.d.ts
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
import type {
|
||||
IconifyJSON,
|
||||
IconifyInfo,
|
||||
IconifyMetaData,
|
||||
IconifyChars,
|
||||
} from '@iconify/types';
|
||||
|
||||
export { IconifyJSON, IconifyInfo, IconifyMetaData, IconifyChars };
|
||||
|
||||
export declare const icons: IconifyJSON;
|
||||
export declare const info: IconifyInfo;
|
||||
export declare const metadata: IconifyMetaData;
|
||||
export declare const chars: IconifyChars;
|
28
packages/utils/tests/fixtures/plain-color-icons/index.js
vendored
Normal file
28
packages/utils/tests/fixtures/plain-color-icons/index.js
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
/* eslint-disable prettier/prettier */
|
||||
exports.icons = {
|
||||
prefix: 'test-color-icons',
|
||||
icons: {
|
||||
about: {
|
||||
body: '<path fill="#2196F3" d="M37 40H11l-6 6V12c0-3.3 2.7-6 6-6h26c3.3 0 6 2.7 6 6v22c0 3.3-2.7 6-6 6z"/><g fill="#fff"><path d="M22 20h4v11h-4z"/><circle cx="24" cy="15" r="2"/></g>',
|
||||
},
|
||||
},
|
||||
lastModified: 1672652184,
|
||||
width: 48,
|
||||
height: 48,
|
||||
};
|
||||
exports.info = {
|
||||
prefix: 'test-color-icons',
|
||||
name: 'Test Color Icons',
|
||||
total: 1,
|
||||
author: {
|
||||
name: 'test',
|
||||
},
|
||||
license: {
|
||||
title: 'MIT',
|
||||
},
|
||||
samples: ['about'],
|
||||
height: 32,
|
||||
displayHeight: 16,
|
||||
};
|
||||
exports.metadata = {};
|
||||
exports.chars = {};
|
31
packages/utils/tests/fixtures/plain-color-icons/index.mjs
vendored
Normal file
31
packages/utils/tests/fixtures/plain-color-icons/index.mjs
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
/* eslint-disable prettier/prettier */
|
||||
const icons = {
|
||||
prefix: 'test-color-icons',
|
||||
icons: {
|
||||
about: {
|
||||
body: '<path fill="#2196F3" d="M37 40H11l-6 6V12c0-3.3 2.7-6 6-6h26c3.3 0 6 2.7 6 6v22c0 3.3-2.7 6-6 6z"/><g fill="#fff"><path d="M22 20h4v11h-4z"/><circle cx="24" cy="15" r="2"/></g>'
|
||||
}
|
||||
},
|
||||
lastModified: 1672652184,
|
||||
width: 48,
|
||||
height: 48
|
||||
}
|
||||
|
||||
const info = {
|
||||
prefix: 'test-color-icons',
|
||||
name: 'Test Color Icons',
|
||||
total: 1,
|
||||
author: {
|
||||
name: 'test'
|
||||
},
|
||||
license: {
|
||||
title: 'MIT'
|
||||
},
|
||||
samples: ['about'],
|
||||
height: 32,
|
||||
displayHeight: 16
|
||||
}
|
||||
|
||||
const metadata = {}
|
||||
const chars = {}
|
||||
export { icons, info, metadata, chars }
|
13
packages/utils/tests/fixtures/plain-color-icons/package.json
vendored
Normal file
13
packages/utils/tests/fixtures/plain-color-icons/package.json
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "plain-color-icons",
|
||||
"main": "index.js",
|
||||
"module": "index.mjs",
|
||||
"types": "index.d.ts",
|
||||
"exports": {
|
||||
"./*": "./*",
|
||||
".": {
|
||||
"require": "./index.js",
|
||||
"import": "./index.mjs"
|
||||
}
|
||||
}
|
||||
}
|
2162
pnpm-lock.yaml
2162
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user