From dbe23c60259cf21beaaca3e1f3ba6950d7e704ba Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Joaqu=C3=ADn=20S=C3=A1nchez?=
Date: Fri, 26 Jan 2024 22:20:31 +0100
Subject: [PATCH] feat: support for external collection packages (#342)
* feat: custom external collection packages
* chore: update readme section
* chore: include an example in vite-vue3
* chore: include using resolvers fro auto-import
* chore: update readme
---
README.md | 44 ++++++++++
examples/vite-vue3/App.vue | 18 ++++
examples/vite-vue3/components.d.ts | 2 +
examples/vite-vue3/vite.config.ts | 28 ++++++-
package.json | 2 +-
pnpm-lock.yaml | 127 ++---------------------------
src/loaders.ts | 6 ++
7 files changed, 104 insertions(+), 123 deletions(-)
diff --git a/README.md b/README.md
index d8dc2a2f..380f6803 100644
--- a/README.md
+++ b/README.md
@@ -735,6 +735,50 @@ IconResolver({
See the [Vue 3 + Vite example](./examples/vite-vue3/vite.config.ts).
+### Use Custom External Collection Packages
+
+From version `v0.18.3` you can use other packages to load icons from others authors.
+
+**WARNING**: external packages must include `icons.json` file with the `icons` data in `IconifyJSON` format, which can be exported with Iconify Tools. Check [Exporting icon set as JSON package](https://iconify.design/docs/libraries/tools/export/json-package.html) for more details.
+
+For example, you can use `an-awesome-collection` or `@my-awesome-collections/some-collection` to load your custom or third party icons:
+```ts
+// loader helpers
+import { ExternalPackageIconLoader } from 'unplugin-icons/loaders'
+
+Icons({ customCollections: ExternalPackageIconLoader('my-awesome-collection') })
+```
+
+When using with resolvers for auto-importing, remember you will need to tell it your custom collection names:
+```ts
+IconResolver({
+ customCollections: [
+ 'my-awesome-collection',
+ ],
+})
+```
+
+
+You can also combine it with `FileSystemIconLoader` or with other custom icon loaders:
+```ts
+// loader helpers
+import { ExternalPackageIconLoader, FileSystemIconLoader } from 'unplugin-icons/loaders'
+
+Icons({
+ customCollections: {
+ ...ExternalPackageIconLoader('an-awesome-collection'),
+ ...ExternalPackageIconLoader('@my-awesome-collections/some-collection'),
+ ...ExternalPackageIconLoader('@my-awesome-collections/some-other-collection'),
+ 'my-yet-other-icons': FileSystemIconLoader(
+ './assets/icons',
+ svg => svg.replace(/^
@@ -27,6 +30,9 @@ import RawMdiAlarmOff3 from 'virtual:icons/mdi/alarm-off?raw&width=unset&height=
+
+
+
Customizer via Config
@@ -80,6 +86,18 @@ import RawMdiAlarmOff3 from 'virtual:icons/mdi/alarm-off?raw&width=unset&height=
'virtual:icons/mdi/alarm-off?raw&width=unset&height=unset'
{{ RawMdiAlarmOff3 }}
+
+
+
import Custom1 from
+ 'virtual:icons/plain-color-icons/about?raw'
+
{{ Custom1 }}
+
+
+
+
import Custom2 from
+ 'virtual:icons/test-color-icons/about?raw'
+
{{ Custom2 }}
+
diff --git a/examples/vite-vue3/components.d.ts b/examples/vite-vue3/components.d.ts
index f1120bd8..012f342e 100644
--- a/examples/vite-vue3/components.d.ts
+++ b/examples/vite-vue3/components.d.ts
@@ -26,7 +26,9 @@ declare module 'vue' {
IMdiLightAlarm: typeof import('~icons/mdi-light/alarm')['default']
INotoV1FlagForFlagJapan: typeof import('~icons/noto-v1/flag-for-flag-japan')['default']
IParkAbnormal: typeof import('~icons/icon-park/abnormal')['default']
+ 'IPlainColorIcons:about': typeof import('~icons/plain-color-icons/about')['default']
IRiApps2Line: typeof import('~icons/ri/apps2-line')['default']
+ 'ITestColorIcons:about': typeof import('~icons/test-color-icons/about')['default']
ITwemoji1stPlaceMedal: typeof import('~icons/twemoji/1st-place-medal')['default']
}
}
diff --git a/examples/vite-vue3/vite.config.ts b/examples/vite-vue3/vite.config.ts
index 16437e9f..7a45a49e 100644
--- a/examples/vite-vue3/vite.config.ts
+++ b/examples/vite-vue3/vite.config.ts
@@ -1,17 +1,33 @@
-import { promises as fs } from 'node:fs'
+import { cpSync, promises as fs } from 'node:fs'
import type { UserConfig } from 'vite'
import Vue from '@vitejs/plugin-vue'
import Icons from 'unplugin-icons/vite'
-import { FileSystemIconLoader } from 'unplugin-icons/loaders'
+import { ExternalPackageIconLoader, FileSystemIconLoader } from 'unplugin-icons/loaders'
import IconsResolver from 'unplugin-icons/resolver'
import Components from 'unplugin-vue-components/vite'
+/************************************************************/
+// DON'T DO THIS IN YOUR CODE: IT IS FOR TESTING PURPOSES ONLY
+cpSync(
+ './plain-color-icons',
+ './node_modules/plain-color-icons',
+ { recursive: true },
+)
+cpSync(
+ './@test-scope',
+ './node_modules/@test-scope',
+ { recursive: true },
+)
+/************************************************************/
+
const config: UserConfig = {
plugins: [
Vue(),
Icons({
compiler: 'vue3',
customCollections: {
+ ...ExternalPackageIconLoader('@test-scope/test-color-icons'),
+ ...ExternalPackageIconLoader('plain-color-icons'),
custom: FileSystemIconLoader('assets/custom-a'),
inline: {
foo: `
@@ -37,7 +53,13 @@ const config: UserConfig = {
alias: {
park: 'icon-park',
},
- customCollections: ['custom', 'inline'],
+ customCollections: [
+ 'custom',
+ 'inline',
+ // custom external packages
+ 'plain-color-icons',
+ 'test-color-icons',
+ ],
}),
],
}),
diff --git a/package.json b/package.json
index 586e5a9b..c5b129e3 100644
--- a/package.json
+++ b/package.json
@@ -194,7 +194,7 @@
"dependencies": {
"@antfu/install-pkg": "^0.3.0",
"@antfu/utils": "^0.7.6",
- "@iconify/utils": "^2.1.12",
+ "@iconify/utils": "^2.1.20",
"debug": "^4.3.4",
"kolorist": "^1.8.0",
"local-pkg": "^0.5.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 5a96b8b6..99c404b1 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -15,8 +15,8 @@ importers:
specifier: ^0.7.6
version: 0.7.6
'@iconify/utils':
- specifier: ^2.1.12
- version: 2.1.12
+ specifier: ^2.1.20
+ version: 2.1.20
debug:
specifier: ^4.3.4
version: 4.3.4
@@ -127,7 +127,7 @@ importers:
version: 2.2.110
'@svgr/core':
specifier: 8.1.0
- version: 8.1.0
+ version: 8.1.0(typescript@5.2.2)
'@svgr/plugin-jsx':
specifier: ^8.1.0
version: 8.1.0(@svgr/core@8.1.0)
@@ -3281,15 +3281,15 @@ packages:
/@iconify/types@2.0.0:
resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==}
- /@iconify/utils@2.1.12:
- resolution: {integrity: sha512-7vf3Uk6H7TKX4QMs2gbg5KR1X9J0NJzKSRNWhMZ+PWN92l0t6Q3tj2ZxLDG07rC3ppWBtTtA4FPmkQphuEmdsg==}
+ /@iconify/utils@2.1.20:
+ resolution: {integrity: sha512-t8TeKlYK/5i9yTY9VAGAE4P0qQHd/0vH+VSRO+bdpxlt8wqB6f2I0/IrciRsdeFZPMoL8IICgP7lgl2ZtbG8Tw==}
dependencies:
'@antfu/install-pkg': 0.1.1
'@antfu/utils': 0.7.6
'@iconify/types': 2.0.0
debug: 4.3.4
kolorist: 1.8.0
- local-pkg: 0.4.3
+ local-pkg: 0.5.0
transitivePeerDependencies:
- supports-color
dev: false
@@ -3872,15 +3872,6 @@ packages:
- supports-color
dev: true
- /@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.22.8):
- resolution: {integrity: sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==}
- engines: {node: '>=14'}
- peerDependencies:
- '@babel/core': ^7.0.0-0
- dependencies:
- '@babel/core': 7.22.8
- dev: true
-
/@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.23.2):
resolution: {integrity: sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==}
engines: {node: '>=14'}
@@ -3890,15 +3881,6 @@ packages:
'@babel/core': 7.23.2
dev: true
- /@svgr/babel-plugin-remove-jsx-attribute@8.0.0(@babel/core@7.22.8):
- resolution: {integrity: sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==}
- engines: {node: '>=14'}
- peerDependencies:
- '@babel/core': ^7.0.0-0
- dependencies:
- '@babel/core': 7.22.8
- dev: true
-
/@svgr/babel-plugin-remove-jsx-attribute@8.0.0(@babel/core@7.23.2):
resolution: {integrity: sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==}
engines: {node: '>=14'}
@@ -3908,15 +3890,6 @@ packages:
'@babel/core': 7.23.2
dev: true
- /@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0(@babel/core@7.22.8):
- resolution: {integrity: sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==}
- engines: {node: '>=14'}
- peerDependencies:
- '@babel/core': ^7.0.0-0
- dependencies:
- '@babel/core': 7.22.8
- dev: true
-
/@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0(@babel/core@7.23.2):
resolution: {integrity: sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==}
engines: {node: '>=14'}
@@ -3926,15 +3899,6 @@ packages:
'@babel/core': 7.23.2
dev: true
- /@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0(@babel/core@7.22.8):
- resolution: {integrity: sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==}
- engines: {node: '>=14'}
- peerDependencies:
- '@babel/core': ^7.0.0-0
- dependencies:
- '@babel/core': 7.22.8
- dev: true
-
/@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0(@babel/core@7.23.2):
resolution: {integrity: sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==}
engines: {node: '>=14'}
@@ -3944,15 +3908,6 @@ packages:
'@babel/core': 7.23.2
dev: true
- /@svgr/babel-plugin-svg-dynamic-title@8.0.0(@babel/core@7.22.8):
- resolution: {integrity: sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==}
- engines: {node: '>=14'}
- peerDependencies:
- '@babel/core': ^7.0.0-0
- dependencies:
- '@babel/core': 7.22.8
- dev: true
-
/@svgr/babel-plugin-svg-dynamic-title@8.0.0(@babel/core@7.23.2):
resolution: {integrity: sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==}
engines: {node: '>=14'}
@@ -3962,15 +3917,6 @@ packages:
'@babel/core': 7.23.2
dev: true
- /@svgr/babel-plugin-svg-em-dimensions@8.0.0(@babel/core@7.22.8):
- resolution: {integrity: sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==}
- engines: {node: '>=14'}
- peerDependencies:
- '@babel/core': ^7.0.0-0
- dependencies:
- '@babel/core': 7.22.8
- dev: true
-
/@svgr/babel-plugin-svg-em-dimensions@8.0.0(@babel/core@7.23.2):
resolution: {integrity: sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==}
engines: {node: '>=14'}
@@ -3980,15 +3926,6 @@ packages:
'@babel/core': 7.23.2
dev: true
- /@svgr/babel-plugin-transform-react-native-svg@8.1.0(@babel/core@7.22.8):
- resolution: {integrity: sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==}
- engines: {node: '>=14'}
- peerDependencies:
- '@babel/core': ^7.0.0-0
- dependencies:
- '@babel/core': 7.22.8
- dev: true
-
/@svgr/babel-plugin-transform-react-native-svg@8.1.0(@babel/core@7.23.2):
resolution: {integrity: sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==}
engines: {node: '>=14'}
@@ -3998,15 +3935,6 @@ packages:
'@babel/core': 7.23.2
dev: true
- /@svgr/babel-plugin-transform-svg-component@8.0.0(@babel/core@7.22.8):
- resolution: {integrity: sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==}
- engines: {node: '>=12'}
- peerDependencies:
- '@babel/core': ^7.0.0-0
- dependencies:
- '@babel/core': 7.22.8
- dev: true
-
/@svgr/babel-plugin-transform-svg-component@8.0.0(@babel/core@7.23.2):
resolution: {integrity: sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==}
engines: {node: '>=12'}
@@ -4016,23 +3944,6 @@ packages:
'@babel/core': 7.23.2
dev: true
- /@svgr/babel-preset@8.1.0(@babel/core@7.22.8):
- resolution: {integrity: sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==}
- engines: {node: '>=14'}
- peerDependencies:
- '@babel/core': ^7.0.0-0
- dependencies:
- '@babel/core': 7.22.8
- '@svgr/babel-plugin-add-jsx-attribute': 8.0.0(@babel/core@7.22.8)
- '@svgr/babel-plugin-remove-jsx-attribute': 8.0.0(@babel/core@7.22.8)
- '@svgr/babel-plugin-remove-jsx-empty-expression': 8.0.0(@babel/core@7.22.8)
- '@svgr/babel-plugin-replace-jsx-attribute-value': 8.0.0(@babel/core@7.22.8)
- '@svgr/babel-plugin-svg-dynamic-title': 8.0.0(@babel/core@7.22.8)
- '@svgr/babel-plugin-svg-em-dimensions': 8.0.0(@babel/core@7.22.8)
- '@svgr/babel-plugin-transform-react-native-svg': 8.1.0(@babel/core@7.22.8)
- '@svgr/babel-plugin-transform-svg-component': 8.0.0(@babel/core@7.22.8)
- dev: true
-
/@svgr/babel-preset@8.1.0(@babel/core@7.23.2):
resolution: {integrity: sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==}
engines: {node: '>=14'}
@@ -4050,19 +3961,6 @@ packages:
'@svgr/babel-plugin-transform-svg-component': 8.0.0(@babel/core@7.23.2)
dev: true
- /@svgr/core@8.1.0:
- resolution: {integrity: sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==}
- engines: {node: '>=14'}
- dependencies:
- '@babel/core': 7.22.8
- '@svgr/babel-preset': 8.1.0(@babel/core@7.22.8)
- camelcase: 6.3.0
- cosmiconfig: 8.1.3
- snake-case: 3.0.4
- transitivePeerDependencies:
- - supports-color
- dev: true
-
/@svgr/core@8.1.0(typescript@5.2.2):
resolution: {integrity: sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==}
engines: {node: '>=14'}
@@ -4107,7 +4005,7 @@ packages:
dependencies:
'@babel/core': 7.23.2
'@svgr/babel-preset': 8.1.0(@babel/core@7.23.2)
- '@svgr/core': 8.1.0
+ '@svgr/core': 8.1.0(typescript@5.2.2)
'@svgr/hast-util-to-babel-ast': 8.0.0
svg-parser: 2.0.4
transitivePeerDependencies:
@@ -6991,16 +6889,6 @@ packages:
yaml: 1.10.2
dev: true
- /cosmiconfig@8.1.3:
- resolution: {integrity: sha512-/UkO2JKI18b5jVMJUp0lvKFMpa/Gye+ZgZjKD+DGEN9y7NRcf/nK1A0sp67ONmKtnDCNMS44E6jrk0Yc3bDuUw==}
- engines: {node: '>=14'}
- dependencies:
- import-fresh: 3.3.0
- js-yaml: 4.1.0
- parse-json: 5.2.0
- path-type: 4.0.0
- dev: true
-
/cosmiconfig@8.3.6(typescript@5.2.2):
resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==}
engines: {node: '>=14'}
@@ -10223,6 +10111,7 @@ packages:
/local-pkg@0.4.3:
resolution: {integrity: sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==}
engines: {node: '>=14'}
+ dev: true
/local-pkg@0.5.0:
resolution: {integrity: sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==}
diff --git a/src/loaders.ts b/src/loaders.ts
index 0e0df740..6757b416 100644
--- a/src/loaders.ts
+++ b/src/loaders.ts
@@ -1,7 +1,13 @@
import type { Awaitable } from '@antfu/utils'
import { FileSystemIconLoader as IconifyFileSystemIconLoader } from '@iconify/utils/lib/loader/node-loaders'
+import { createExternalPackageIconLoader } from '@iconify/utils/lib/loader/external-pkg'
+import type { AutoInstall, ExternalPkgName } from '@iconify/utils/lib/loader/types'
import type { CustomIconLoader } from '.'
export function FileSystemIconLoader(dir: string, transform?: (svg: string) => Awaitable): CustomIconLoader {
return IconifyFileSystemIconLoader(dir, transform)
}
+
+export function ExternalPackageIconLoader(packageName: ExternalPkgName, autoInstall?: AutoInstall): Record {
+ return createExternalPackageIconLoader(packageName, autoInstall)
+}