Configure webpack to output images/fonts in a separate subfolders

Webpack

Webpack Problem Overview


I managed configure webpack to output CSS and JS into respective sub-directories, i.e. public/asests/css and public/assets/js. However, I don't know how to do the same for images and fonts.

In other words, I want to output images in public/assets/images folder and fonts into public/assets/fonts folder.

Here's my webpack config file:

var path = require('path');
var ExtractCSS = require('extract-text-webpack-plugin');

module.exports = {
    context: path.resolve('private/js'),
    resolve: ['', '.js', '.jsx', '.es6', '.json'],
    entry: {
        homepage: './homepage'
    },
    output: {
        path: path.resolve('public/assets'),
        publicPath: '/public/assets/',
        filename: 'js/[name].js'
    },
    plugins: [
        new ExtractCSS('css/[name].css')
    ],
    devServer: {
        contentBase: 'public'
    },
    module: {
        loaders: [
            {
                test: /\.(es6|js|jsx)$/,
                exclude: /node_modules/,
                loader: 'babel-loader'
            },
            {
                test: /\.css$/,
                exclude: /node_modules/,
                loader: ExtractCSS.extract('style-loader', 'css-loader')
            },
            {
                test: /\.less$/,
                exclude: /node_modules/,
                loader: ExtractCSS.extract('style-loader', 'css-loader!less-loader')
            },
            {
                test: /\.(jpg|jpeg|gif|png|woff|woff2|eot|ttf|svg)$/,
                exclude: /node_modules/,
                loader: 'url-loader?limit=1024'
            }
        ]
    }
}

Webpack Solutions


Solution 1 - Webpack

I could figure this out by referring to [url-loader][1] & [file-loader][2] documentation on GitHub.

All, I needed to do was to add a name query-string parameter in loader to specify full path. I also learned that you can specify how files should be named in output location.

{
    test: /\.(jpg|jpeg|gif|png)$/,
    exclude: /node_modules/,
    loader:'url-loader?limit=1024&name=images/[name].[ext]'
},
{
    test: /\.(woff|woff2|eot|ttf|svg)$/,
    exclude: /node_modules/,
    loader: 'url-loader?limit=1024&name=fonts/[name].[ext]'
}

[1]: https://github.com/webpack/url-loader "url-loader" [2]: https://github.com/webpack/file-loader "file-loader"

Solution 2 - Webpack

{ 
    test: /\.(ttf|eot|svg|woff2?)(\?v=[a-z0-9=\.]+)?$/i,
    include: folders.npm,
    loader: 'file?name=fonts/[name].[ext]'
},
{
    test: /\.(jpe?g|png|gif|svg|ico)$/i,
    include: folders.src,
    loaders: [
        'file?name=images/[sha512:hash:base64:7].[ext]',
        'image-webpack?progressive=true&optimizationLevel=7&interlaced=true'
    ]
}

This is what I use in production. I often come across situation where *.svg pictures are used and SVG fonts for IE fallback. Here I assume font are always inside node_modules. I have also seen devs doing test: /fonts\/[w+].(svg|eot ....).

Solution 3 - Webpack

Webpack 5 case-

Keeping it here just in case someone is looking for a Webpack 5 solution to the requirement of outputting image/font assets into separate directories into the output path.

Webpack 5 replaces raw-loader, url-loader and file-loader with Assets Module and it has 4 new modules to handle assets. Read in more details here - https://webpack.js.org/guides/asset-modules/

To our problem statement, the following configuration outputs all fonts/svgs into the directory fonts. But if certain files are less than 8KB, it throws them inline.

  {
    test: /\.(woff(2)?|eot|ttf|otf|svg|)$/,
    type: 'asset',   // <-- Assets module - asset
    parser: {
      dataUrlCondition: {
        maxSize: 8 * 1024 // 8kb
      }
    },
    generator: {  //If emitting file, the file path is
      filename: 'fonts/[hash][ext][query]'
    }
  }

Similarly we could do the same for images -

  {
    test: /\.(?:ico|gif|png|jpg|jpeg)$/i,
    type: 'asset/resource',  //<-- Assets module - asset/resource
    generator: {
      filename: 'images/[hash][ext][query]'
    }
  }

Outputs all images to images directory irrespective of file size.

Solution 4 - Webpack

I was able to solve this with file-loader If you're using the Webpack 4, you would use use instead of loader.

{
    test: /\.(woff|woff2|eot|ttf|otf)$/,
    use: ['file-loader?name=fonts/[name].[ext]']
}

Solution 5 - Webpack

Configure file/url-loader output path & file name

You can read in the official file-loader documentation: there's a name parameter in the configuration object, where a path can be specified.

This would do the job:

                         // ...
        {
            test    : /\.(woff|woff2|ttf|eot)$/,
            loader  : 'url-loader',
            options: {
                limit : 70000,
                name: 'fonts/[name].[ext]'
            }
        },
                         // ...

I suggest you to take a look at the available placeholders, to know what can be used in a file name.

Loaders usually have a configuration object available, even if you can pass the options like other devs suggested ( loader:'url-loader?limit=1024&name=images/[name].[ext]') I'd prefer using a plain object configuration, to have something more readable, and easier to configure/debug as well

Output path

There's an outputPath (optional) configuration param, in case one wants to override the default output path, which in any case must be configured (check the docs). I'm pretty sure that webpack for it's loaders will use the default output dir. In my project I've got this:

                         // ...
output: {
    path: helpers.publicPath(),      // local output path: <project-root>/public
    publicPath,                      // same path on the server: /myapp/public
    filename: 'js/[name].bundle.js', // default output configuration for js
    chunkFilename: 'js/[name].chunk.js'
},
                         // ...

Note

I'm not sure that you need to exclude the node_modules folder: in case that libraries like font-awesome or material-icons are used, then you need node_modules to be included. Webpack will automatically leave out files that are not useful to make the project build & run. I would only exclude folders containing old legacy scripts that are not using imports and stuff like that, and I don't want to be considered by webpack in any case.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionPratik MehtaView Question on Stackoverflow
Solution 1 - WebpackPratik MehtaView Answer on Stackoverflow
Solution 2 - WebpackgcerarView Answer on Stackoverflow
Solution 3 - WebpackSouvik GhoshView Answer on Stackoverflow
Solution 4 - WebpackRustyView Answer on Stackoverflow
Solution 5 - Webpackfunder7View Answer on Stackoverflow