Unable to resolve absolute url() paths for background images in CSS with Webpack

11,668

Solution 1

One possible solution that I could come up with was the following:

Use resolve-url-loader (immediately after sass-loader):

style!${CSS_LOADER}!autoprefixer!resolve-url!${SASS_LOADER}

Then, define a resolve.alias for static images:

resolve: {
  alias: {
    images: path.join(__dirname, 'public/images')
  }
}

And then in the CSS, you can point to the images like so:

:local .SomeClass {
  background: url('images/bg.png');
}

Depending on your URL structure, you may also need to tweak the url-loader name param:

{
  test: /\.(jpe?g|png|gif|svg)$/,
  loader: 'url-loader?limit=10000&name=images/[name].[ext]'
}

So I don't know if there's a cleaner way to solve this issue, but that's the best I could come up with so far. I'd welcome any feedback or alternatives.

Thanks!

UPDATE 6/30/2016

There's a couple PR's out that would address this issue, but the maintainer prefers the solution to be in the CSS AST rather than in the CSS loader...

https://github.com/webpack/style-loader/pull/124 https://github.com/webpack/style-loader/pull/96

Here's hoping a real fix happens soon...

Solution 2

With webpack 2:

In your .scss files use ~ before the path.

.yourClass {
      background: url('~img/wallpaper.png');
}

Make use of the resolve root from webpack, add this to your webpack.config.js:

resolve: {
        modules: [
            path.resolve(root),
            'node_modules'
        ]
    },

It should work also for the @import, eg @import "~otherfile.scss"

Share:
11,668
Muers
Author by

Muers

Updated on June 04, 2022

Comments

  • Muers
    Muers almost 2 years

    I have the following Webpack config (roughly, it has been simplified for this post):

    const rootPublic = path.resolve('./public');
    const LOCAL_IDENT_NAME = 'localIdentName=[name]_[local]_[hash:base64:5]';
    const CSS_LOADER = `css?sourceMap&${LOCAL_IDENT_NAME}&root=${rootPublic}`;
    const SASS_LOADER = 'sass?sourceMap&includePaths[]=' + path.resolve(__dirname, './src/styles');
    
    // ... loaders:
    
    loaders: [
      {
        test: /\.(jpe?g|png|gif|svg)$/,
        loader: 'url-loader?limit=10000&name=[path][name].[ext]'
      },
      {
        test: /\.scss$/,
        loader: config.DEVELOPMENT_MODE ? `style!${CSS_LOADER}!autoprefixer!${SASS_LOADER}`
                    : ExtractTextPlugin.extract('style', `${CSS_LOADER}!autoprefixer!${SASS_LOADER}`)
      }, // ...
    ]
    

    Now this works perfectly fine for images referenced normally in scss files:

    .some-static-class {
      background: url('/images/bg.png');
    }
    

    However, it does not work when using the :local directive:

    :local .SomeClass {
      background: url('/images/bg.png');
    }
    

    And I think that's because root is defined for the CSS loader. I get a build error: Module not found: Error: Cannot resolve 'file' or 'directory' ./../../../../../../../../images/bg.gif

    If instead I remove root from the css-loader config, then it builds fine but then the path "looks" correct in Chrome's inspector, but when you actually open the link in a new tab, it points to: chrome-devtools://devtools/bundled/"/images/bg.png" which obviously doesn't resolve correctly.

    I'm not sure if the problem is with the url-loader config, or what's going on exactly.

    I played around with different webpack configs to specify the resolve.root, resolve.modulesDirectories, etc but totally not sure if they're having any affect or if I'm just going about it completely wrong. I also came across resolve-url-loader but not sure if that's even what I need at all.

    Any ideas? MTIA!

    UPDATE

    I should note, that it works fine in Safari, but not in Chrome. So it seems like a Chrome-specific bug, but it's not practical to have to do all our development in Safari.

    I also came across, vue-style-loader, which is a fork of style-loader that claims to fix this issue, but the way it fixes it is by relying on a hacky deprecated escape/unescape method.