Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Images in CSS (file-loader)

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $12.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

Most of the CSS comes from our base layout: base.html.twig. On top, there's a link tag to assets/css/main.css:

... lines 1 - 2
<head>
... lines 4 - 10
{% block stylesheets %}
<link href="https://netdna.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" />
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" integrity="sha384-wvfXpqpZZVQGK6TAh5PVlGOfQNHSoD2xbE+QkPxCAFlNEevoEH3Sl0sibVcOQVnN" crossorigin="anonymous" />
<link href="{{ asset('assets/css/main.css') }}" rel="stylesheet" />
{% endblock %}
... lines 16 - 17
</head>
... lines 19 - 107

But, to keep with our new system, instead of adding this link tag manually, I want to require that CSS from JavaScript. For global CSS, I'll include it from my global JavaScript: layout.js.

In other words, remove that link tag!

... lines 1 - 2
<head>
... lines 4 - 10
{% block stylesheets %}
... lines 12 - 13
<link href="{{ asset('assets/css/main.css') }}" rel="stylesheet" />
{% endblock %}
... lines 16 - 17
</head>
... lines 19 - 107

Then, in the source layout.js file, add require('../css/main.css'):

... lines 1 - 6
require('../css/main.css');
... lines 8 - 12

And... that should just work!

Webpack Follows Images in CSS

So... refresh! And as promised... woh! Nevermind! It does not work... it looks terrible! What happened?

Our console shows an error:

Module parse failed, dummbell-mini.png, unexpected character

Huh. Over in the terminal, the watch script has a similar message... and it looks like it happens when Webpack reads main.css.

Hmm. Open main.css. Ah! There's the image: it's a background image inside our CSS!

... lines 1 - 7
.mini-dumbbell {
... lines 9 - 10
background: url('../images/dumbbell-mini.png') center center no-repeat;
... line 12
}
... lines 14 - 78

This, is very interesting. When we tell Webpack to load a CSS file, it actually parses the background images and - basically - requires them! It does the same with fonts and also finds and requires any @import calls.

So... why is this failing? Well, just like with CSS, a .png file is not JavaScript... so Webpack has no idea what to do with it!

Using file-loader

What's the fix? We need a loader capable of understanding image files.

Head over to webpack.js.org, click on Guides, Asset Management and then Loading Images.

Ah, the file-loader! It has one simple job: move any files it processes into the build/ directory. When it does that, internally, it will return the filename to that new file... which Webpack will use to re-rewrite the background-image path in our CSS to point to it. It's pretty amazing.

Install it first: copy the name of the module and then, in your open terminal, run:

yarn add file-loader@5 --dev

Back in webpack.config.js, we need to add a third loader. Copy the css-loader config. This time, for test, in the docs, it basically looks for any image file. I'll paste something similar that includes even more image filenames. And for use, pass these to file-loader:

... lines 1 - 3
module.exports = {
... lines 5 - 13
module: {
rules: [
... lines 16 - 32
{
test: /\.(png|jpg|jpeg|gif|ico|svg)$/,
use: [
'file-loader'
]
}
]
},
... lines 41 - 46
};

Before you do anything else, head over, and restart Webpack!

./node_modules/.bin/webpack --watch

Ah, look at the output! Not only did it write layout.js, rep_log.js and login.js files, it output a .png file... with a long funny name. You can see it in the build/ directory. This is mini-dumbbell.png. Its name is a hash of its contents - more on that later.

Configuring publicPath

Let's try it! Refresh! The image should show up on this first menu item... but it's not there! And my console has a 404 for the image! What's up?!

Inspect the element. Ok, the final CSS from Webpack changed the background-image to point to the new filename. Let's open that in a new tab.

Ah! The filename is right, but it's missing the build/ directory prefix!

Webpack is almost doing everything correctly: it moves the file into build/ and even updates the CSS to point to that filename.

Open webpack.config.js. Yes, we did tell Webpack to put everything into web/build. But, Webpack doesn't actually know what the public path is to files in this directory. I mean, it doesn't know that web/ is our document root, and so it doesn't know that these files are accessible via build/ then the filename. Nope, this is something we need to tell Webpack, so that it can create the correct paths.

How? Under output, set publicPath to /build/:

... lines 1 - 3
module.exports = {
... lines 5 - 9
output: {
... lines 11 - 12
publicPath: '/build/'
},
... lines 15 - 47
};

Find you terminal and restart Webpack:

./node_modules/.bin/webpack --watch

Everything looks the same here... but when we refresh and open the menu... there it is! Our little icon. The background-image now point to /build/ the filename.

Guys, this is another monumental step forward! Now, as long as we correctly reference image paths in CSS, Webpack will make sure those images are available in build/ and that their paths in the final CSS are correct. We focus on our source files, and Webpack takes care of the rest.

Even better, if you make a mistake - like a typo - you'll actually see a Webpack build error. There's no way to accidentally have a broken link.

Leave a comment!

10
Login or Register to join the conversation

Hey,

Just ran into a problem where (using webpack 4) my url('../images/dumbbell-mini.png') was replaced with url([object Module]).

