Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Importing 3rd Party CSS + Image Paths

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.

We're on a mission to refactor all the old <script> and <link> tags out of our templates. For the base layout, we're half way done! There is only one script tag, which points to the app entry:

<!doctype html>
<html lang="en">
... lines 3 - 17
<body>
... lines 19 - 90
{% block javascripts %}
{{ encore_entry_script_tags('app') }}
{% endblock %}
</body>
</html>

That's perfect.

Back on top, we do still have multiple link tags, including Bootstrap from a CDN, FontAwesome, which I apparently just committed into my public/css directory, and some custom CSS in styles.css:

<!doctype html>
<html lang="en">
<head>
... lines 5 - 8
{% block stylesheets %}
{{ encore_entry_link_tags('app') }}
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
<link rel="stylesheet" href="{{ asset('css/font-awesome.css') }}">
<link rel="stylesheet" href="{{ asset('css/styles.css') }}">
{% endblock %}
</head>
... lines 17 - 94
</html>

First, eliminate Bootstrap! In the same way that we can properly install JavaScript libraries with Yarn, we can also install CSS libraries! Woo!

In app.js, we're already importing a single app.css file:

25 lines assets/js/app.js
... lines 1 - 7
// any CSS you require will output into a single css file (app.css in this case)
import '../css/app.css';
... lines 10 - 25

We could add another import right here for the Bootstrap CSS. Instead, I prefer to import just one CSS file per entry. Then, from within that CSS file, we can use the standard @import CSS syntax to import other CSS files. To Webpack, these two approaches are identical.

Now, you might be thinking:

Don't we need to install the bootstrap CSS library?

And... yes! Well, I mean, no! Um, I mean, we already did it! In node_modules/, look for bootstrap/. This directory contains JavaScript but it also contains the Bootstrap CSS.

Importing CSS from node_modules

But... hmm... In JavaScript, we can say import then simply the name of the package and... it just works! But we can't repeat that same trick for CSS.

Instead, we'll point directly to the path we want, which, in this case is probably dist/css/bootstrap.css. Here's how: @import, ~bootstrap and the path: /dist/css/bootstrap.css:

@import '~bootstrap/dist/css/bootstrap.css';

The ~ part is special to CSS and Webpack. When you want to reference the node_modules/ directory from within a CSS file, you need to start with ~. That's different than JavaScript where any path that doesn't start with . is assumed to live in node_modules/. After the ~, it's just a normal, boring path.

But yea... that's all we need! Move over and refresh. This looks exactly the same!

Referencing just the Package Name

And... remember how I said that we can't simply import CSS by referencing only the package name? That was... kind of a lie. Shorten this to just ~bootstrap:

@import '~bootstrap';

Go try it! Refresh and... the same!

This works thanks to a little extra feature we added to Encore... which may become a more standard feature in the future. We already know that when we import a package by its name in JavaScript, Webpack looks in package.json, finds the main key.... there it is and uses this to know that it should finally import the dist/js/bootstrap.js file.

Some libraries also include these style or sass keys. And when they do, you only need to @import ~ and the package name. Because we're doing this from inside a CSS file, it knows to look inside package.json for a style key.

This is just a shortcut to do the exact same thing we had before.

Installing & Importing Font Awesome

Bootstrap, check! Let's keep going: the next link tag is for FontAwesome. Get rid of that and celebrate by deleting the public/css/font-awesome.css file and this entire fonts/ directory. This feels great! We're deleting things that I never should have committed in the first place.

Next, download FontAwesome with:

yarn add font-awesome --dev

When it finishes, go back to node_modules/ and search for font-awesome/. Got it! Nice! It has directories for css/, less/, scss/ whatever format we want. And fortunately, if you look inside package.json, it also has a style key.

Easy peasy! In app.css, add @import '~font-awesome':

@import '~bootstrap';
@import '~font-awesome';

Done. Find your browser and refresh. Let's see... down here, yes! This is a FontAwesome icon. It still works!

Image & Font Handling

But this is way cooler than it seems! Internally, the FontAwesome CSS file references some font files that the user's browser needs to download: these files here. But... these files aren't in our public directory... so shouldn't the paths to these be broken?

Close up node_modules/ and check out the public/build/ directory. Whoa! Where did this fonts/ directory come from? When Webpack sees that a CSS file refers to a font file, it copies those fonts into this fonts/ directory and rewrites the code in the final app.css file so that the font paths point here. Yes, it just handles it.

It also automatically adds a hash to the filename that's based on the file's contents. So if we ever update the font file, that hash would automatically change and the CSS would automatically point to it. That's free browser cache busting.

Moving our CSS into Encore

Ok one more link tag to go:

