react-fontawesome icon indicating copy to clipboard operation
react-fontawesome copied to clipboard

Lighter version for reduced bundle size

Open Floriferous opened this issue 5 years ago • 15 comments

Hi guys,

I noticed that fontawesome-svg-core takes up 38kB of space in our bundle, compared to react-fontawesome which only takes 6.6kB.

We're really only displaying icons without doing anything fancy, yet we have to serve 2500 loc of fontawesome-svg-core to all our customers. Is all of it necessary? Could there be a "lighter" version of that package that doesn't include the massive amount of code if you don't need it?

I'm not sure what's going on in there, I see an entire mock of Promises as well, what is it doing?

Floriferous avatar Apr 10 '19 13:04 Floriferous

I have the same thoughts when exploring react + FA. According to the docs, in order to utilize 15-20 icons, (each 500-800 bytes), I have to include ~30kb libs. Seems to be insane. In my case it is easier to import icons individually from node_modules/@fortawesome/fontawesome-free/svgs

Deliaz avatar Apr 11 '19 11:04 Deliaz

@Floriferous we don't have a lighter version right now but we might one day. The size of the package is to support all of the features that are listed on https://fontawesome.com/how-to-use/on-the-web

robmadole avatar Apr 12 '19 01:04 robmadole

In theory, using only explicit imports would make the icon (lookup) import obsolete, and then all that's left is the parse import from fontawesome-svg-core. If those two dependencies could be refactored (or a lighter sub-component extracted out of this one), then the fontawesome-svg-core dependency could be optional rather than required.

vdh avatar Jul 09 '19 13:07 vdh

Look up tree-shaking, and ensure that your bundling is doing this properly (FontAwesome does have some issues with it, but it does work given understanding of the subject) :-)

Rycochet avatar Jul 18 '19 11:07 Rycochet

Same here - just adding one icon via fontawesome-react will add 30 KB to our bundle (11 KB gzipped). The icon itself is about 260 B (gezipped) when added via SVGR + CRA 😭

I added an example based on CRA here: https://github.com/pstrh/fontawesome-bundle-size-issue

Screenshot from source-map-explorer: source-map-explorer

pstrh avatar Dec 13 '19 16:12 pstrh

Has anyone made progress on finding a solution for this? @fortawesome/fontawesome-svg-core is taking up a lot of space in our bundle when we don't use any of the extra features.

joshverd avatar Aug 04 '20 01:08 joshverd

If you don't need any of the extra features an alternative approach is to convert the svgs into react components yourself - via https://react-svgr.com/.

pstrh avatar Aug 04 '20 07:08 pstrh

It's almost 2022 now. In case anyone still needs a solution except svgr I've pushed a separate package react-fontawesome-svg-icon. It does not support power transforms or masking, but should be fine for basic usage.

eugenezinovyev avatar Nov 14 '21 21:11 eugenezinovyev

Anyone wants to remove @fortawesome/fontawesome-svg-core and reduce bundle size could use the following snippet:

import type { IconDefinition } from '@fortawesome/fontawesome-common-types'
import type { SVGProps } from 'react'

export type FaIconProps = SVGProps<SVGSVGElement> & { icon: IconDefinition }

const xmlns = 'http://www.w3.org/2000/svg'

export function FaIcon(props: FaIconProps) {
    const { icon: iconProps, children, ...rest } = props

    const { prefix, iconName, icon } = iconProps
    const [width, height, ligatures, unicode, svgPathData] = icon

    const dataFa = `${prefix}-${iconName}`

    return (
        <svg
            viewBox={`0 0 ${width} ${height}`}
            xmlns={xmlns}
            role={'img'}
            aria-hidden="true"
            data-fa={dataFa}
            {...rest}
        >
            {children}
            {Array.isArray(svgPathData) ? (
                <g>
                    <path d={svgPathData[0]} />
                    <path d={svgPathData[1]} />
                </g>
            ) : (
                <path d={svgPathData} />
            )}
        </svg>
    )
}

Usage:

import { faClose } from '@fortawesome/pro-solid-svg-icons'

