Search code examples
webpackwebpack-4webpack-file-loaderwebpack-html-loader

Webpack wrong relative path in css and html


I have googled a lot, but I didn't find a clear solution to my question.

Both my xxx.sass and index.html will reference the same xxx.png from images folder. But the webpack resolved to the wrong relative path. I use

  • webpack ^4.41.2,
  • file-loader ^4.2.0 for xxx.png
  • mini-css-extract-plugin ^0.8.0 for xxx.css
  • html-loader ^0.5.5 for index.html

The source code:

xxx.sass

.banner
    background-image: url(../images/xxx.png)

index.html

<body>
    <h1>Hello World</h1>
    <img src="./images/xxx.png" />
</body>

My folder structure like this:

/dist
  /images
     xxx.png
  /css
     xxx.css
  index.js
  index.html

/src
   /css
      xxx.sass
   /images
      xxx.png
   index.js
   index.html

As you can see the relative path to xxx.png in index.html and xxx.sass should be different. But after I run webpack, the index.html and xxx.css have the same relative path to xxx.png, like:

index.html

<body>
   <h1>Hello World</h1>
   <img src="images/google_0877987d.png" />
   <script type="text/javascript" src="index_b88aa84a.js"></script>
</body>

xxx.css

.banner
{
    width:184px;
    height:60px;
    background-image:url(images/google_0877987d.png)
}

My webpack.config.js:

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

module.exports = {
    mode: 'production',
    entry: {
        index: './src/index.js',
    },
    output: {
        path: path.join(__dirname, 'dist'),
        filename: '[name]_[chunkhash:8].js',
    },
    module: {
        rules: [
            {
                test: /\.sass$/,
                use: [
                    MiniCssExtractPlugin.loader,
                    'css-loader',
                    'sass-loader',
                ],
            },
            {
                test: /\.(png|jpg|gif)$/,
                exclude: /node_modules/,
                use: [{
                    loader: 'file-loader',
                    options: {
                        name: '[path][name]_[contenthash:8].[ext]',
                        context: path.resolve(__dirname, 'src/'),
                        useRelativePaths: true,
                    },
                }],
            },
            {
                test: /\.html$/,
                use: [{
                    loader: 'html-loader',
                    options: {
                        root: path.resolve(__dirname, 'src/'),
                        attrs: ['img:src', 'link:href'],
                    },
                }],
            },
        ],
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: './src/index.html',
        }),
        new MiniCssExtractPlugin({
            filename: 'css/[name]_[contenthash:8].css',
        }),
    ],
}

Solution

  • Finally, I found the solution. Add the publicPath option for the MiniCssExtractPlugin.loader solve the problem.

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

    Although the problem is solved, I'm not 100% satisfied with this solution as the publicPath need to be updated accordingly if the folder structor is changed.

    It will be better if there's some nice solution to resolve the relative path according to the folder structure automatically.