<!doctype html>
<html lang="en">
<head>
... lines 5 - 8
{% block stylesheets %}
... lines 10 - 11
<link rel="stylesheet" href="{{ asset('css/styles.css') }}">
{% endblock %}
</head>
... lines 15 - 92
</html>

Remove it! Then, open css/styles.css, copy all of this, delete that file, and, in app.css, highlight the blue background and paste!

246 lines assets/css/app.css
@import '~bootstrap';
@import '~font-awesome';
body {
position: relative;
background: #efefee;
min-height: 45rem;
padding-bottom: 80px;
}
html {height:100%}
/* NAVIGATION */
.navbar-bg {
background: url('../images/space-nav.jpg');
background-size: 80%;
}
.dropdown-menu, .dropdown-menu.show {
right: 0;
}
.space-brand {
color: #fff;
font-weight: bold;
}
.nav-profile-img {
width: 50px;
border: 1px solid #fff;
}
.nav-tabs .nav-link:focus, .nav-tabs .nav-link:hover {
color: #efefee;
}
/* ADVERTISEMENT */
.ad-space {
background: #fff;
border-radius: 5px;
border-top: 5px solid green;
}
.advertisement-img {
width: 150px;
height: auto;
border: 2px solid #efefee;
border-radius: 5px;
}
.advertisement-text {
font-weight: bold;
}
.quote-space {
background: #fff;
margin-top: 30px;
border-radius: 5px;
border-top: 5px solid hotpink;
}
/* ARTICLES */
.main-article {
border: 2px solid #efefee;
Background: #fff;
border-top-left-radius: 6px;
border-top-right-radius: 6px;
}
.main-article img {
width: 100%;
height: 250px;
border-top-right-radius: 5px;
border-top-left-radius: 5px;
border-top: 5px solid lightblue;
}
.article-container {
border: 1px solid #efefee;
border-top-left-radius: 5px;
border-bottom-left-radius: 5px;
background: #fff;
}
.main-article-link, .article-container a {
text-decoration: none;
color: #000;
}
.main-article-link:hover {
text-decoration: none;
color: #000;
}
.article-title {
min-width: 300px;
}
@media (max-width: 440px) {
.article-title {
min-width: 100px;
max-width: 245px;
}
}
.article-img {
height: 100px;
width: 100px;
border-top-left-radius: 5px;
border-bottom-left-radius: 5px;
}
.article-author-img {
height: 25px;
border: 1px solid darkgray;
}
.article-details {
font-size: .8em;
}
/* PROFILE */
.profile-img {
width: 150px;
height: auto;
border: 2px solid #fff;
}
.profile-name {
font-size: 1.5em;
}
.my-article-container {
background: #FFBC49;
border: solid 1px #efefee;
border-radius: 5px;
}
/* CREATE ARTICLE */
.create-article-container {
min-width: 400px;
background-color: lightblue;
border-radius: 5px;
}
/* ARTICLE SHOW PAGE */
.show-article-container {
width: 100%;
background-color: #fff;
}
.show-article-container.show-article-container-border-green {
border-top: 3px solid green;
border-radius: 3px;
}
.show-article-img {
width: 250px;
height: auto;
border-radius: 5px;
}
.show-article-title {
font-size: 2em;
}
.like-article, .like-article:hover {
color: red;
text-decoration: none;
}
@media (max-width: 991px) {
.show-article-title {
font-size: 1.5em;
}
.show-article-title-container {
max-width: 220px;
}
}
.article-text {
margin-top: 20px;
}
.share-icons i {
font-size: 1.5em;
}
.comment-container {
max-width: 600px;
}
.comment-img {
width: 50px;
height: auto;
border: 1px solid darkgray;
}
.commenter-name {
font-weight: bold;
}
.comment-form {
min-width: 500px;
}
@media (max-width: 767px) {
.comment-form {
min-width: 260px;
}
.comment-container {
max-width: 280px;
}
}
/* FOOTER */
.footer {
position: absolute;
bottom: 0;
width: 100%;
height: 60px; /* Set the fixed height of the footer here */
line-height: 60px; /* Vertically center the text there */
background-color: #fff;
margin-top: 10px;
}
/* Sortable */
.sortable-ghost {
background-color: lightblue;
}
.drag-handle {
cursor: grab;
}

That's a simple step so... it should work, right? Nope! Check out the build failure:

Module not found: Can't resolve ../images/space-nav.jpg in our assets/css/ directory.

It doesn't show the exact file, but we only have one. Ah, here's the problem:

246 lines assets/css/app.css
... lines 1 - 12
/* NAVIGATION */
.navbar-bg {
background: url('../images/space-nav.jpg');
... line 17
}
... lines 19 - 246

