FortAwesome / vue-fontawesome

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

Dynamically Importing Icons #170

Open knackjason opened 5 years ago

knackjason commented 5 years ago

I'm trying to dynamically import icons using import(), but am running into some issues. Has anyone done this or is this not supported?

Below is what I've tried, with varying results. I'm using a Pro icon here and have everything correctly set up to use the Pro packages (Pro icons imported via the static import work just fine). This code is in my main.js, before I bootstrap Vue.

First I tried:

import { library } from '@fortawesome/fontawesome-svg-core';

const x = '@fortawesome/pro-light-svg-icons/faAlicorn';
import(x)
  .then((icon) => {
    library.add(icon);
  });

which results in:

Error: Cannot find module '@fortawesome/pro-light-svg-icons/faAlicorn'
Could not find one or more icon(s) Object { prefix: "fal", iconName: "alicorn" } Object {  }

Next I tried:

import { library } from '@fortawesome/fontawesome-svg-core';

import('@fortawesome/pro-light-svg-icons/faAlicorn')
  .then((icon) => {
    library.add(icon);
  });

which results in the icon showing up on the screen, but I see this in my console:

TypeError: icon is undefined[Learn More]

If anyone has some insights into what's going on and/or how to get it working (w/o errors in the console), I'd appreciate it!

jarvelov commented 5 years ago

I don't know what the official stance is on this but I got it working by wrapping the FontAwesomeIcon component in my own component. It supports icons using both String and Array as the icon prop.

It has no real error handling other than that it shows the spinner-thirdicon while loading the icon and the exclamationicon if it fails to load.

These are the versions I'm using

    "@fortawesome/fontawesome-svg-core": "^1.2.17",
    "@fortawesome/free-solid-svg-icons": "^5.8.1",
    "@fortawesome/vue-fontawesome": "^0.1.6",

This is the component, place it in a .vue file and use it as you would use the FontAwesomeIconcomponent.

<script>
import { library } from '@fortawesome/fontawesome-svg-core';
import { faExclamation, faSpinnerThird } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
import { camelCase } from 'lodash';

library.add([faExclamation, faSpinnerThird]);

export default {
  name: 'font-awesome-dynamic-icon',
  props: FontAwesomeIcon.props,
  data() {
    return {
      fetched: null,
      fetchedIcon: null,
      iconTypes: {
        fas: 'solid',
        far: 'regular',
        fal: 'light',
      },
    };
  },
  computed: {
    iconDefinition() {
      if (this.fetchedIcon) {
        return this.fetchedIcon;
      }

      if (library.definitions[this.iconPrefix] && library.definitions[this.iconPrefix][this.iconName]) {
        return {
          icon: library.definitions[this.iconPrefix][this.iconName],
          iconName: this.iconName,
          prefix: this.iconPrefix,
        };
      }

      return null;
    },
    iconFileName() {
      return camelCase(`fa-${this.iconName}`);
    },
    iconName() {
      return Array.isArray(this.icon) ? this.icon[1] : this.icon;
    },
    iconPrefix() {
      return Array.isArray(this.icon) ? this.icon[0] : 'fas';
    },
    iconType() {
      return this.iconTypes[this.iconPrefix];
    },
  },
  methods: {
    fetchIcon() {
      this.fetched = false;
      return import(/* webpackChunkName: "fonts/[request]" */
      `@fortawesome/free-${this.iconType}-svg-icons/${this.iconFileName}.js`)
        .then((response) => {
          this.fetched = true;

          if (response && response.definition) {
            this.fetchedIcon = response.definition;
            library.add(response.definition);
          }
        })
        .catch(() => {
          this.fetched = true;
        });
    },
  },
  created() {
    if (!this.iconDefinition) {
      this.fetchIcon();
    }
  },
  watch: {
    icon() {
      if (!this.iconDefinition) {
        this.fetchIcon();
      }
    },
  },
  render() {
    if (!this.iconDefinition) {
      if (!this.fetched) {
        return this.$createElement(FontAwesomeIcon, {
          props: {
            spin: true,
            icon: faSpinnerThird,
          },
        });
      }

      return this.$createElement(FontAwesomeIcon, {
        props: {
          icon: faExclamation,
        },
      });
    }

    return this.$createElement(FontAwesomeIcon, {
      props: {
        ...this.$options.propsData,
        icon: this.iconDefinition,
      },
    });
  },
};
</script>
WvanDam commented 5 years ago

