Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature Request] Include Icons in build instead of doing network requests #34

Closed
madebyfabian opened this issue Dec 12, 2022 · 31 comments · Fixed by #243
Closed

[Feature Request] Include Icons in build instead of doing network requests #34

madebyfabian opened this issue Dec 12, 2022 · 31 comments · Fixed by #243

Comments

@madebyfabian
Copy link

madebyfabian commented Dec 12, 2022

Hey there, thanks so much for this module! It's absolutely great and easy to use.
I noticed something though, where I couldn't find a config for. In Production, the icons are loaded over the network via https://api.iconify.design/ri.json?icons=icon-name. This can cause several issues, such as:

  • When API is potentially offline, users would not see icons?
  • In the EU it's actually a law issue with GDPR since the HTTP endpoint could receive & process some personal data in the headers (I'm sure you don't, but of course I don't have any control), like the IP, User-Agent, Refrerrer, etc. See the google fonts GDPR issue https://complianz.io/google-fonts-and-gdpr-does-it-work/)
  • The loading times for slow connections could be high, leading in slower web loading times
  • Building offline apps (PWAs) might be more difficult (I see that it gets cached in localStorage)
  • Other general issues I may not have listed above

Maybe I'm understanding wrong and the Module perfectly works without connecting to this API, but I think it would be great to have an option to store all used icons locally on build and request them from a local server endpoint.

@vencho-mdp
Copy link

Hey guys! I was thinking of implementing this feature but I was wondering which approach should I take. Maybe adding a module option like offline and then downloading icons at build-time and referencing them conditionally in the Icon component.

@toniengelhardt
Copy link
Contributor

toniengelhardt commented Feb 14, 2023

I think it's also important for online mode, bc for instance if I have a theme switcher and click on it (switching color theme and icon) the icon disappears for a few hundred ms until the new one is downloaded. Would be nice if in online mode it would download and cache all icons right away in the background after page load is finished.

You can see the effect here: https://webapicheck.com

@suchorski
Copy link

Hey guys! I was thinking of implementing this feature but I was wondering which approach should I take. Maybe adding a module option like offline and then downloading icons at build-time and referencing them conditionally in the Icon component.

Any news about that? Thanks

@XStarlink
Copy link

I found this in the Doc for building an offline bundle https://docs.iconify.design/icon-components/bundles/

@suchorski
Copy link

I found this in the Doc for building an offline bundle https://docs.iconify.design/icon-components/bundles/

Hi, thanks for the help. I took a look.

I'm using this https://github.com/nuxt-modules/icon and figured out that this module puts the svg directly on html, so it loads offline.

I saw that using capacitor and generating an apk the icon wasn't showing. So I think the problem is with capacitor and not from offline.

@vencho-mdp
Copy link

I think it's also important for online mode, bc for instance if I have a theme switcher and click on it (switching color theme and icon) the icon disappears for a few hundred ms until the new one is downloaded. Would be nice if in online mode it would download and cache all icons right away in the background after page load is finished.

You can see the effect here: https://webapicheck.com

@toniengelhardt I guess you could do that with plain CSS

@toniengelhardt
Copy link
Contributor

I think it's also important for online mode, bc for instance if I have a theme switcher and click on it (switching color theme and icon) the icon disappears for a few hundred ms until the new one is downloaded. Would be nice if in online mode it would download and cache all icons right away in the background after page load is finished.
You can see the effect here: https://webapicheck.com

@toniengelhardt I guess you could do that with plain CSS

It says "the icons won't be loaded on initial load and an HTTP request will be made to Iconify CDN to load them" in the README though?

@lynxionxs
Copy link

lynxionxs commented Mar 6, 2023

I need this. It only shows mdi:access-point-network-off text, instead of the icon when the PWA is offline.

@mkdirnow
Copy link

In the end, I downloaded icons from icones.js.org and integrated them using nuxt-icons. 😅

@lynxionxs
Copy link

In the end, I downloaded icons from icones.js.org and integrated them using nuxt-icons. sweat_smile

@mkdirnow how is that possible?

@toniengelhardt
Copy link
Contributor

In the end, I downloaded icons from icones.js.org and integrated them using nuxt-icons. 😅

Seems inconvenient though...

@mkdirnow
Copy link

@mkdirnow how is that possible?

Seems inconvenient though...

In fact, most of the icons in my project are individual SVG files, and only a few places use icons from icones.js.org.
However, this plugin is really convenient, and I'm looking forward to the offline feature.

