Webpack 4 – Loading Fonts within SCSS url() and more!

Today we talk about an overview on how to set up Webpack loaders and a deeper dive on how to specifically handle fonts and load it through webpack!


Context

I was working on a project requirement that needed to import a specific font bought by the client (so I can’t just do a link tag with google fonts).

The project utilized React built over Webpack 4.

And so the challenge begins: allowing webpack to recognize non-JS code.

Here’s the project structure overview:

webpack.config.js
index.html
index.js
styles
   --- fonts
       --- custom_font.ttf
   --- index.scss

Preliminary Webpack.config.js Setup:

(You may skip this if you’ve set up your HMR, Babel, SCSS & CSS, and SVG loaders and go directly below for Font Loading)

This is our current module object within our webpack.config,js

P.S. I’m sorry if this is a picture, my wordpress editor doesnt allow me to insert a code block.

Screen Shot 2019-07-20 at 11.08.35 PM.png

You may install these loaders through

npm install --save-dev <loader_name>

Lines 18 – 20 (react-hot-loader/webpack and babel-loader) – allows webpack-dev-server to have the hot module reload functionality and allow us to use future javascript “features” within specs on proposal stages on the fly (with corresponding plugins of course).

Here’s a link for Hot Module Replacement for Webpack, and a link to a more detailed Babel overview/documentation.

Lines 22 – 43 ( could’ve been smooshed to one, adjust the test regex to satisfy both and keep the rest unchanged ) allows for webpack to recognize .scss and .css files imported within index.js (and/or its children) either via require or import './cool-design.scss'

Line 44 – 47 allows webpack to recognize SVG imports from index.js (and/or its children) and render it like a component (not directly though, but via different ways)

example:

import Logo from './logo.svg' return <Logo></Logo>;

This wont work as the loader we used svg-inline-loader just sends the <svg><svg> data to the named import.

Logo basically is “<svg>….</svg>”

So to render that we either use `dangerouslySetInnerHTML or a helper like react-svg-inline or a babel plugin

so we can just do this if using react-svg-inline

<SVGInline height="100%" width="100%" svg={Logo} />

or with babel just directly (see the plugin here)

<Logo></Logo>

or with dangerouslySetInnerHTML (please do take precautions for XSS attacks as using this will expose your app for external code modification)

​​​<​div dangerouslySetInnerHTML={{__html: Logo}}></div>

And the preliminary is set. We can now import write jSX, import CSS, SCSS, and SVG!

We proceed to loading the font!


Loading Fonts within SCSS url() import – Webpack

First is having to install file-loader to allow Webpack to recognize specific file types allowed by file-loader and this includes fonts! so install it via

npm install --save-dev file-loader

we modify our module object within webpack.config.js and add another child object inside the rules array.

{
    test: /\.(woff(2)?|ttf|eot)(\?v=\d+\.\d+\.\d+)?$/,

    exclude: /node_modules/,

    use: [

      {

        loader: "file-loader",

        options: {

          name: "[name].[ext]",

          outputPath: "./fonts/"

        }

      }

    ]
}

This rule tells webpack that any file imported within index.js and/or its children that satisfies the regex test, use file-loader and transfer it to the dist/ or build/ directory (whatever you name your output) with the filename structure of [name].[ext] (basically copy the filename of the matched file) and put it under dist/fonts/ or build/fonts/

example!

if and IF webpack finds custom.ttf being imported within index.scss or index.js (or its children), webpack will put this file custom.ttf under dist/fonts/

dist/
  -- fonts/
    -- custom.ttf
  index.js
  index.html

Now that webpack recognizes and transfers fonts, we have to fix one issue with scss!

By default, when scss encounters url() (like below) it looks over to the dist/ directory and not our current source directory. This is a problem because we can’t detect any font within scss if this is the case

(unless we manually transfer fonts to dist/ or import the font within a javascript file and reference the dist/ path within import in scss url() which isnt very elegant and isnt adviced)

Remember our dir structure

webpack.config.js
index.html
index.js
styles
   --- fonts
       --- custom_font.ttf
   --- index.scss

and given webpack encounters in index.scss:

@font-face{
  font-family: "Test Font",
  src: url(./fonts/custom_font.ttf)
}

webpack actually doesnt look at the dir structure above but our dist/ structure which currently looks like this

(index.html is there because I’m using a plugin called HtmlWebpackPlugin that copies my root html to dist/)

dist/
  index.js
  index.html

there’s no ​fonts/ directory here since scss didnt track the right path to the font file which results to webpack not seeing any file to transfer,

which finally makes url() fail!

So we use a helper resolve-url-loader that changes this behavior of looking into dist/ and instead look into our original source code.

We modify our scss loader within our module object inside webpack.config.js

so from

{

        test: /\.css$/,

        use: [

          // fallback to style-loader in development

          process.env.NODE_ENV !== "production"

            ? "style-loader"

            : MiniCssExtractPlugin.loader,

          "css-loader",

          "sass-loader"

        ]

},

to this new .scss loader

{

    test: /\.scss$/,

    use: [

      // fallback to style-loader in development

      process.env.NODE_ENV !== "production"

      ? "style-loader"

      : MiniCssExtractPlugin.loader,

      "css-loader",

      "resolve-url-loader",

      {
        loader: "sass-loader",
        options: {
          sourceMap: true,
          sourceMapContents: false
        }
       }
    ]
}

One take away from this is to always remember! that any loader after resolve-url-loader should implement a source-map!

More details for resolve-url-loader here

With this set up, when SCSS sees url() it searches through our source directory and if it does find it, transfers it to dist/fonts/ (depending on how you set up file-loader)

And that’s it! You can now import fonts!

This is how modules now look!
Screen Shot 2019-07-21 at 12.17.02 AM.png

Hope that helps! This took me 3 days! Hahahaha like what Sansa said “I’m a slow learner, but I learn”

P.S. Wrote this blog so I would remember the process for a long time!