Open knackjason opened 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-third
icon while loading the icon and the exclamation
icon 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 FontAwesomeIcon
component.
<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>
@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.
@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.
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?
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.
@Zyles try library.add(faExclamation, faSpinnerThird)
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.
@marceloavf Did you ever solve the performance issue?
@dannywp Nope went back to manual import 😞.
Any movement on this? This is pretty unfortunate, at least provide a workaround or an explanation.
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?
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.
following code loads dynamic icons in react : it loads icon 2 ways
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>
);
};
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 mymain.js
, before I bootstrap Vue.First I tried:
which results in:
Next I tried:
which results in the icon showing up on the screen, but I see this in my console:
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!