Require CSS!?
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:
// ... 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
:
<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:
<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:
<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:
<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:
<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:
<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:
<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.
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:
Once I set this, everything runs smooth as hell