15

Webpack + file-loader + sass-loader is having trouble resolving relative paths for CSS background images.

The compiled SCSS file contains a path to the background image that is relative to /dist/ instead of relative to the SCSS/CSS document. I researched this problem; sass-loader recommends using resolve-url-loader (with source maps). However, adding the resolve-url-loader made no difference to the compiled CSS.

I have been able to resolve the issue by setting the 'publicPath' to '../..' on the file-loader. Or by disabling the 'url' setting on the css-loader. Neither is a good solution and causes issues with copying files and referencing images via HTML or other sources.

The online examples of Webpack and CSS place the CSS and images in the same folder (often in the root). This is not an optimal choice for my webpack implementation. The concept of structuring files in subfolders seems like a fairly basic requirement. Is this simply the wrong approach?

Running Webpack ^3.5.1. Sass-loader ^6.0.6. File-loader ^0.11.2. Css-loader ^0.28.4.

File structure

example/
├── dist/
│   ├── assets
│   │   ├── media
│   │   │   └── logo.png
│   │   └── styles
│   │       ├── app.css
│   │       └── app.css.map
│   ├── index.html
│   └── app.bundle.js
└── src/
    ├── assets
    │   ├── media
    │   │   └── logo.png
    │   └── styles
    │       └── app.scss
    └── app.js

app.scss

body {
  background: url(../media/logo.png);
}

app.css

body {
  background: url(assets/media/logo.png); //This should be ../media/logo.png
}

app.js

require('./assets/styles/app.scss');

webpack.config.js

const path = require('path');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
module.exports = {
  entry: './src/app.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'app.bundle.js'
  },
  devtool: 'source-map',
  module: {
    loaders: [
      {
        test: /\.scss$/,
        use: ExtractTextPlugin.extract({
          use: [
            {
              loader: 'css-loader',
              options: {
                sourceMap: true
              }
            }, {
              loader: 'resolve-url-loader'
            }, {
              loader: 'sass-loader',
              options: {
                sourceMap: true
              }
            }
          ]
        })
      }, {
        test: /\.png$/,
        use: [
          {
            loader: 'file-loader',
            options: {
              name: 'assets/media/[name].[ext]'
            }
          }
        ]
      }
    ]
  },
  plugins: [
    new ExtractTextPlugin({
      filename: 'assets/styles/app.css'
    })
  ]
}
sepehr
  • 17,110
  • 7
  • 81
  • 119
Jason
  • 4,079
  • 4
  • 22
  • 32

2 Answers2

9

ExtractTextPlugin has a publicPath option that can resolve this issue.

{
  test: /\.scss$/,
  include: [
    path.resolve(__dirname, "src/assets/styles")
  ],
  use: ExtractTextPlugin.extract({
    publicPath: '../../',
    use: [
      {
        loader: 'css-loader',
        options: {
          sourceMap: true
        }
      }, {
        loader: 'sass-loader',
        options: {
          sourceMap: true
        }
      }
    ]
  })
}

Added include array to target files in a particular directory. Recommended for instances where all stylesheets are located in the same folder.

sepehr
  • 17,110
  • 7
  • 81
  • 119
Jason
  • 4,079
  • 4
  • 22
  • 32
  • 1
    In my case, `publicPath: './'` worked to resolve file path assets relative to the output directory. – James Feb 07 '19 at 17:50
  • As @James said, solution to this problem (in my case and I guess to others) was to add/edit output`s pablicPath option – Velaro Feb 03 '22 at 06:42
3

The reason why path in url(...) is incorrect is webpack generate path relative to output dir, not the output css file.

You can use css-url-relative-plugin to solve this problem, it will replace url(...) with correct relative path by re-calculate relationship of output css and assets file.

yibn2008
  • 43
  • 1
  • 7