Buy Access to Course
13.

Images in CSS (file-loader)

Share this awesome video!

|

Keep on Learning!

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:

107 lines | app/Resources/views/base.html.twig
// ... 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!

107 lines | app/Resources/views/base.html.twig
// ... 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'):

12 lines | web/assets/js/layout.js
// ... 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!

78 lines | web/assets/css/main.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:

48 lines | webpack.config.js
// ... 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/:

49 lines | webpack.config.js
// ... 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.