PhpStorm is super angry about it too! This background image references ../images/, which was perfect when the code lived in the public/css/ directory. But when we moved it, we broke that path!

This is awesome! Instead of us silently not realizing we did this, we get a build error. Amazing! We can't break paths without Webpack screaming.

To fix this, let's "cut" the entire images/ directory and move it into the assets/ folder. Yep, it's gone. But Encore doesn't know to re-compile... so make a small change and save. Build successful!

Go check it out. Refresh! It works! And even better, look at the build/ folder. We have an images/ directory with space-nav.jpg inside. Just like with fonts, Webpack sees our path, realizes that space-nav.jpg needs to be public, and so moves it into the build/images/ directory and rewrites the background-image code in the final CSS to point here.

The moral is this: all we need to do is worry about writing our code correctly: using the proper relative paths from source CSS file to source image file. Webpack handles the ugly details.

Now, this did break a few <img> tags on our site that are referencing some of these files. Now that they're not in the public/ directory... they don't work. We'll handle that soon.

But next, let's get more from our CSS by using Sass.

Leave a comment!

14
Login or Register to join the conversation
Ad F. Avatar
Ad F. Avatar Ad F. | posted 2 years ago | edited

now you need a loader for css

`ERROR in ./assets/styles/app.css 1:0
Module parse failed: Unexpected token (1:0)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders

.bd-callout{padding:1.25rem;margin-top:1.25rem;margin-bottom:1.25rem;border:1px solid #eee;border-left-width:.25rem;border-radius:.25rem}`

1 Reply

Hey @cybernet2u!

What's your code look like that gives you this error? Encore automatically adds the CSS loader, so you should never see an error like this.

Cheers!

Reply
Kensley L. Avatar
Kensley L. Avatar Kensley L. | posted 3 years ago

Any suggestions on how to install and run the latest version of font-awesome 5 with this tutorial? A source referred that we should be using @fortawesome/fontawesome-free as a package download now, not font-awesome. Any suggestions?

1 Reply

Hey Kensley L.!

I just did that yesterday on something :). Quick answer:


yarn add @fortawesome/fontawesome-free --dev

Then, from inside some CSS file somewhere, add:


@import '~@fortawesome/fontawesome-free/css/all.css';

There are variations - like you could only import some of the CSS, or you could import the Sass in that same package, but that's the idea. Let me know if it helps :).

Cheers!

1 Reply
Martin K. Avatar
Martin K. Avatar Martin K. | posted 1 year ago

After moving the image files from public/images into the assets folder they are not shown anymore.
Doesn't Webpack need to take care of copying these image files into the public/images folder after this step?

Reply

Hey Martin,

No, unfortunately Webpack Encore does not know what exactly files you want to copy to the public/ dir, so you have to specify it in webpack.config.js - Webpack Encore has .copyFiles() method that helps with it. It will be next in this course btw, see https://symfonycasts.com/sc... :)

Cheers!

Reply
MAli Avatar

Hi, After adding @import '~bootstrap'; in app.css
I get the following error in console:
GET http://localhost/build/~bootstrap net::ERR_ABORTED 404 (Not Found)
and bootstrap css not working :(

When i stop yarn watch and rerun it agan the app.css file goes back to the prevues version and delete the new line `@import '~bootstrap'; ` !!!???

Bootstrap.js on app.js works with out any program.

Reply
MAli Avatar

And yarn watch not working for app.css file !!
When I try to rerun the yarn watch command the app.css file removes every change and gets back to the first version when I installed encore using `composer require encore`

I don't understand why , any help please :)

Reply
MAli Avatar

Everything works fine now when I enabled `.enableSassLoader()` in webpack.config.js and changed from .css to .scss :)

Reply

Hey @Ali!

Nice job debugging! It's unfortunate that you got such an odd error originally... I actually would have expected it to work fine... but who knows. You've got it working now is all that matters :).

Cheers!

Reply
MAli Avatar
MAli Avatar MAli | weaverryan | posted 1 year ago | edited

Hey @weaverryan
Thank you for your reply :)

Cheers!

Reply

Hello!

I am trying to change the font path used for boostrap variable $font-family-sans-serif. I have upoaded my fonts in /assets/fonts and create a new copyFiles in my webpack to copy the fonts in public folder:

`
.copyFiles({

from: './assets/fonts',
to: 'fonts/[path][name].[hash:8].[ext]'

})`

In my variables.scss, I defined the font path as follow:
$font-path: '/public/build/fonts';

The new value for bootstrap font-family:
$font-family-sans-serif: 'Numito-Regular'

