Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine
This tutorial has a new version, check it out!

Require CSS!?

Video not working?

It looks like your browser may not support the H264 codec. If you're using Linux, try a different browser or try installing the gstreamer0.10-ffmpeg gstreamer0.10-plugins-good packages.

Thanks! This saves us from needing to use Flash or encode videos in multiple formats. And that let's us get back to making more videos :). But as always, please feel free to message us.

Oh, before we talk about CSS, I forgot to mention that these public/build files do not need to be committed to your repository: we can rebuild them whenever we want from the source files. So inside .gitignore, add /public/build/* to make sure we don't commit them:

19 lines .gitignore
... line 1
/public/build/*
... lines 3 - 19

Ok, onto something more important! Go to /login. Thanks to some JavaScript, if you type a really long username, a message pops up. The styling for this message comes from login.css. This is included in the template: 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

This makes sense: we have a script tag for login.js and also a link tag for login.css. But remember: I want you to start thinking about your JavaScript as an application... an application that can require its dependencies. And... isn't this CSS really a dependency of our app? I mean, if we forgot to include the CSS on this page, the application wouldn't break exactly... but it would look horrible! Honestly, it would look like I designed it.

What I'm saying is: wouldn't it be cool if we could require CSS from right inside our JavaScript?

Requiring CSS

Whelp... surprise! We can! Inside login.js, add require('../css/login.css'):

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

We don't need to assign this to any variable.

So... what the heck does that do? Does that somehow magically embed that CSS onto the page? Well, it's not that magic. Look inside the build/ directory - you may need to right click and select "Synchronize" to update it. Woh! Suddenly, there is a new login.css file... which contains all of the stuff from our source login.css:

.wrapper {
margin-top: 80px;
margin-bottom: 20px;
}
.form-signin {
max-width: 420px;
padding: 30px 38px 66px;
margin: 0 auto;
background-color: #eee;
border: 3px dotted rgba(0,0,0,0.1);
}
.form-signin-heading {
text-align:center;
margin-bottom: 30px;
}
.form-control {
position: relative;
font-size: 16px;
height: auto;
padding: 10px;
}
input[type="text"] {
margin-bottom: 0px;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
input[type="password"] {
margin-bottom: 20px;
border-top-left-radius: 0;
border-top-right-radius: 0;
}
.colorgraph {
height: 7px;
border-top: 0;
background: #c4e17f;
border-radius: 5px;
background-image: -webkit-linear-gradient(left, #c4e17f, #c4e17f 12.5%, #f7fdca 12.5%, #f7fdca 25%, #fecf71 25%, #fecf71 37.5%, #f0776c 37.5%, #f0776c 50%, #db9dbe 50%, #db9dbe 62.5%, #c49cde 62.5%, #c49cde 75%, #669ae1 75%, #669ae1 87.5%, #62c2e4 87.5%, #62c2e4);
background-image: -moz-linear-gradient(left, #c4e17f, #c4e17f 12.5%, #f7fdca 12.5%, #f7fdca 25%, #fecf71 25%, #fecf71 37.5%, #f0776c 37.5%, #f0776c 50%, #db9dbe 50%, #db9dbe 62.5%, #c49cde 62.5%, #c49cde 75%, #669ae1 75%, #669ae1 87.5%, #62c2e4 87.5%, #62c2e4);
background-image: -o-linear-gradient(left, #c4e17f, #c4e17f 12.5%, #f7fdca 12.5%, #f7fdca 25%, #fecf71 25%, #fecf71 37.5%, #f0776c 37.5%, #f0776c 50%, #db9dbe 50%, #db9dbe 62.5%, #c49cde 62.5%, #c49cde 75%, #669ae1 75%, #669ae1 87.5%, #62c2e4 87.5%, #62c2e4);
background-image: linear-gradient(to right, #c4e17f, #c4e17f 12.5%, #f7fdca 12.5%, #f7fdca 25%, #fecf71 25%, #fecf71 37.5%, #f0776c 37.5%, #f0776c 50%, #db9dbe 50%, #db9dbe 62.5%, #c49cde 62.5%, #c49cde 75%, #669ae1 75%, #669ae1 87.5%, #62c2e4 87.5%, #62c2e4);
}
.login-long-username-warning {
color: #8a6d3b;
background-color: #fcf8e3;
padding: 15px;
margin-bottom: 10px;
border: 1px solid #faebcc;
border-radius:4px;
}

Here's what's happening: we point Webpack at our login entry file: login.js:

... lines 1 - 2
Encore
... lines 4 - 10
.addEntry('login', './public/assets/js/login.js')
... lines 12 - 16
;
... lines 18 - 21

Then, it parses all of the require statements inside:

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

For any required JS files, it puts them in the final login.js. But it also parses the CSS files... and puts any CSS it finds into a final file - called login.css.

Actually, it's a bit confusing: the name login.css comes from the name of the entry: login:

... lines 1 - 2
Encore
... lines 4 - 10
.addEntry('login', './public/assets/js/login.js')
... lines 12 - 16
;
... lines 18 - 21

Yep, each entry will cause one JavaScript file to be built and - if any of that JavaScript requires a CSS file - then it will also cause a CSS file to be created with the same name.

Of course, to use this in the template, we still need one link tag pointed to build/login.css:

... lines 1 - 4
{% block stylesheets %}
... lines 6 - 7
<link rel="stylesheet" href="{{ asset('build/login.css') }}" />
{% endblock %}
... lines 10 - 72

Let's try it - refresh! If you type a long name... it works! And... bonus time! When we talk about creating a production build later, this CSS file will automatically be minified.

Requiring the Layout CSS

So let's do this everywhere. Open layout.js and also the base layout: base.html.twig. Look at the top: we have a few css files, the first is main.css:

<!DOCTYPE html>
<html lang="en">
<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 - 106
</html>

In layout.js, require this: ../css/main.css:

... lines 1 - 3
require('bootstrap');
require('../css/main.css');
... lines 6 - 10

As soon as we hit save, synchronize the build directory again... Yes! We have a new layout.css file! In base.html.twig, update the link tag to use this:

<!DOCTYPE html>
<html lang="en">
<head>
... lines 4 - 10
{% block stylesheets %}
... lines 12 - 13
<link href="{{ asset('build/layout.css') }}" rel="stylesheet" />
{% endblock %}
... lines 16 - 17
</head>
... lines 19 - 106
</html>

Yep... everything still looks fine.

Handling Images

But... wait! Something amazing just happened! Look inside main.css. Woh! We're referencing a background image: ../images/dumbell-mini.png:

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

That's a problem! Why? Because the final file lives in a completely different directory, so that ../ path will break!

Actually... it's not a problem! Webpack is amazing! It parses our CSS looking for any background images or fonts. When it finds one, it moves it into a build/images/ directory and rewrites the path inside the final CSS file to point there.

Tip

The file-loader has esModule: true by default since v5.0.0. If the generated URL looks like [object Module] - you will need to set esModule to false:

// webpack.config.js

Encore
    // ...
    .configureUrlLoader({
        images: {
            esModule: false
        }
    })
    // ...

The point is: all we need to do is write our CSS files correctly and... well... Webpack takes care of the rest!

Requiring Bootstrap & FontAwesome CSS

We're on a roll! There are two CSS files left in base.html.twig: Bootstrap and FontAwesome:

<!DOCTYPE html>
<html lang="en">
<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" />
... line 14
{% endblock %}
... lines 16 - 17
</head>
... lines 19 - 106
</html>

You know the drill: require this! Remove the Bootstrap link tag first:

<!DOCTYPE html>
<html lang="en">
<head>
... lines 4 - 10
{% block stylesheets %}
<link href="https://netdna.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" />
... lines 13 - 14
{% endblock %}
... lines 16 - 17
</head>
... lines 19 - 106
</html>

In layout.js, above main.css, so that our CSS overrides their stuff, add require()... um... require... what? If we just require('bootstrap'), that will require the JavaScript file!

So... how can we include CSS files? Look in the node_modules/ directory... and scroll down to find bootstrap/. Ah, ok. Inside, there is a dist/ directory, then css/ and bootstrap.css.

A little bit of explanation: when you require the name of a module, Node reads a special key in that package's package.json file called main to figure out which file to actually require. But, if you want to require a specific file... just do it: bootstrap/dist/css/bootstrap.css:

... lines 1 - 3
require('bootstrap');
require('bootstrap/dist/css/bootstrap.css');
require('../css/main.css');
... lines 7 - 11

This time, we don't need to make any other changes to base.html.twig: we already have a link tag for layout.css, which has everything we need:

<!DOCTYPE html>
<html lang="en">
<head>
... lines 4 - 10
{% block stylesheets %}
... line 12
<link href="{{ asset('build/layout.css') }}" rel="stylesheet" />
{% endblock %}
... lines 15 - 16
</head>
... lines 18 - 105
</html>

To prove it, go back and refresh! It's still beautiful!

Yep, the built layout.css now has Bootstrap inside. And actually, Bootstrap itself references some fonts... and hey! There are now fonts in the build/ directory too! Those are handled just like background images.

Ok: one more CSS file to remove: FontAwesome. It's getting easy now! Remove the link tag from the layout:

<!DOCTYPE html>
<html lang="en">
<head>
... lines 4 - 10
{% block stylesheets %}
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" integrity="sha384-wvfXpqpZZVQGK6TAh5PVlGOfQNHSoD2xbE+QkPxCAFlNEevoEH3Sl0sibVcOQVnN" crossorigin="anonymous" />
... line 13
{% endblock %}
... lines 15 - 16
</head>
... lines 18 - 105
</html>

Then, install that library:

yarn add font-awesome@4 --dev

I added @4 to make sure we get the version compatible with this project. Oh, and how did I know to use font-awesome as the exact library name? I cheated: I already used npms.io before recording to find it.

Back in layout.js, require font-awesome. Oh, but we need to find the exact file... in node_modules/font-awesome... ah! It looks like css/font-awesome.css - add that to the require:

... lines 1 - 3
require('bootstrap');
require('bootstrap/dist/css/bootstrap.css');
require('font-awesome/css/font-awesome.css');
require('../css/main.css');
... lines 8 - 12

And Webpack is happy! Try it! Find the site and refresh! We still have our Bootstrap CSS and... yes! Our little user icon from FontAwesome is there! And on the homepage... yep! Those trash icons are from FontAwesome too!

Now, our base.html.twig file looks great! We have one CSS file and one JS file:

<!DOCTYPE html>
<html lang="en">
<head>
... lines 4 - 10
{% block stylesheets %}
<link href="{{ asset('build/layout.css') }}" rel="stylesheet" />
{% endblock %}
... lines 14 - 15
</head>
<body>
... lines 19 - 96
{% block javascripts %}
... lines 98 - 100
<script src="{{ asset('build/layout.js') }}"></script>
{% endblock %}
</body>
</html>

And all our dependencies are being required internally.

Leave a comment!

7
Login or Register to join the conversation
dgoosens Avatar
dgoosens Avatar dgoosens | posted 3 years ago | edited

right... so I figured it out...
it has to do with the default behavior in the url-loader module
(see https://stackoverflow.com/questions/59114479/when-i-using-file-loader-and-html-loader-in-webpack-the-src-attr-of-image-gonna/59115624#59115624)

So I had to disable the esModule in the config
This is done with:


// ./webpack.config.js
.configureUrlLoader({
        images: {
            esModule: false
        }
    })

Once I set this, everything runs smooth as hell

1 Reply
dgoosens Avatar
dgoosens Avatar dgoosens | dgoosens | posted 2 years ago | edited

just for the record, you should also do so for the fonts

`
// ./webpack.config.js
.configureUrlLoader({

images: {
    esModule: false
},
fonts: {
    esModule: false
}

})
`

Reply
dgoosens Avatar
dgoosens Avatar dgoosens | posted 3 years ago | edited

hi

I think I followed the steps as indicated...
Yet.... my CSS images result in


    [object Module]

so this CSS:


    .navbar-brand {
        background: url('../css_images/ardenneimmo_logo.png') center left no-repeat;
    }

becomes this once built:


    .navbar-brand {
        background: url([object Module]) center left no-repeat;
    }

Spent countless hours on this...
Any suggestions ?

Reply

Hey Dmitri,

It looks like you figured it out here: https://symfonycasts.com/sc...

Good job!

Cheers!

Reply
Dmitriy Avatar
Dmitriy Avatar Dmitriy | posted 4 years ago

Help me please. How can I deactivate the plugin ExtractText Plugin? Which is enabled by default.

Reply

Hey Dmitriy,

You can do it like:


Encore.configureExtractTextPlugin((options) => {
    options.disable = true;
});

But keep in mind that ExtractTextPlugin was removed and replaced with mini-css-extract-plugin in Webpack 4. Accordingly, extractTextPluginOptionsCallback() will be removed soon.

Reply
Dmitriy Avatar

Thank you, Victor. Everything works perfectly. Thanks for the recommendations.

Reply
Cat in space

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

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.2.0",
        "ext-iconv": "*",
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "doctrine/doctrine-bundle": "^1.6", // 1.8.1
        "doctrine/doctrine-cache-bundle": "^1.2", // 1.3.2
        "doctrine/doctrine-fixtures-bundle": "~3.0", // 3.0.2
        "doctrine/doctrine-migrations-bundle": "^1.2", // v1.3.1
        "doctrine/orm": "^2.5", // v2.7.2
        "friendsofsymfony/jsrouting-bundle": "^2.2", // 2.2.0
        "friendsofsymfony/user-bundle": "dev-master", // dev-master
        "sensio/framework-extra-bundle": "^5.1", // v5.1.5
        "symfony/asset": "^4.0", // v4.0.4
        "symfony/console": "^4.0", // v4.0.4
        "symfony/flex": "^1.0", // v1.17.6
        "symfony/form": "^4.0", // v4.0.4
        "symfony/framework-bundle": "^4.0", // v4.0.4
        "symfony/lts": "^4@dev", // dev-master
        "symfony/monolog-bundle": "^3.1", // v3.1.2
        "symfony/polyfill-apcu": "^1.0", // v1.7.0
        "symfony/serializer-pack": "^1.0", // v1.0.1
        "symfony/swiftmailer-bundle": "^3.1", // v3.1.6
        "symfony/twig-bundle": "^4.0", // v4.0.4
        "symfony/validator": "^4.0", // v4.0.4
        "symfony/yaml": "^4.0", // v4.0.4
        "twig/twig": "2.10.*" // v2.10.0
    },
    "require-dev": {
        "symfony/debug-pack": "^1.0", // v1.0.4
        "symfony/dotenv": "^4.0", // v4.0.4
        "symfony/phpunit-bridge": "^4.0", // v4.0.4
        "symfony/web-server-bundle": "^4.0" // v4.0.4
    }
}

What JavaScript libraries does this tutorial use?

// package.json
{
    "devDependencies": {
        "@symfony/webpack-encore": "^0.19.0", // 0.19.0
        "bootstrap": "3", // 3.3.7
        "copy-webpack-plugin": "^4.4.1", // 4.4.1
        "font-awesome": "4", // 4.7.0
        "jquery": "^3.3.1", // 3.3.1
        "node-sass": "^4.7.2", // 4.7.2
        "sass-loader": "^6.0.6", // 6.0.6
        "sweetalert2": "^7.11.0", // 7.11.0
        "webpack-notifier": "^1.5.1" // 1.5.1
    }
}
userVoice