Buy

When you type a really long username on the login page, we give you a fancy error. Hey! Stop making things so long!

Open this pages's template: login.html.twig. We include build/login.js and we also include a CSS file: login.css:

72 lines app/Resources/FOSUserBundle/views/Security/login.html.twig
... lines 1 - 4
{% block stylesheets %}
... lines 6 - 7
<link rel="stylesheet" href="{{ asset('assets/css/login.css') }}" />
{% endblock %}
{% block javascripts %}
... lines 12 - 13
<script src="{{ asset('build/login.js') }}"></script>
{% endblock %}
... lines 16 - 72

That lives at web/assets/css/login.css. If we forgot to include this, the page would look terrible. But, it's even more important than that! Our JavaScript adds this error div to the page. And that element is styled thanks to code in login.css.

What I'm saying is: our "login app" depends on the login.css file... it's as much of a dependency as any JavaScript files! But right now, we still need to remember to include login.css. I want to stop doing that!

Instead, let's treat login.css like any other dependency: by requiring it. I know it sounds crazy, but, at the top, add require('../css/login.css'):

24 lines web/assets/js/login.js
... lines 1 - 2
const $ = require('jquery');
require('../css/login.css');
... lines 5 - 24

What!? This is outrageous!!? Requiring a CSS file from JavaScript doesn't even make sense! Or... does it?

The css-loader

Go over to your terminal and find the watch tab. It's so angry!

Module parse failed: login.css unexpected token.

It tries to open login.css... but surprise! It's not a JavaScript file, so Webpack explodes! But, the error says a bit more:

You may need an appropriate loader to handle this file type.

This is where Webpack becomes incredible. Webpack can load more than mere JavaScript files. It can load anything! It can load CSS files, JSON files, HTML files, image files from your vacation! Heck, you an even load markdown files or Twig templates! Webpack can load anything... as long as you have a loader that can read that file type.

In your browser, google for css-loader and open its GitHub page. Copy the name of the library: let's get it installed! In your open terminal, run:

yarn add css-loader style-loader --dev

We'll need style-loader in a minute.

Ok, when you try to require something that is not a JavaScript file, the job of the loader is to somehow convert that file to JavaScript. I realize that still doesn't make sense - "Convert a CSS file to JavaScript?" - but stay with me!

Back in webpack.config.js, add a second rule. This time with test: /\.css$/ and use: ['css-loader']:

41 lines webpack.config.js
... lines 1 - 3
module.exports = {
... lines 5 - 13
module: {
rules: [
... lines 16 - 25
{
test: /\.css$/,
use: [
'css-loader'
]
}
]
},
... lines 34 - 39
};

This is just a shorter syntax than the loader and options format we used above... but it means the same thing.

To see what this actually does, open login.js and assign a new css variable to the require() call. Then, console.log(css):

25 lines web/assets/js/login.js
... lines 1 - 3
const css = require('../css/login.css');
console.log(css);
... lines 6 - 25

Because, if I'm telling you that somehow Webpack can require css files... well... I'm curious. What does that mean? What will the require function actually return?

Let's find out! Go over to your watch terminal, hit Control+C, and re-run webpack:

./node_modules/.bin/webpack --watch

Hey! No errors! Find the login page and refresh. First... yes, the page looks really ugly. This should not work yet. But check out the console: our css variable is a JavaScript array, and it includes all the css on one of its keys. Weird! The css-loader converted our CSS into a JavaScript object... which is interesting... but still not useful.

Adding style-loader

But now, go back to webpack.config.js and, before css-loader, add style-loader:

42 lines webpack.config.js
... lines 1 - 3
module.exports = {
... lines 5 - 13
module: {
rules: [
... lines 16 - 25
{
test: /\.css$/,
use: [
'style-loader',
'css-loader',
]
}
]
},
... lines 35 - 40
};

Remember how I said you can have multiple loaders? In this case, we're using css-loader and style-loader. When you activate loaders with the inline syntax we saw earlier, you read the loaders from right to left. Here, it's similar: read the loaders from bottom to top.

So, this says: when we require a file ending in .css, first send its contents through css-loader - which converts it to that JavaScript object - and then send that through style-loader.

And what does style-loader do? Let's find out! Go back to your watch tab and restart webpack:

./node_modules/.bin/webpack --watch

Ok, find your browser and refresh.

What!? It works! Our CSS is back... even though we don't have a link tag on the page! The console logs an empty value.

But if you inspect the page and look in the head tag, there's a surprise: a new style element with all our CSS. Yep! the style-loader's job is to package the CSS inside login.js, along with some extra JavaScript that injects that CSS onto the page in a style tag. It's incredible.

But, it does have one downside. Refresh and watch closely: the page is ugly for just a moment, before the JavaScript loads. Then, it looks fine. But, for production, that's a problem. Yep, style-loader is a development-only tool. But, I promise to show you a proper, great solution for production later.

For now, celebrate! We can require CSS from our JavaScript! We are truly creating self-contained JavaScript apps.

Requiring CSS from node_modules

Remove console.log and the const css = part:

24 lines web/assets/js/login.js
... lines 1 - 3
require('../css/login.css');
... lines 5 - 24

Let's require one more CSS file right now. Open RepLogApp.js. This uses sweetalert2, which itself needs a CSS File:

216 lines web/assets/js/Components/RepLogApp.js
... lines 1 - 4
const swal = require('sweetalert2');
... lines 6 - 216

In app/Resources/views/lift/index.html.twig, we manually added a link tag for that:

59 lines app/Resources/views/lift/index.html.twig
... lines 1 - 47
{% block stylesheets %}
... lines 49 - 50
<link rel="stylesheet" href="https://cdn.jsdelivr.net/sweetalert2/6.6.0/sweetalert2.min.css" />
{% endblock %}
... lines 53 - 59

Get rid of it!

Go back to RepLogApp. Hmm, this is a little bit more interesting. We know how to import the SweetAlert JavaScript: require('sweetalert2'). But, what about the CSS file? Did yarn even download a CSS file when it installed SweetAlert?

Open up node_modules/ and go all the way down to find sweetalert2/. Inside, there is a dist/ directory and - awesome! It has a sweetalert2.css file. How can we require it? With require('sweetalert2/dist/sweetalert2.css'):

217 lines web/assets/js/Components/RepLogApp.js
... lines 1 - 4
const swal = require('sweetalert2');
require('sweetalert2/dist/sweetalert2.css');
... lines 7 - 217

I'll talk more soon about requiring files in this way, but, it should make sense: look in the sweetalert2 directory and then go require dist/sweetalert2.css.

Go back to the "Lift Stuff" main page, and hit delete. I love it. I just love it.

Let's keep going and require CSS across our entire app! When we do, we're going to find a few surprises.

Leave a comment!