Example of a font face :
<br />@font-face {<br /> font-family: 'Nunito-Bold';<br /> src: url('#{$font-path}/Nunito-Bold.eot');<br /> src: url('#{$font-path}/Nunito-Bold.eot?#iefix') format('embedded-opentype'),<br /> url('#{$font-path}/Nunito-Bold.woff2') format('woff2'),<br /> url('#{$font-path}/Nunito-Bold.woff') format('woff'),<br /> url('#{$font-path}/Nunito-Bold.ttf') format('truetype'),<br /> url('#{$font-path}/Nunito-Bold.svg#Nunito-Bold') format('svg');<br /> font-weight: bold;<br /> font-style: normal;<br />}<br />

Compilation is working well but in my page, I got a 404 for the font. Checking the network tab, I can see it's looking for the font file without the hash.

How can I make it looking for the right font file? Or maybe I should do it differently?

Thx!

Reply

Hey be_tnt

IIRC you shouldn't create separate .copyFiles() for fonts, you should just configure $font-path relative to your variables.scss for example $font-path: '../fonts'; and webpack will do all the magic.

Cheers!

Reply

Indeed!! Thx for this explanation :)

Reply
Cat in space

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

This tutorial works great with Symfony5!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.1.3",
        "ext-iconv": "*",
        "aws/aws-sdk-php": "^3.87", // 3.91.4
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "knplabs/knp-markdown-bundle": "^1.7", // 1.7.1
        "knplabs/knp-paginator-bundle": "^2.7", // v2.8.0
        "knplabs/knp-time-bundle": "^1.8", // 1.9.0
        "league/flysystem-aws-s3-v3": "^1.0", // 1.0.22
        "league/flysystem-cached-adapter": "^1.0", // 1.0.9
        "liip/imagine-bundle": "^2.1", // 2.1.0
        "nexylan/slack-bundle": "^2.0,<2.2.0", // v2.1.0
        "oneup/flysystem-bundle": "^3.0", // 3.0.3
        "php-http/guzzle6-adapter": "^1.1", // v1.1.1
        "sensio/framework-extra-bundle": "^5.1", // v5.3.1
        "stof/doctrine-extensions-bundle": "^1.3", // v1.3.0
        "symfony/asset": "^4.0", // v4.2.5
        "symfony/console": "^4.0", // v4.2.5
        "symfony/flex": "^1.9", // v1.17.6
        "symfony/form": "^4.0", // v4.2.5
        "symfony/framework-bundle": "^4.0", // v4.2.5
        "symfony/orm-pack": "^1.0", // v1.0.6
        "symfony/security-bundle": "^4.0", // v4.2.5
        "symfony/serializer-pack": "^1.0", // v1.0.2
        "symfony/twig-bundle": "^4.0", // v4.2.5
        "symfony/validator": "^4.0", // v4.2.5
        "symfony/web-server-bundle": "^4.0", // v4.2.5
        "symfony/webpack-encore-bundle": "^1.4", // v1.5.0
        "symfony/yaml": "^4.0", // v4.2.5
        "twig/extensions": "^1.5" // v1.5.4
    },
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.0", // 3.1.0
        "easycorp/easy-log-handler": "^1.0.2", // v1.0.7
        "fzaninotto/faker": "^1.7", // v1.8.0
        "symfony/debug-bundle": "^3.3|^4.0", // v4.2.5
        "symfony/dotenv": "^4.0", // v4.2.5
        "symfony/maker-bundle": "^1.0", // v1.11.5
        "symfony/monolog-bundle": "^3.0", // v3.3.1
        "symfony/phpunit-bridge": "^3.3|^4.0", // v4.2.5
        "symfony/profiler-pack": "^1.0", // v1.0.4
        "symfony/var-dumper": "^3.3|^4.0" // v4.2.5
    }
}

What JavaScript libraries does this tutorial use?

// package.json
{
    "devDependencies": {
        "@symfony/webpack-encore": "^0.27.0", // 0.27.0
        "autocomplete.js": "^0.36.0",
        "autoprefixer": "^9.5.1", // 9.5.1
        "bootstrap": "^4.3.1", // 4.3.1
        "core-js": "^3.0.0", // 3.0.1
        "dropzone": "^5.5.1", // 5.5.1
        "font-awesome": "^4.7.0", // 4.7.0
        "jquery": "^3.4.0", // 3.4.0
        "popper.js": "^1.15.0",
        "postcss-loader": "^3.0.0", // 3.0.0
        "sass": "^1.29.0", // 1.29.0
        "sass-loader": "^7.0.1", // 7.3.1
        "sortablejs": "^1.8.4", // 1.8.4
        "webpack-notifier": "^1.6.0" // 1.7.0
    }
}
userVoice