'Webpack 5: file-loader generates a copy of fonts with hash-name

I cant figure out whats going on here. I use the file-loader to load the fonts of my application:

{
    test: /.(ttf|otf|eot|svg|woff(2)?)(\?[a-z0-9]+)?$/,
    use: [{
      loader: 'file-loader',
      options: {
        name: '[name].[ext]',
        outputPath: 'assets/fonts/',
        publicPath: '../fonts/'
      }
    }]
  },

The fonts are also generated in the structure I specified: dist/assets/fonts. outputPath: 'assets/fonts/' ... it seems to work correctly.

But: Webpack also packs the fonts with a hash as a name under /dist and sets the path in the CSS file to these files.

@font-face{font-family:"PaulGrotesk";font-style:normal;font-weight:400;src:url(../../d9bc4e30281429c0cddd.eot);

strange font files

What is happening here? How can I prevent the additional files from being generated and used as a path?

I am using

  • webpack: ^5.47.1
  • webpack-cli: ^4.7.2

My webpack.config:

const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {

  entry: './src/index.js',

  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'assets/js/bundle_v1.0.0.js'
  },

  module: {

    rules: [

      {
        test: /\.js?$/,
        exclude: /node_modules/,
        loader: 'babel-loader',
        include: path.join(__dirname, 'index'),
        options: {
          presets: ['@babel/preset-env']
        }
      },

      {
        test: /.(ttf|otf|eot|svg|woff(2)?)(\?[a-z0-9]+)?$/,
        use: [{
          loader: 'file-loader',
          options: {
            name: '[name].[ext]',
            outputPath: 'assets/fonts/',
            publicPath: '../fonts/'
          }
        }]
      },

      {
        test: /\.scss$/,
        use: [
          MiniCssExtractPlugin.loader,
          {
            loader: 'css-loader'
          },
          {
            loader: 'sass-loader',
            options: {
              sourceMap: true,
            }
          }
        ]
      },

      {
        test: /\.css$/i,
        use: [MiniCssExtractPlugin.loader, 'css-loader'],
      },

    ]
  },
  resolve: {
    alias: {
      '@': path.resolve(__dirname, 'node_modules'),
      /* https://webpack.js.org/configuration/resolve/ */
    },
  },
  plugins: [

    new MiniCssExtractPlugin({
      filename: 'assets/css/bundle_v1.0.0.css'
    }),

    new HtmlWebpackPlugin({
      inject: 'body',
      template: './src/index.html',
      filename: 'index.html',
    })

  ]

};


Solution 1:[1]

I had the exact same issue as the OP (@toge) having migrated from Webpack 4 to 5. Whilst I was looking to implement the solution posted by @seanbun, I came across the following setup for the new Webpack 5 asset modules config that allowed me to get both my previous file-loader svgs and font files to generate correctly without duplication in the root output folder:

{
    test: /\.(woff(2)?|ttf|eot)$/,
    type: 'asset/resource',
    generator: {
        filename: './fonts/[name][ext]',
    },
},

This tells webpack to handle it via the asset modules config but output to a custom filepath, similar to how the file-loader config used to work.

This setup also removed my need to downgrade css-loader too which was an added benefit

Solution 2:[2]

Apparently, Webpack 5 has a Asset Modules function which allows one to use asset files (fonts, icons, etc) without configuring additional loaders i.e. file-loader, url-loader and etc. The Asset Modules provides four types to handle different type of asset. You can find more details at https://webpack.js.org/guides/asset-modules/.

So, when you add below rule for font files, the files are acutally handled by the Asset Modules as well as the file-loader.

    {
        test: /.(ttf|otf|eot|svg|woff(2)?)(\?[a-z0-9]+)?$/,
        use: [{
          loader: 'file-loader',
          options: {
            name: '[name].[ext]',
            outputPath: 'assets/fonts/',
            publicPath: '../fonts/'
          }
        }]
    }

In this case, we want to use our own file-loader and disable the Asset Module. We can set the type to 'javascript/auto'. This would follow your path setting and keep the font files in the assets/fonts folder.

    {
        test: /.(ttf|otf|eot|svg|woff(2)?)(\?[a-z0-9]+)?$/,
        **type: "javascript/auto",**
        use: [{
          loader: 'file-loader',
          options: {
            name: '[name].[ext]',
            outputPath: 'assets/fonts/',
            publicPath: '../fonts/'
          }
        }]
    }

P.S I was using css-loader 6.2.0 and had an error - url.replace is not a function. I downgraded to css-loader 5.2.7 to resolve the issue.

Solution 3:[3]

Loaders are applied from last to first. So my guess is that placing the file loader below the css/sass loaders could fix the issue.

The fonts may not be registered this way when needed by the css loader and therefore loaded twice.

Solution 4:[4]

I already had my font files in a serveable location so I didn't need to copy the assets. (I'm confused why webpack did this by default). My solution was to ignore those files by:

    new webpack.IgnorePlugin({
      resourceRegExp: /\.(woff(2)?|ttf|eot|svg)$/
    }),

Solution 5:[5]

I had similar problem but in my case problem was solved by "type:'asset/inline'"

   {
      test: /\.(woff|woff2|ttf|otf|eot)$/,
      type:'asset/inline',
      exclude: /node_modules/,
      loader: 'file-loader',
      options: {          
        esModule: false,
        name: '[name].[ext]',
        outputPath: '../assets/fonts/',
        publicPath: '../assets/fonts/'
      }
    },

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2
Solution 3 matthiasgiger
Solution 4 Joey Carlisle
Solution 5 Votech