I fixed it with esModule to false in my file loader options


    {
        test: /\.(png|jpg|jpeg|gif|ico|svg)$/,
        use: [
          {
            loader: 'file-loader',
            options: {
              esModule: false,
            }
          }
        ]
      }```


Hope this helps
1 Reply
Default user avatar
Default user avatar Marcos Lorente | julien_bonnier | posted 3 years ago | edited

Hey Julien, I'm trying to fix as you mentioned but still not working.

url([object Module])

Reply

Hey Marcos,

I haven't tried the solution yet... but did you restarted your webpack encore after making those changes? Encore requires to be restarted on any changes in webpack.config.js file.

I hope this helps!

Cheers!

Reply

Hey Julien,

Thank you for sharing this workaround with others!

Cheers!

Reply
Ian M. Avatar
Ian M. Avatar Ian M. | posted 2 years ago | edited

It's happy with yarn add file-loader@5 --dev but not yarn add file-loader --dev which causes ERROR in ./web/assets/images/dumbbell-mini.png<br />Module build failed: TypeError [ERR_INVALID_ARG_TYPE]: The "from" argument must be of type string. Received type undefined

Reply

Thanks Ian M.! We'll add a note about using v5 if you want to follow the tutorial :).

Reply
Dan_M Avatar

Ryan,

This is really good stuff, but I've hit a snag! When I code along with you on this episode, I don't get the 404 error on mini-dumbbell. Instead I get no image and no error. When I inspect the element, I see it has this style:
background: url([object Object]) center center no repeat;

Changing the publicPath as you showed did not help.

What in the world?!!

Reply

Yo Dan_M!

Woooh! That's totally weird! But, this might be the problem: https://github.com/webpack-contrib/html-loader/issues/140

If you lock your version of file-loader 0.11.2, does that help? I've not seen this issue before, but it's sort weird, that I searched for a bug first :).

If that doesn't help, can you post your webpack.config.js and package.json files?

Cheers!

Reply
Dan_M Avatar
Dan_M Avatar Dan_M | weaverryan | posted 5 years ago | edited

weaverryan,

OK, this is really weird! I downgraded to 0.11.2 and it worked. Then I upgraded to 1.1.4, and...it STILL worked.

I don't know what version of file-loader I had originally, but now it just works and I can't break it. Go figure!

Reply

Oh man, super weird. Honestly, the package version resolution in npm/yarn still doesn't seem to be as strong as Composer. I *still* sometimes do an rm -rf node_modules to reset things!

Anyways, happy it's working now :)

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

This tutorial explains the concepts of an old version of Webpack using an old version of Symfony. The most important concepts are still the same, but you should expect significant differences in new versions.

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.2.0",
        "symfony/symfony": "3.3.*", // v3.3.16
        "twig/twig": "2.10.*", // v2.10.0
        "doctrine/orm": "^2.5", // v2.7.0
        "doctrine/doctrine-bundle": "^1.6", // 1.10.3
        "doctrine/doctrine-cache-bundle": "^1.2", // 1.3.5
        "symfony/swiftmailer-bundle": "^2.3", // v2.6.3
        "symfony/monolog-bundle": "^2.8", // v2.12.1
        "symfony/polyfill-apcu": "^1.0", // v1.4.0
        "sensio/distribution-bundle": "^5.0", // v5.0.22
        "sensio/framework-extra-bundle": "^3.0.2", // v3.0.26
        "incenteev/composer-parameter-handler": "^2.0", // v2.1.2
        "friendsofsymfony/user-bundle": "^2.0", // v2.1.2
        "doctrine/doctrine-fixtures-bundle": "~2.3", // v2.4.1
        "doctrine/doctrine-migrations-bundle": "^1.2", // v1.3.2
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "friendsofsymfony/jsrouting-bundle": "^1.6" // 1.6.0
    },
    "require-dev": {
        "sensio/generator-bundle": "^3.0", // v3.1.6
        "symfony/phpunit-bridge": "^3.0" // v3.3.5
    }
}

What JavaScript libraries does this tutorial use?

// package.json
{
    "dependencies": [],
    "devDependencies": {
        "babel-core": "^6.25.0", // 6.25.0
        "babel-loader": "^7.1.1", // 7.1.1
        "babel-plugin-syntax-dynamic-import": "^6.18.0", // 6.18.0
        "babel-preset-env": "^1.6.0", // 1.6.0
        "bootstrap-sass": "^3.3.7", // 3.3.7
        "clean-webpack-plugin": "^0.1.16", // 0.1.16
        "copy-webpack-plugin": "^4.0.1", // 4.0.1
        "core-js": "^2.4.1", // 2.4.1
        "css-loader": "^0.28.4", // 0.28.4
        "extract-text-webpack-plugin": "^3.0.0", // 3.0.0
        "file-loader": "^0.11.2", // 0.11.2
        "font-awesome": "^4.7.0", // 4.7.0
        "jquery": "^3.2.1", // 3.2.1
        "lodash": "^4.17.4", // 4.17.4
        "node-sass": "^4.5.3", // 4.5.3
        "resolve-url-loader": "^2.1.0", // 2.1.0
        "sass-loader": "^6.0.6", // 6.0.6
        "style-loader": "^0.18.2", // 0.18.2
        "sweetalert2": "^6.6.6", // 6.6.6
        "webpack": "^3.4.1", // 3.4.1
        "webpack-chunk-hash": "^0.4.0", // 0.4.0
        "webpack-dev-server": "^2.6.1", // 2.6.1
        "webpack-manifest-plugin": "^1.2.1" // 1.2.1
    }
}
userVoice