FortAwesome / vue-fontawesome

Font Awesome Vue component
https://fontawesome.com
MIT License
2.39k stars 133 forks source link

Automatically optimize to SVG symbols #223

Open kawazoe opened 4 years ago

kawazoe commented 4 years ago

Is your feature request related to a problem? Please describe. After getting a better understanding of how this library can be used through #217 , I had an interesting idea. I think that components should be self-contained and that recommending the library pattern (aka using library.add(...)) as a first solution to use this library isn't really a best practice.

This doesn't mean that the idea of a library isn't useful, through.

Describe the solution you'd like First, I wish that the documentation gets updated to recommend using the component and icons directly instead of through the library.

Second, I think that the library should be used as a performance optimization opportunity, with the help of an additional component, to automatically convert all usages of registered icons into pre-rendered svg symbols.

The recommended usage would then become this:

// app.vue
<template>
  <div id="app">
    <!-- Include a component that would render all icons in library with :symbol="true" -->
    <font-awesome-svg-icon-cache></font-awesome-svg-icon-cache>

    <!-- Write your app as always... --->
    <app-component></app-component>
  </div>
</template>

<script>
// ...
import { FontAwesomeSvgIconCache } from '@fortawesome/vue-fontawesome';

export default {
  components: {
    FontAwesomeSvgIconCache,
    AppComponent,
  },
};
</script>
// AppComponent.vue
<template>
  <div>
    <!-- Include an icon in your component. If icon is in library, it will switch to an svg symbol reference. -->
    <font-awesome-icon :icon="faAcorn"></font-awesome-icon>
  </div>
</template>

<script>
// ...
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
import { faAcorn } from '@fortawesome/something-svg-icons';

export default {
  components: {
    FontAwesomeSvgIconCache
  },
  data() {
    return {
      faAcorn,
    };
  },
};
</script>
// main.js
import { library } from '@fortawesome/fontawesome-svg-core';
import { faAcorn } from '@fortawesome/something-svg-icons';

// Only add icons to library that needs to be converted to svg symbols
library.add(faAcorn);

new Vue({ ... });

This solution turns the library into a performance optimization and highlights the fact that it is optional. This would be a breaking change as it drastically modifies the rendering behavior of the library. There might be a way of making this opt in, by only switching to this behavior if the cache component is rendered, for instance.

Describe alternatives you've considered There is always the possibility of managing svg symbols yourself, but I find this feature difficult to maintain as my project grows.

On one side, it is very convenient to add an icon with <font-awesome-icon :icon="['fas', 'acorn']" />, but then dealing with <svg><use xlink:href="#fas-fa-acorn" class="icon"></use></svg> everywhere seems like a lot of overhead to write. It's also hard to extract this into its own component and keep it relatable to people who only used vue-fontawesome before.

On the other side, it's also very clunky to manage the library when you plan to potentially extract some components into their own package. You end up with multiple ways of doing things, and it only gets worse when you think about including symbols in the mix.

Additional context I have already accomplished this by wrapping the FontAwesomeIcon component with my own, if you are interested in a prototype to use as a starting point.

While I do have a prototype ready, I see this more as an RFC where we could design a more interesting API for the use of SVG symbols.

robmadole commented 4 years ago

Interesting idea @kawazoe.

Some thoughts:

<!-- Include a component that would render all icons in library with :symbol="true" -->
<font-awesome-svg-icon-cache></font-awesome-svg-icon-cache>

I think if we were to go as far as having a pre-rendered set of symbols it would be best to do this not at render time but at build time. As libraries grow rendering every single icon on first page load (which is probably where this is going to happen) might negate some of the performance benefits you see with using SVG symbols. So that's something to consider.

This solution turns the library into a performance optimization and highlights the fact that it is optional. This would be a breaking change as it drastically modifies the rendering behavior of the library. There might be a way of making this opt in, by only switching to this behavior if the cache component is rendered, for instance.

Yeah, we wouldn't want to break existing functionality. I'd be 👎 to any changes like that if it broke backward compatibility. Plus I'm pretty sure we could come up with a way that is just an additive thing (even if it meant creating an entirely new project that uses vue-fontawesome).

There is always the possibility of managing svg symbols yourself, but I find this feature difficult to maintain as my project grows.

