unplugin-icons
unplugin-icons copied to clipboard
Multiple compiler support?
Clear and concise description of the problem
Especially in a meta-framework like Astro, where we can mix frameworks, we may want to import icons as Astro components in the .astro file and as Vue components in the .vue file.
Currently we can retrieve raw SVG text using ?raw
query, but I think it would be nice to have a more flexible choice of formats.
Suggested solution
If an extension is specified, use the appropriate compiler for that extension. This would be a breaking change, so we need to make this feature opt-in.
examples:
Icons({
useExtension: false, // default
})
Icons({
useExtension: true,
})
Icons({
useExtension: {
// custom overrides
'.vue': 'vue2',
},
})
Alternative
Select compiler by ?compiler=astro
query.
In this solution, we probably do not need to make any changes to the options
.
Additional context
No response
Validations
- [X] Follow our Code of Conduct
- [X] Read the Contributing Guide.
- [X] Check that there isn't already an issue that request the same feature to avoid creating a duplicate.
I was able to use icons in both .astro and .vue files by configuring the Icons compiler property (under vite.plugins) to 'vue3'. I was also able set the IconsResolver (under vite.plugins.Components.resolvers) to allow for icon pack aliases and a custom template prefix to use. These settings are working for both Astro and Vue components. As a bonus, the 'unplugin-vue-components' package allows for auto-importing icons within the Vue components (still have to manually import in the Astro files though).
This addressed my use case but I don't think this approach would work should you have multiple UI integrations (e.g. Vue + Svelte), so something like your proposed solution would make sense.
I'm using this in a project which has both astro and svelte components, it works correctly without extra configuration by simply calling the Icons function twice inside the plugins array, one for svelte and one for astro, but the one for svelte should be first:
export default defineConfig({
integrations: [svelte()],
vite: {
plugins: [
Icons({
compiler: 'svelte',
autoInstall: true,
}),
Icons({
compiler: 'astro',
autoInstall: true,
}),
],
},
});
I'm using this in a project which has both astro and svelte components, it works correctly without extra configuration by simply calling the Icons function twice inside the plugins array, one for svelte and one for astro, but the one for svelte should be first:
export default defineConfig({ integrations: [svelte()], vite: { plugins: [ Icons({ compiler: 'svelte', autoInstall: true, }), Icons({ compiler: 'astro', autoInstall: true, }), ], }, });
@DeadOce4n This does seem to work, thanks! Do you use typescript? I can't figure out how to set up type declarations for this. The code compiles fine, but I get typescript errors in either .astro
or .svelte
files. If I add
// tsconfig.json
{
"compilerOptions": {
"types": [
"unplugin-icons/types/astro",
]
}
}
as per the docs, my astro imports show no errors. But as soon as I try to add the svelte types the astro types break again. I've tried adding "unplugin-icons/types/svelte"
to the "types"
array in tsconfig.json
as well as adding import 'unplugin-icons/types/svelte'
or /// <reference types="unplugin-icons/types/svelte" />
, as well as other things, but nothing works.
Any ideas how to fix this?
Subscribing here too for the Typescript solution
I solved it by using ~icons/*
for Astro
and virtual:icons/*
for Svelte
.
To achieve that I put the needed contents of the referenced .d.ts
files directly into env.d.ts
like this:
/// <reference path="../.astro/types.d.ts" />
/// <reference types="astro/client" />
/// <reference types="astro/astro-jsx" />
declare module "virtual:icons/*" {
import { SvelteComponent } from "svelte"
import type { SvelteHTMLElements } from "svelte/elements"
export default class extends SvelteComponent<SvelteHTMLElements["svg"]> {}
}
declare module "~icons/*" {
const component: (props: astroHTML.JSX.SVGAttributes) => astroHTML.JSX.Element
export default component
}
I solved it by using
~icons/*
forAstro
andvirtual:icons/*
forSvelte
.
Thanks for that @Visual-Dawg! I've been able to use icons for React and Astro using that tip.
If anyone is wondering, first, if you have imported the types on your tsconfig.json, remove it:
{
"extends": "astro/tsconfigs/strict",
"compilerOptions": {
"baseUrl": "/Users/arthur/Documents/Projetos/Khada/khada-astro",
"paths": {
"@/*": [
"./src/*"
]
},
"jsx": "react-jsx",
"jsxImportSource": "react",
- "types": [
- "unplugin-icons/types/astro",
- ]
}
}
Then add this at the end of your env.d.ts file:
//...
declare module "virtual:icons/*" {
import type { SVGProps } from "react";
import type React from "react";
const component: (props: SVGProps<SVGSVGElement>) => React.ReactElement;
export default component;
}
declare module "~icons/*" {
const component: (
props: astroHTML.JSX.SVGAttributes,
) => astroHTML.JSX.Element;
export default component;
}
To use an icon from a .tsx file:
import MdiAlarmOff from "virtual:icons/mdi/alarm-off";
export default function Test() {
return (
<MdiAlarmOff />
)
}
To use an icon from a .astro file:
---
import CarbonSun from "~icons/carbon/sun";
---
<CarbonSun />
@Trombach I did something similar to what others have commented here:
// src/env.d.ts
declare module 'icons:astro/*' {
const component: (
props: astroHTML.JSX.SVGAttributes,
) => astroHTML.JSX.Element;
export default component;
}
declare module 'icons:svelte/*' {
import { SvelteComponent } from 'svelte';
import type { SvelteHTMLElements } from 'svelte/elements';
export default class extends SvelteComponent<SvelteHTMLElements['svg']> {}
}
// astro.config.mjs
import { defineConfig } from 'astro/config';
import Icons from 'unplugin-icons/vite';
import svelte from '@astrojs/svelte';
// https://astro.build/config
export default defineConfig({
integrations: [svelte()],
vite: {
resolve: {
alias: [
{ find: 'icons:svelte', replacement: '~icons' },
{ find: 'icons:astro', replacement: '~icons' },
],
},
plugins: [
Icons({
compiler: 'svelte',
autoInstall: true,
}),
Icons({
compiler: 'astro',
autoInstall: true,
}),
],
},
});
Thanks @Visual-Dawg and @DeadOce4n for your solutions! I have gone with the vite resolve solution for now.
@antfu I'm wondering if this could maybe be improved either on the types or at least the documentation side? Since one of Astro's main strengths is that you can use other framework components within it, I think it would be great if the astro types that are shipped with this library allow you to import icons into all supported framework components.
I believe, one solution would be to add /icons:svelte/
, /icons:astro/
etc to the allowed URL_PREFIXES
array here and then declare all framework specific modules in the astro type file.
There are probably better solutions and I'm open for suggestions. I'm happy to provide a pull request for this if I get some guidance on how this should be handled.
Did someone manage to use it with Astro and React? Because it doesn't work since unplugin is trying to use an Astro component instead of a React component
Astro components cannot be rendered directly via function call, such as Component() or {items.map(Component)}.
EDIT: Even mixing React and Vue doesn't work, so indeed we really need a way to tells Unplugin which compiler it should use (via import query or path matching)
Did someone manage to use it with Astro and React? Because it doesn't work since unplugin is trying to use an Astro component instead of a React component
Astro components cannot be rendered directly via function call, such as Component() or {items.map(Component)}.
EDIT: Even mixing React and Vue doesn't work, so indeed we really need a way to tells Unplugin which compiler it should use (via import query or path matching)
Yes! I wrote a quick blog on it.