<FaIcon icon={faClose} style={{width: 16, height: 16}} fill={'grey'} />

Another way without react:

import type { IconDefinition } from '@fortawesome/fontawesome-common-types'

function faIconToString(def: IconDefinition) {
    const { icon } = def
    const [width, height, ligatures, unicode, svgPathData] = icon

    let content = ''
    if (Array.isArray(svgPathData)) {
        content = `<g>${svgPathData.map((x) => `<path d="${x}" />`)}</g>`
    } else {
        content = `<path d="${svgPathData}" />`
    }

    return `<svg viewBox="0 0 ${width} ${height}" role="img" xmlns="http://www.w3.org/2000/svg">${content}</svg>`
}

export default faIconToString

Usage:

import { faClose } from '@fortawesome/pro-solid-svg-icons'

// when component mount
element.current.append(faIconToString(faClose))

Adapt as you need.

sep2 avatar Jun 17 '22 08:06 sep2

@eugenezinovyev wow, this actually does everything I need it to. Thank you!

rscotten avatar Jul 24 '22 01:07 rscotten

It's almost 2022 now. In case anyone still needs a solution except svgr I've pushed a separate package react-fontawesome-svg-icon. It does not support power transforms or masking, but should be fine for basic usage.

Oh my gosh this is amazing, works perfectly and saves me 20kb in my bundle, thank you!

maxratajczak avatar Oct 09 '22 17:10 maxratajczak

Copied from sept2's solution for an angular app (ng 15 with standalone components)

import { NgIf } from '@angular/common';
import { Component, Input } from '@angular/core';

import { Pipe, PipeTransform } from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';
import { IconDefinition } from '@fortawesome/fontawesome-svg-core';

@Pipe({
  standalone: true,
  name: 'faIcon',
})
export class FaIconPipe implements PipeTransform {
  constructor(private sanitizer: DomSanitizer) {}

  transform(def: IconDefinition) {
    const { icon } = def;
    const [width, height, ligatures, unicode, svgPathData] = icon;

    let content = '';
    if (Array.isArray(svgPathData)) {
      content = `<g>${svgPathData.map((x) => `<path d="${x}" />`)}</g>`;
    } else {
      content = `<path fill="currentColor" d="${svgPathData}" />`;
    }

    return this.sanitizer.bypassSecurityTrustHtml(content);
  }
}

@Component({
  standalone: true,
  selector: 'standard-icon',
  template: `
    <svg
      *ngIf="icon"
      class="svg-inline--fa fa-fw"
      [attr.viewBox]="'0 0 ' + icon.icon[0] + ' ' + icon.icon[1]"
      role="img"
      xmlns="http://www.w3.org/2000/svg"
      [innerHTML]="icon | faIcon"
    ></svg>
  `,
  styles: [
    `
      svg:not(:root).svg-inline--fa,
      svg:not(:host).svg-inline--fa {
        overflow: visible;
        box-sizing: content-box;
      }

      .svg-inline--fa.fa-fw {
        width: var(--fa-fw-width, 1.25em);
      }

      .fa-fw {
        text-align: center;
        width: 1.25em;
      }

      .svg-inline--fa {
        display: var(--fa-display, inline-block);
        height: 1em;
        overflow: visible;
        vertical-align: -0.125em;
      }
    `,
  ],
  imports: [NgIf, FaIconPipe],
})
export class StandardIconComponent {
  @Input() icon: IconDefinition | undefined;
}

NateRadebaugh avatar Nov 22 '22 15:11 NateRadebaugh

bump,

there is no reason why me using few emoticons on react pulls in 70KB+ worth of stuff

chr4ss12 avatar Jul 06 '23 11:07 chr4ss12

Can remove prop-types as a dependency. No reason this should be listed as a dependency

jlurena avatar Jul 16 '23 17:07 jlurena

If you're only using free, then just grab the svg source directly from their site: https://fontawesome.com/v5/search?o=r&m=free. Wrap it how you want and call it a day.

morganney avatar Oct 25 '23 21:10 morganney