Search code examples
javascriptwebpackcss-loadersass-loaderwebpack-file-loader

Webpack url() path resolution with css-loader


I am developing a site and using webpack for obvious reasons. The problem I am having is with path resolution for images which are imported into my project via my SCSS files. The issue is that css-loader isn't resolving the correct path. What seems to be happening is the following:

If I allow css-loader to handle the url() imports (leaving the url option to true) it rewrites the file path relative to the output directory specified in ExtractCSSChunksPlugin(), for example:

url('../img/an-image.jpg') should be rewritten to url('http://localhost:3000/assets/img/an-image.jpg'), however, what is actually being outputted is url('http://localhost:3000/assets/css/assets/img/an-image.jpg').

If I change it to false the correct path is resolved but the file-loader isn't able to find the images and then emit them.

I know that the images are being outputted when the css-loader is handling url resolution as I can see the emitted message when the bundle is compiled -- it does not fail.

I can also get the images to display if I manually add import calls to them in the JS entry point, set in the entry: field, and then call the absolute path in SCSS. But this is not desirable as it becomes tedious with the growing project.

I have tried to use resolve-url-loader and changing multiple settings but I just can't seem to get this to work.

I have also tried using the resolve: { alias: { Images: path.resolve(__dirname, 'src/assets/img/' } } option provided by webpack and then calling url('~Images/an-image.jpg') in my SCSS but it just reproduces the same results.

So, overall, my issue is that I need to be able to use relative paths in my SCSS and then have them rewritten to the correct path by one of my loaders.

My current webpack config (outputting the files with file loader but prepending assets/css/ to the start of the url) is as follows:

"use strict";
const webpack = require('webpack');
const merge = require('webpack-merge');
const common = require('./webpack.common');
const ExtractCSSChunksPlugin = require('extract-css-chunks-webpack-plugin');

module.exports = merge(common, {
    mode: 'development',
    devtool: 'inline-source-map',
    entry: [
        'webpack-hot-middleware/client',
    ],
    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: /(node_modules|bower_components)/,
                use: {
                  loader: 'babel-loader',
                  options: {
                    presets: ['@babel/preset-env'],
                  }
                }
            },
            {
                test: /\.scss$/,
                use: [
                    {
                        loader: ExtractCSSChunksPlugin.loader,
                        options: {
                            hot: true,
                        }
                    },
                    {
                        loader: 'css-loader',
                        options: {
                            sourceMap: true,
                        }
                    },
                    {
                        loader: 'sass-loader',
                        options: {
                            sourceMap: true,
                          }
                    }
                ]
            },
            {
                test: /\.html$/,
                use:['html-loader']
            },
            {
                test:/\.(svg|jpg|png|gif)$/,
                use: [{
                    loader:'file-loader',
                    options: {
                        publicPath: 'assets/img',
                        outputPath: 'assets/img',
                        name: '[name].[ext]',
                        esModule: false
                    }
                }],
            },
        ]
    },
    plugins: [
        new webpack.HotModuleReplacementPlugin(),
        new ExtractCSSChunksPlugin({
            filename: 'assets/css/[name].css',
            chunkFilename: 'assets/css/[id].css',
        }),
    ]
});

Thank you in advance.


Solution

  • Ok, so it seems I have fixed the issue by resolving the publicPath set in the file loader config field: publicPath: path.resolve(__dirname, '/assets/img').

    My config is now:

    "use strict";
    const webpack = require('webpack');
    const merge = require('webpack-merge');
    const common = require('./webpack.common');
    const path = require('path');
    const ExtractCSSChunksPlugin = require('extract-css-chunks-webpack-plugin');
    
    module.exports = merge(common, {
        mode: 'development',
        devtool: 'inline-source-map',
        entry: [
            'webpack-hot-middleware/client',
        ],
        module: {
            rules: [
                {
                    test: /\.js$/,
                    exclude: /(node_modules|bower_components)/,
                    use: {
                      loader: 'babel-loader',
                      options: {
                        presets: ['@babel/preset-env'],
                      }
                    }
                },
                {
                    test: /\.scss$/,
                    use: [
                        {
                            loader: ExtractCSSChunksPlugin.loader,
                            options: {
                                hot: true,
                            }
                        },
                        {
                            loader: 'css-loader',
                            options: {
                                sourceMap: true,
                            }
                        },
                        {
                            loader: 'sass-loader',
                            options: {
                                sourceMap: true,
                              }
                        }
                    ]
                },
                {
                    test: /\.html$/,
                    use:['html-loader']
                },
                {
                    test:/\.(svg|jpg|png|gif)$/,
                    use: [{
                        loader:'file-loader',
                        options: {
                            publicPath: path.resolve(__dirname, '/assets/img'),
                            outputPath: 'assets/img',
                            name: '[name].[ext]',
                            esModule: false
                        }
                    }],
                },
            ]
        },
        plugins: [
            new webpack.HotModuleReplacementPlugin(),
            new ExtractCSSChunksPlugin({
                filename: 'assets/css/[name].css',
                chunkFilename: 'assets/css/[id].css',
            }),
        ]
    });