Require CSS Files

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

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:

... 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'):

... lines 1 - 2
const $ = require('jquery');
... 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


To avoid incompatibility problems in further chapters make sure to install css-loader version 0.28:

yarn add css-loader@0.28 --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']:

... lines 1 - 3
module.exports = {
... lines 5 - 13
module: {
rules: [
... lines 16 - 25
test: /\.css$/,
use: [
... 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):

... lines 1 - 3
const css = require('../css/login.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:

... lines 1 - 3
module.exports = {
... lines 5 - 13
module: {
rules: [
... lines 16 - 25
test: /\.css$/,
use: [
... 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:

... lines 1 - 3
... lines 5 - 24

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

... 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:

... lines 1 - 47
{% block stylesheets %}
... lines 49 - 50
<link rel="stylesheet" href="" />
{% 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'):

... lines 1 - 4
const swal = require('sweetalert2');
... 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!

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
        "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