unplugin / unplugin-icons

🤹 Access thousands of icons as components on-demand universally.
https://www.npmjs.com/package/unplugin-icons
MIT License
3.66k stars 131 forks source link

Multiple compiler support? #253

Open SegaraRai opened 1 year ago

SegaraRai commented 1 year ago

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

jwing8 commented 1 year ago

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.

DeadOce4n commented 9 months ago

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,
      }),
    ],
  },
});
Trombach commented 5 months ago

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?

Visual-Dawg commented 5 months ago

Subscribing here too for the Typescript solution

Visual-Dawg commented 5 months ago

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
}
arthurrmp commented 5 months ago

I solved it by using ~icons/* for Astro and virtual:icons/* for Svelte.

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 />
DeadOce4n commented 5 months ago

@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,
      }),
    ],
  },
});
Trombach commented 4 months ago

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.

MrSquaare commented 4 months ago

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)

rishi-raj-jain commented 1 month ago

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.