Search code examples
ruby-on-railsrubywebpackwebpackerreact-rails

Load fonts from node_modules in react-rails application with webpack


I have a react-rails application set up with webpacker.

I am trying to load font-awesome-pro with it's fonts from node_modules.

I assume this is a trivial task but I can't seem to find any good documentation on how to do this.

This is what I have so far:

package.json dependencies:

  "dependencies": {
    "@rails/webpacker": "3.5",
    "babel-preset-react": "^6.24.1",
    "bootstrap": "^4.1.3",
    "prop-types": "^15.6.2",
    "react": "^16.5.2",
    "react-dom": "^16.5.2",
    "react-slick": "^0.23.1",
    "react_ujs": "^2.4.4",
    "slick-carousel": "^1.8.1",
    "tachyons-z-index": "^1.0.9"
  },
  "devDependencies": {
    "@fortawesome/fontawesome-pro": "^5.2.0",
    "babel-plugin-transform-class-properties": "^6.24.1",
    "babel-preset-env": "^1.7.0",
    "file-loader": "^2.0.0",
    "path": "^0.12.7",
    "webpack-dev-server": "2.11.2"
  }

file.js:

var path = require('path');

module.exports = {
  test: /\.(woff(2)?|eot|otf|ttf|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
    exclude: path.resolve(__dirname, '../../app/assets'),
    use: {
      loader: 'file-loader',
      options: {
        outputPath: 'fonts/',
        useRelativePath: false
      }
    }
}

environment.js

const { environment } = require('@rails/webpacker')
const file = require('./file')

environment.loaders.prepend('file', file)

module.exports = environment

application.scss:

@import '@fortawesome/fontawesome-pro/scss/fontawesome.scss';

application.rb:

config.assets.paths << Rails.root.join('node_modules')

What am I missing? From what I can gather, webpack should be looking at the node_modules directory, finding font files based on the webpack test and putting the assets into the output directory: fonts/.


Solution

  • FontAwesome with webfonts:

    For me with the free version the example below is working well. I don't know the pro version, but if I'm not mistaken, you just have to rename fontawesome-free to fontawesome-pro in the paths.

    application.scss:

    $fa-font-path: "~@fortawesome/fontawesome-free/webfonts";
    @import "~@fortawesome/fontawesome-free/scss/fontawesome.scss";
    @import "~@fortawesome/fontawesome-free/scss/solid.scss";
    @import "~@fortawesome/fontawesome-free/scss/regular.scss";
    @import "~@fortawesome/fontawesome-free/scss/brands.scss";
    

    In SCSS ~ (tilde import) means that look for the nearest node_modules directory. Not all SASS compilers supports it, but node-sass does, and this is the common for Webpack.

    This way in your html you only have to use your application.css. There's no need to include any other FontAwesome css files.

    Your font loader config seems OK (tested, worked). With that Webpack should resolve the font files and then copy them to your desired output as you wanted. This needs that your css-loader be configured with url: true but I that is the default.

    A minimal/usual config for the loaders in your Webpack config file:

    module: {
      rules: [
        {
          test: /\.s?css$/,
          use: [
            MiniCssExtractPlugin.loader, // optional (the most common way to export css)
            "css-loader", // its url option must be true, but that is the default
            "sass-loader"
          ]
        },
        {
          // find these extensions in our css, copy the files to the outputPath,
          // and rewrite the url() in our css to point them to the new (copied) location
          test: /\.(woff(2)?|eot|otf|ttf|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
          use: {
            loader: 'file-loader',
            options: {
              outputPath: 'fonts/'
            }
          }
        }
      ]
    },
    

    Loading only the needed fonts (the new way with JS and SVGs)

    Again, I will demonstrate it with the free version because I don't have the pro version.

    This way your generated bundle will only contain those icons what you need, resulting in a much smaller size which means faster page loads. (I'm using this in my projects)

    The needed packages:

    @fortawesome/fontawesome-svg-core
    @fortawesome/free-brands-svg-icons
    @fortawesome/free-regular-svg-icons
    @fortawesome/free-solid-svg-icons
    

    Include this in your scss file:

    @import "~@fortawesome/fontawesome-svg-core/styles";
    

    Create a new file, name it fontawesome.js:

    import { library, dom, config } from '@fortawesome/fontawesome-svg-core';
    
    config.autoAddCss = false;
    config.keepOriginalSource = false;
    config.autoReplaceSvg = true;
    config.observeMutations = true;
    
    // this is the 100% working way (deep imports)
    import { faUser } from '@fortawesome/free-solid-svg-icons/faUser';
    import { faHome } from '@fortawesome/free-solid-svg-icons/faHome';
    import { faFacebook } from '@fortawesome/free-brands-svg-icons/faFacebook';
    import { faYoutube } from '@fortawesome/free-brands-svg-icons/faYoutube';
    
    // this is the treeshaking way (better, but read about it below)
    import { faUser, faHome } from '@fortawesome/free-solid-svg-icons';
    import { faFacebook, faYoutube } from '@fortawesome/free-brands-svg-icons';
    
    library.add(faUser, faHome, faFacebook, faYoutube);
    dom.watch();
    

    .. and then require it somewhere in your js:

    require('./fontawesome');
    

    That's all. If you want to read more on this, start with understanding SVG JavaScript Core, have a look on its configuration and read the documantation of treeshaking.