Relative CSS urls in Webpack

Jason picture Jason · Aug 9, 2017 · Viewed 17.2k times · Source

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'
    })
  ]
}

Answer

Jason picture Jason · Aug 11, 2017

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.