@jarvelov Thanks!

I was stuck for a bit with webpack errors because I didn't add .js to the import. Finding this solved that and more

Is there a reason, other than probably performance, to not use findIconDefinition from the API? https://fontawesome.com/how-to-use/with-the-api/methods/findicondefinition

PS. You're missing faExclamation in the library.add statement.

jarvelov commented 5 years ago

@WvanDam Glad it helped and thanks for the heads up about faExclamation. I also noticed that iconName wasn't used and thus array values in icon prop wouldn't work.

Regarding findIconDefinition from the API: No there's no reason to not use it. The only reason I didn't use it was because I didn't know about it, but it should definitely be used as the code will be cleaner and from a quick glance at the source code it does pretty much the exact same thing.

Zyles commented 5 years ago

Trying to get this to work but I get a TypeError: Cannot read property 'prefix' of undefined error on library.add([faExclamation, faSpinnerThird])

Got any updated code?

marceloavf commented 4 years ago

Thank you for your help @jarvelov, but this code doesn't seems to be optimized.

I ran some tests with webpack-bundle-analyzer and it goes from 2mb to 24mb after doing this trick, seems that each pass throw some big chunk for every component and import it all in the end.

robmadole commented 4 years ago

@Zyles try library.add(faExclamation, faSpinnerThird)

scadergit commented 4 years ago

A year later...I discovered (though analyzing the module loaded via import(...)) that it's looking for the "icon" property that is a child of the "definition" property of the module. For example:

import('@fortawesome/pro-light-svg-icons/faAlicorn').then((icon) => {
  library.add(icon.definition);
});

Any feedback from Font Awesome if that is expected? Not sure why this differs from the static import syntax that can be passed entirely into the add method.

dannywp commented 3 years ago

@marceloavf Did you ever solve the performance issue?

marceloavf commented 3 years ago

@dannywp Nope went back to manual import 😞.

klausXR commented 3 years ago

Any movement on this? This is pretty unfortunate, at least provide a workaround or an explanation.

stemeda commented 3 years ago

I'm trying something like this. Later I want to make the Icon dynamic, but for testing I placed all strings as a constant:

<template>
    <li class="nav-item">
      <a href="#" class="nav-link">
        <font-awesome-icon icon="atom" class="nav-icon" />
        <p>
          {{ name }}
        </p>
      </a>
    </li>
</template>

<script>
    import { library } from '@fortawesome/fontawesome-svg-core'
    import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'

    export default {
        components: {
            'font-awesome-icon': FontAwesomeIcon
        },
        props: ['name'],

        beforeCreate() {
            import('@fortawesome/free-solid-svg-icons/faAtom').then((icon) => {
                library.add(icon.definition);
                console.log(library);
            });
        }
    }
</script>

Debug Log:

{
  "definitions": {
    "fas": {
      "bars": [
        448,
        512,
        [],
        "f0c9",
        "M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z"
      ],
      "th-large": [
        512,
        512,
        [],
        "f009",
        "M296 32h192c13.255 0 24 10.745 24 24v160c0 13.255-10.745 24-24 24H296c-13.255 0-24-10.745-24-24V56c0-13.255 10.745-24 24-24zm-80 0H24C10.745 32 0 42.745 0 56v160c0 13.255 10.745 24 24 24h192c13.255 0 24-10.745 24-24V56c0-13.255-10.745-24-24-24zM0 296v160c0 13.255 10.745 24 24 24h192c13.255 0 24-10.745 24-24V296c0-13.255-10.745-24-24-24H24c-13.255 0-24 10.745-24 24zm296 184h192c13.255 0 24-10.745 24-24V296c0-13.255-10.745-24-24-24H296c-13.255 0-24 10.745-24 24v160c0 13.255 10.745 24 24 24z"
      ],
      "atom": [
        448,
        512,
        [],
        "f5d2",
        "M223.99908,224a32,32,0,1,0,32.00782,32A32.06431,32.06431,0,0,0,223.99908,224Zm214.172-96c-10.877-19.5-40.50979-50.75-116.27544-41.875C300.39168,34.875,267.63386,0,223.99908,0s-76.39066,34.875-97.89653,86.125C50.3369,77.375,20.706,108.5,9.82907,128-6.54984,157.375-5.17484,201.125,34.958,256-5.17484,310.875-6.54984,354.625,9.82907,384c29.13087,52.375,101.64652,43.625,116.27348,41.875C147.60842,477.125,180.36429,512,223.99908,512s76.3926-34.875,97.89652-86.125c14.62891,1.75,87.14456,10.5,116.27544-41.875C454.55,354.625,453.175,310.875,413.04017,256,453.175,201.125,454.55,157.375,438.171,128ZM63.33886,352c-4-7.25-.125-24.75,15.00391-48.25,6.87695,6.5,14.12891,12.875,21.88087,19.125,1.625,13.75,4,27.125,6.75,40.125C82.34472,363.875,67.09081,358.625,63.33886,352Zm36.88478-162.875c-7.752,6.25-15.00392,12.625-21.88087,19.125-15.12891-23.5-19.00392-41-15.00391-48.25,3.377-6.125,16.37891-11.5,37.88478-11.5,1.75,0,3.875.375,5.75.375C104.09864,162.25,101.84864,175.625,100.22364,189.125ZM223.99908,64c9.50195,0,22.25586,13.5,33.88282,37.25-11.252,3.75-22.50391,8-33.88282,12.875-11.377-4.875-22.62892-9.125-33.88283-12.875C201.74516,77.5,214.49712,64,223.99908,64Zm0,384c-9.502,0-22.25392-13.5-33.88283-37.25,11.25391-3.75,22.50587-8,33.88283-12.875C235.378,402.75,246.62994,407,257.8819,410.75,246.25494,434.5,233.501,448,223.99908,448Zm0-112a80,80,0,1,1,80-80A80.00023,80.00023,0,0,1,223.99908,336ZM384.6593,352c-3.625,6.625-19.00392,11.875-43.63479,11,2.752-13,5.127-26.375,6.752-40.125,7.75195-6.25,15.00391-12.625,21.87891-19.125C384.7843,327.25,388.6593,344.75,384.6593,352ZM369.65538,208.25c-6.875-6.5-14.127-12.875-21.87891-19.125-1.625-13.5-3.875-26.875-6.752-40.25,1.875,0,4.002-.375,5.752-.375,21.50391,0,34.50782,5.375,37.88283,11.5C388.6593,167.25,384.7843,184.75,369.65538,208.25Z"
      ]
    }
  }
}

The other icons are added somewhere else in my application. When rendering I get:

Could not find one or more icon(s) 
Object { prefix: "fas", iconName: "atom" }

Object {  }

Maybe someone has an idea to use this?

klausXR commented 3 years ago

You need to add the .js extension after the filename, otherwise it does not work.

Webpack, however, seems to detect the partial dynamic part, and since it cannot safely assume which file you will import at runtime, it takes the entire contents of the folder and adds it to the build.

For example, if you use this path @fortawesome/free-solid-svg-icons/ with an attempt to later load the atom icon, like so @fortawesome/free-solid-svg-icons/atom.js, webpack will put all of the icons from free-solid-svg-icons/ in your bundle, which causes a major size impact.

I'm not completely sure as to why exactly they end up in the main bundle.

They can still end up in the bundle, but be lazily loaded on demand, however, once I tried that, a huge file chunk ends up in the bundle even without importing all of them - ie, they are all served to the client despite the fact that I only use a small portion of them.

nikhilkhandelwal18 commented 2 years ago

following code loads dynamic icons in react : it loads icon 2 ways

  1. return FontAwesomeIcon from function after adding Icon to library
  2. Only add the Icon to the library in function or useEffect and render using <FontAwesomeIcon ..... />
import React from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { library } from '@fortawesome/fontawesome-svg-core';
//import { fas } from '@fortawesome/free-solid-svg-icons';

//library.add(fas);

export const DynamicImportAppGitHub = () => {
  const dynamicIcon = () => {
    import('@fortawesome/free-solid-svg-icons/faBus').then((icon) => {
      console.log(icon.definition);

      library.add(icon.definition);

      console.log(library);
    });

    return <FontAwesomeIcon icon={['fas', 'bus']} size="5x" />;
  };

  return (
    <div>
      Dynamic FontAwesome Icon :{dynamicIcon()}
      <FontAwesomeIcon icon={['fas', 'bus']} size="5x" />;
    </div>
  );
};