How can I resolve background image URL inside sass file using webpack-4?

dedZombie picture dedZombie · Aug 13, 2019 · Viewed 15.2k times · Source

this is my first time writing here so please bear that in mind. I'm first time using webpack in a project and I have a problem where I'm trying to set a background image using background url() function in my sass file, something like this:

.img-container {
  background: url('../../img/homepage-login-prog.jpg') no-repeat center center fixed;
  background-size: cover;
}

My folder structure is:

- css
  | app.css
- dist (this folder is created after building for production)
- img
  | some-image.jpg
- js
  | app.js (entry point for webpack output, 
            this is where I import main.scss file)
- js-vendor
  | vendor.js (third-party libraries like bootstrap, jquery)
- sass  (this is where I keep all my sass files which get compiled using 
         webpack loaders)
  | components
    | greetings-section.scss
    | navbar.scss
  | main.scss
  | variables.scss
- template.html
- webpack.common.js
- webpack.dev.js
- webpack.prod.js

Because I have webpack configured both for development and production, I have separate files for both of them, where both extend on general (common) configuration file. That one looks like this:

const path = require("path");

module.exports = {
  entry:{
    main: "./js/app.js",
    vendor: "./js-vendor/vendor.js"
  },
  module: {
    rules: [
      {
        test: /\.html$/,
        use: ["html-loader"]
      },
      {
        test: /\.(svg|png|jp(e*)g|gif)$/,
        use: [{
          loader: "file-loader",
          options: {
            name: "[name].[hash].[ext]",
            outputPath: "img",
          }
        }]
      }
    ]
  }
};

My webpack dev configuration looks like this:

const common = require("./webpack.common");
const merge = require("webpack-merge");
const path = require("path");
var HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

module.exports = merge(common, {
  mode: "development",
  output: {
    filename: "[name].bundle.js",
    path: path.resolve(__dirname, "dist")
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: "./template.html"
    }),
    new MiniCssExtractPlugin({
      filename: "[name].[contentHash].min.css"
    }),
  ],
  module: {
    rules: [
      {
        test: /\.scss$/,
        use: [
          {
            loader: MiniCssExtractPlugin.loader,
            options: {
              publicPath: "../../"
            }
          },
          "css-loader",
          "sass-loader"
        ]
      }
    ]
  }
});

So, after webpack-dev-server starts, it pops an error:

ERROR in ./sass/main.scss (./node_modules/css-loader/dist/cjs.js!./node_modules/sass-loader/lib/loader.js!./sass/main.scss)
Module not found: Error: Can't resolve '../../img/homepage-login-prog.jpg' in 'C:\Users\...\...\...\sass'

As you can see I've tried setting a relative path using publicPath option in mini-css-extract-plugin, but it doesn't solve it for me. I've also been reading that other people have similar problems, but none of their solutions worked for me, so what I'm trying to ask is, does anyone know how can i configure webpack to recognize the url of a image inside my sass files, and properly bind it?

Sorry for the long post, any help would be greatly appreciated.

Answer

Aaron picture Aaron · Sep 27, 2019

Ran into a similar problem and what worked for me was to move my main.scss file into my sass folder. Before, it was in the src folder. The main.scss file imports all my other .scss files. Inside my index.js, which is the entry point, I import the main.scss file.

My file structure however is different than yours.

file structure

I have a dist folder for the output, and the src folder for input.

index.js:

import "./sass/main.scss";

main.scss:

@import "./footer";
@import "./header";
@import "./home";
@import "./navigation";
@import "./reset";
@import "./typography";
@import "./variables";

_header.scss:

  #hero {
  background-image: url("../assets/heroImage.jpg");
  background-size: cover;
  height: 100vh;
  width: 100vw;
  display: flex;
  justify-content: center;
  align-items: center;
}

I have been getting the same exact error because of the background-image: url() problem in my scss code. This import structure solved it for me.

For my webpack, I have a very similar config split into 3, common, dev, and prod. The only plugin I added is 'resolve-url-loader' in webpack.dev. I've tried the publicpath in miniCssExtractPlugin to no avail.

My common is almost the exact same as yours except for the entrypoint. Mine is index.js in the src folder and not app.js.

webpack.dev.js

const path = require("path");
const common = require("./webpack.common");
const merge = require("webpack-merge");
var HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = merge(common, {
  mode: "development",
  output: {
    filename: "[name].bundle.js",
    path: path.resolve(__dirname, "dist")
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: "./src/template.html"
    })
  ],
  module: {
    rules: [
      {
        test: /\.scss$/,
        use: [
          {
            loader: "style-loader"
          },
          {
            loader: "css-loader"
          },
          {
            loader: "resolve-url-loader"
          },
          {
            loader: "sass-loader"
          }
        ]
      }
    ]
  }
});

This solved it for me after countless hours. Let me know if you this helps in any way. Cheers.