@warflash
Copy link
Member

warflash commented Mar 31, 2023

I agree with everything @madebyfabian mentioned but want to add another point.
The icon svg's are embedded into the rendered html nuxt renders meaning the nuxt server had to actually fetch them on request time - which can't be beneficial for performance/ time to first byte.

Tbh I was assuming things were bundled at build time as nowhere in the readme individual http requests are mentioned.
Hoping this is something that can be resolved, going down the rabbit hole of individually creating components for each icon sort of defeats the purpose of the module unfortunately

@valgeirb
Copy link

valgeirb commented Apr 3, 2023

I was using unplugin-icons (which bundled the icons at build time) before migrating to Nuxt.

Do you foresee this to be a difficult issue to solve?

@leoboyerbx
Copy link

With Unplugin icons you need to import manually the icons you use, with Nuxt icons its a string that could be any unicons icon.
Maybe we could have a whitelist feature that bundles the whitelisted icons ?

@valgeirb
Copy link

valgeirb commented Apr 3, 2023

With Unplugin icons you need to import manually the icons you use, with Nuxt icons its a string that could be any unicons icon. Maybe we could have a whitelist feature that bundles the whitelisted icons ?

Not if you set up auto-importing

@frasza
Copy link

frasza commented May 20, 2023

As a user od the module I would also love to see the option to support bundled icons without having to do the request to get them. Only missing part for it to be perfect for us.

@stafyniaksacha
Copy link

We can create a nitro event handler to serve the icons data from either @iconify/json or @iconify-json/[collection-id] if installed, with a fallback to iconify api?

What do you think @atinux ?

Copy link
Member

atinux commented Dec 4, 2023

In what case would it be more performant than Iconify API @stafyniaksacha ?

@kantum
Copy link

kantum commented Feb 2, 2024

I'm currently developing a Vue application designed for offline use. I've been contemplating migrating to Nuxt due to its appealing features, such as automatic component imports and integrated UI libraries. However, I'm concerned that Nuxt may predominantly cater to applications with an online-first approach.

I'm eager to hear from anyone who has experience in building offline applications using Nuxt. Specifically, I'm interested in understanding any potential drawbacks or limitations encountered during development. Does Nuxt accommodate the needs of offline applications effectively, or are there significant challenges I should be aware of?

@sohaha
Copy link

sohaha commented Mar 17, 2024

Currently very unfriendly for offline applications.

Copy link
Member

atinux commented Mar 18, 2024

We are planning to rewrite this module to avoid making network requests.

As workaround, if you are using UnoCSS, you can use https://unocss.dev/presets/icons

If you are using TailwindCSS, you take take a look at https://github.com/egoist/tailwindcss-icons

If you are using Nuxt UI, we already support offline with UIcon component (based on TailwindCSS-Icons)

delano added a commit to AnimalFoodBank/afb-requests that referenced this issue May 1, 2024
re: "Include Icons in build instead of doing network requests"

nuxt/icon#34 (comment)
Signed-off-by: delano <delano@cpan.org>
@ChrisGV04
Copy link

I still believe there would be some of us who would benefit from bundling the icons into the build. Maybe there could be an option to opt-in?

Mainly because right now (v1.2.0) if I create a SPA with ssr: false, every time I navigate to a page with new icons they aren't rendered until they load from Iconify's API. I just see them suddenly appear, which doesn't look good.

It would be nice to be able to bundle the icons natively without relying on the UnoCSS or TailwindCSS workarounds.

@antfu
Copy link
Member

antfu commented Jul 20, 2024

The main selling point of using Nuxt Icons is the dynamic bits, which support rendering icons that you retrieved at runtime (dynamic content, user content, etc.). Other solutions like unplugin-icons and UnoCSS Icons only support bundling the icons that are known at build time. Supposedly, they should work as a complement; Nuxt Icon v1 also dedupe the icon fetching if it's already been provided by UnoCSS.

Because the focus is fully dynamic, meaning it would be hard for Nuxt Icon to pre-bundle the icons at build time.

For example, to know the usage of your icons, the module would need to scan every file in your project to detect usage statically like this:

<template>
  <Icon name="carbon:sun" />
</template>

Where the cases like:

<script setup>
const props = defineProps({
  icon: String
})
</script>

<template>
  <Icon :name="icon" />
</template>

It won't be possible to know what's the value of the props passed in at build time. (any many more edge cases like this).