Yep, the management of this as the project grows is a sticking point. We haven't cracked the solution yet to "let me use any icon I want whenever I want but keep it performant and don't pre-download or render any of them first".

The thing with symbols that always irritate me is that at their most performant you really need to just use <svg><use xlink:href="#fas-fa-acorn" class="icon"></use></svg>. My irritation with it is the same as you have: it's a pain to style and put that markup where you need it but dangit it's also simple and the most performant. If you do anything else you are running an O(n) operation. When you have thousands of icons this quickly becomes an issue if you are working to speed up a page load.

On the other side, it's also very clunky to manage the library when you plan to potentially extract some components into their own package.

I wonder if there is an opportunity for API improvement here more than anywhere else. I haven't thought far in this direction but we have this same pain on fontawesome.com. The library is pretty unwieldy and we'd like to separate components out that don't rely on it.

Great thoughts @kawazoe ! Thanks for taking the time. We'll let this one stew a bit in an "RFC"-style waiting period and see what other might think.

kawazoe commented 4 years ago

I think if we were to go as far as having a pre-rendered set of symbols it would be best to do this not at render time but at build time. As libraries grow rendering every single icon on first page load (which is probably where this is going to happen) might negate some of the performance benefits you see with using SVG symbols. So that's something to consider.

Hmm, I agree with you on that one. It is something that I considered initially in my prototype but decided against for two reasons. 1) I am not sure of how to accomplish this in the current vuecli toolchain in an idiomatic way. 2) Not everyone uses vuecli, and any solution we choose here would probably tie us to webpack. I wanted to keep the proposition in a state where it could be easily implemented and maintained and I wasn't sure of your expertise around all of this. After all, writing a webpack plugin is very different from writing a component.

But lets intertain the idea that this would only be supported under vuecli. We could consider parsing every vue file to find usages of the icon component and automatically register them as symbols. We could use the webpack config file to list symbols we want to include, similar to the current library. We should probably generate a single file and include it in a way that would be chunkable for independent caching.

I wonder if there is an opportunity for API improvement here more than anywhere else. I haven't thought far in this direction but we have this same pain on fontawesome.com. The library is pretty unwieldy and we'd like to separate components out that don't rely on it.

This is also a very good point. I know I have suggested these improvements in the vue package, but I realize that the bulk of this should probably be handled by the svg-core package or some other generic package that can tie to webpack independently of vuejs if we go with the build-time solution.

robmadole commented 4 years ago

Not everyone uses vuecli, and any solution we choose here would probably tie us to webpack.

Yep, good point. We can assume most people are using Webpack but if we create a dependency on it that has some negative consequences. 🤔

I wasn't sure of your expertise around all of this [webpack plugins]

I think we can swing this if we decide to do it and I'm not overly concerned with maintenance or development effort.

We could use the webpack config file to list symbols we want to include, similar to the current library. We should probably generate a single file and include it in a way that would be chunkable for independent caching.

Explicit is the way to go. So yes, webpack config, .faiconsrc.json file in the root, something like that.

I know I have suggested these improvements in the vue package, but I realize that the bulk of this should probably be handled by the svg-core package or some other generic package

Yeah, this feels bigger than just the Vue component. I might end up re-framing the spirit of this work and posting it on the main FortAwesome/Font-Awesome repo.

kawazoe commented 1 year ago

Hey @robmadole , It's been a while since the last time we discussed this. I've had a small hack around the FontAwesomeIcon component in my personal project for a long time and it recently stopped working. That prompted me into coming back to this issue and looking into what it would take to get this stuff actually going.

I forked the repo and added a "proper" implementation for v3.x, if you want to take a look. The solution shows some advantages to not doing this at compile time, which is great as it makes this a lot simpler, but still shows that support from fontawesome-svg-core might be a good thing.

I also took a few liberties that might goes against some core ideas around the library, but they aren't necessary for the solution to work. If you want to take a look: https://github.com/kawazoe/vue-fontawesome/tree/3.x I also added some of info about the reasoning and how to use it in the readme. From my tests so far, it seems to work great. I haven't really added any unit tests though, since this is more of a prototype at this point.

I'd love to get your feedback, and if you think it's a good idea, maybe turn this into a proper PR.