That said, I would still love to improve the DX with this, but in order to perform good static analysis result, we would need to:

  1. Define a strict syntax convention that can be statically extracted (we are kind of re-implementing UnoCSS in this case)
  2. Only usages following that convention would be able to be bundled, the others will still fallback to runtime fetching
  3. Opt-in this behavior as it would perform a project-wide filesystem scan. It can be inefficient for projects with a lot of files.

I would need to have some examples of your project usage to know what would be the best way to support that.

Please share your project or a minified version below and tell your usage/expectation.

So we could come up with a better design that works for the majority of the users. Thanks

@toniengelhardt
Copy link
Contributor

@antfu it might not be ideal, but if the scanning is too complex, maybe there could be a preload list in the config, similar to the safelist in unocss, and we can then prefetch all icons in the preload list and alias list if the preload setting is enabled.

Defining aliases for all icons is anyway good practice IMO, it makes the project more maintainable.

@ChrisGV04
Copy link

You are right @antfu. I see your point in how this introduces complexity beyond what I would know how to do.

I won't say this is a deal breaker, it's just a UX/DX improvement. It is definitely so nice to only write the icon name and magically have it appear. Unfortunately, as soon as the client is hydrated and SPA takes control, rendering icons that weren't at the initial page load last a tiny bit to appear, which feels weird but it's not critical.

If this feature is at all possible, but requires defining a fixed pattern to write the names of icons (such as TailwindCSS Icons with i-{collection}-{icon}) I believe that anyone who really needs it won't mind the extra step. That's how Nuxt UI v2 does it and I think it is both nice UX/DX. Of course, @nuxt/icon will use dynamic icons as the default and bundled would be opt-in.

I tried unplugin-icons and it works just fine. If this is not possible for @nuxt/icon, I will certainly use it. But of course, only writing <Icon name="i-ph-x" /> is way nicer than having to import every icon and use them as a separate component. That's why it's not a deal breaker, just a quality-of-life improvement.

Most of the projects I build are SPA's on the client side, since they are admin dashboards or management platforms. I don't use SSR as much. But even with SSR, once hydration happens, I have the same issue. Here's a video on what I mean (uses 3G network throttling to make the icon loading time more visible)

Arc.mp4

@antfu
Copy link
Member

antfu commented Jul 29, 2024

#208 and #223 should give the capability of doing so. We will still need to figure out a better way to do auto-discovery in the future

@antfu
Copy link
Member

antfu commented Aug 27, 2024

Please upgrade to v1.5.0 and give it a try with #243, feedback are greatly welcome (please create new issues).

@ChrisGV04
Copy link

ChrisGV04 commented Aug 27, 2024

Hi @antfu
Absolutely fenomenal job! I just updated the project that I demonstrated last time and it just works. I tested with the name of the icons being in multiple parts of the app and it worked nicely (<Icon name="..." /> and strings inside <script setup>).

I was even surprised to find out that dynamic icons still get loaded without any extra setup. For example:

<script setup lang="ts">
const props = defineProps<{ type: string }>();

export function getMimeTypeIcon(type: string) {
  if (!type) return 'i-heroicons-question-mark-circle';
  if (type.startsWith('image')) return 'i-heroicons-camera';
  if (type.startsWith('video')) return 'i-heroicons-video-camera';
  if (type.startsWith('audio')) return 'i-heroicons-musical-note';
  if (type.endsWith('csv')) return 'i-heroicons-table-cells';
  return 'i-heroicons-document';
}
</script>

<template>
  <Icon :name="`${getMimeTypeIcon(type)}-16-solid`" />
</template>

(I know that example is not ideal, but I wrote it just out of curiosity to test string templates and it worked)

Even though they won't get bundled since it's nearly impossible to scan for those cases, the fact that I didn't have to set anything up to make that icon dynamic is just magic.

Thank you very much! I will keep trying it with more use cases and let you know if anything doesn't work.

@some-user123
Copy link

The clientBundle/scan feature is great 👍

However, whenever an icon is missed, my app (ssr: false) still does a request to api.iconify.design. For legal reasons, I have to avoid any such request and would prefer a missing icon, that reminds developers of adding it. Can this be achieved with configuration?

@ChrisGV04
Copy link

@some-user123 Avoiding to render if not included in the bundle is not possible and I don't believe it's part of what this module is trying to achieve.

I recommend you use unplugin-icons instead, since that library ALWAYS includes them in the bundle and never makes runtime external requests by default. That's how it was built and it might suit your requirements better.

Hope that helps!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.