Symfony for frontend developers #5 – Assets and Assetic

This article is part of a series; see Symfony for frontend developers for more.

Introduction

Symfony has some useful functionality for dealing with your site’s assets – its javascripts, images and stylesheets.  Most Symfony apps also use “Assetic” to provide even more power and control.  This blog post helps you understand how these asset-management approaches work, so you can make the most of their features, rather than feeling like you’re fighting them!

Let’s start at the very beginning

I’m going to walk you through various example of how you can include assets in a Symfony file, from the simplest to the most powerful.  Along the way, we’ll discover some Symfony commands you might need, and even explain a bit more about Symfony’s folder structure.

But let’s start at the very beginning…

The vanilla approach to including an asset

<script src="/js/script.js"></script>

There’s nothing special about this one – we’re just referencing a javascript in the same way you’ve probably done a million times before.  This sort of thing still works in Symfony, but other approaches are better…

Using Symfony’s asset function

<script src="{{ asset('js/script.js') }}"></script>

This approach uses a special Twig function called asset to reference the script.  The asset function provides you with two particular benefits:

The asset function’s main purpose is to make your application more portable. If your application lives at the root of your host (e.g. http://example.com), then the rendered paths should be like /images/logo.png. But if your application lives in a subdirectory (e.g. http://example.com/my_app), each asset path should render with that subdirectory (e.g. /my_app/images/logo.png). The asset function takes care of this by determining how your application is being used and generating the correct paths accordingly.

Tip: If you’re getting assets from a CDN, you can specify base URLs for assets, and Symfony will automatically load-balance requests across the different URLs you provide!  For more information, see the Symfony documentation entry for the assets_base_urls parameter.

Secondly, if you use the asset function, Symfony can automatically append a query string to your asset, in order to guarantee that updated static assets won’t be cached when deployed. For example, /images/logo.png might look like /images/logo.png?v2. For more information, see the assets_version configuration option.

Tip: If you’re doing this, did you know that you can automate the change of the asset version based on git hashes?

The asset function is described in more detail at http://symfony.com/doc/current/book/templating.html#book-templating-assets

Before we take things to the next level, we need a brief aside about the “web” folder.

The web folder

If you read the article in this series “where are my files?!”, you might remember that Symfony allows for logically structured folders for the files that you edit (the src folder) and then for a more website-friendly folder structure when the application is actually running (the web folder).

Whatever method you use to include your assets — the vanilla approach, Symfony’s asset function, or Assetic — the assets will need to be somewhere in the web folder.  The web folder is the public root folder for your site, so if a page references /js/script.js, that effectively means web/js/script.js.

This begs the question — how do things end up in the web folder?  The answer is this command:

app/console assets:install web [--symlink]

If you’re anything like me, you’ve probably used this command like a magic wand — “help, my site’s styling has disappeared, get it back!”  I’ll explain what the command actually does…

Before you run this command, you’ll have assets somewhere in src (on the left in the screenshot below), and your web folder (on the right) will mainly contain various Symfony scripts:

before-assets-install

If you run the command without the –symlink option, you end up with this:

after-no-symlink

Your src folder hasn’t changed, but your web folder now contains your assets.  Notice the special folder structure Symfony creates for them.  Note also that the example above contains assets for three bundles, not just for the one bundle shown on the left.

 If you’d run the command with the –symlink option, you’d end up with something very similar to the above, except that every folder under bundles (acmedemo, framework and sensiodistribution in the example above) will not be a real folder, but will be a symlink back to the things in src.

Tip: Always use the –symlink option in your development environment.  It prevents you having to re-run the command when assets change.  You’ll then only need to re-run it if a whole new bundle is added.

Okay, let’s jump back to thinking about ways to reference assets.  So far you’ve seen the special Symfony Twig command asset.  Next on the agenda, we’ll improve our examples by using the special asset-management functionality provided by “Assetic”.

Assetic example

Assetic is an asset management framework for PHP, which is available as a Symfony bundle (so it can be easily incorporated into Symfony projects).

Why might you want to use it?  There are two main reasons:

First off, Assetic allows you to use “filters” to manipulate assets before they are loaded.  You can do all kinds of things this way – automatically minify CSS, compress javascript, compile LESS or SASS, optimise JPEGs and a whole lot more.  I’m not going to cover this in detail here, but you can read more in the Symfony cookbook.

 Secondly, you can combine multiple scripts or CSS files into one.  Here’s an example:

{% javascripts '@AcmeFooBundle/Resources/public/js/*' %}
    <script type="text/javascript" src="{{ asset_url }}"></script>
{% endjavascripts %}

(Writing @AcmeFooBundle here is the same as writing bundles/acmefoo (remember the folder structure under web in the screenshots earlier?))  Including images and CSS is done in a similar way but uses slightly different syntax.

Note how this is delivering multiple javascripts, but combining them all into one file.  For example, the actual HTML rendered in the development environment might look like this: <script src=”/app_dev.php/js/abcd123.js”></script>

Tip: If you’re referencing fonts or images in your CSS, you’re probably wondering what happens to those references when CSS files from multiple directories are combined into one.  To ensure that your relative paths still work, use Assetic’s cssrewrite filter.  For example:

{% stylesheets 'bundles/projcore/css/*' filter='cssrewrite' %}
    <link rel="stylesheet" href="{{ asset_url }}" />
{% endstylesheets %}

Tip: If using Assetic tags for the first time, you may see an error like “You must add YourBundle to the assetic.bundle config to use the {% javascripts %} tag”.  You need to update config.yml as described here.

Gotchas

If you’re using Assetic to combine multiple CSS files into one, as explained above, there are a few gotchas to be aware of.

First of all, note that – depending on your Symfony setup – you may find that the combining of files into one only happens in your live environment, not on your local development environment.  To be honest, that’s probably a good thing, as it’ll make local troubleshooting easier!  But you can make your development environment act like live if need be (just remember to change it back!)

Secondly, be aware of the order in which files are combined.  Consider this code which includes CSS files:

{% stylesheets 'bundles/projcore/css/*' %}
    <link rel="stylesheet" href="{{ asset_url }}" />
{% endstylesheets %}

This is combining all files in the css folder into one.  If the order of the stylesheets matters, you’ll need to change this code to explicitly name the files.  For instance:

{% stylesheets
'bundles/projcore/css/style.css'
'bundles/projcore/css/style-responsive.css'
'bundles/projcore/css/animate.css'
%}
    <link rel="stylesheet" href="{{ asset_url }}" />
{% endstylesheets %}

We saw this problem when combining CSS files — we had to explicitly include Bootstrap’s CSS before our own.

Finally, be aware that older versions of IE (<= 9) have a limit on the number of CSS selectors they will process in any one file!  If you have too many, you may just find some of your style rules are ignored!  Thankfully, the workaround is easy – split your stylesheets block into two.  The example above would become something like this:

{% stylesheets
'bundles/projcore/css/style.css'
%}
    <link rel="stylesheet" href="{{ asset_url }}" />
{% endstylesheets %}
{% stylesheets
'bundles/projcore/css/style-responsive.css'
'bundles/projcore/css/animate.css'
%}
    <link rel="stylesheet" href="{{ asset_url }}" />
{% endstylesheets %}

New syntax but new benefits!

Hopefully this article has helped you understand the new syntax you’ll encounter when dealing with assets in Symfony projects.  Thankfully, this new way of doing things brings great benefits too:

  • Cache-busting querystrings
  • Automatic asset manipulation with filters
  • Automatic combination of multiple scripts or CSS files into one

Enjoy!

Post a Comment

Your email address is never published nor shared. Required fields are marked *

Ready to talk?

Whether you want to create a new digital product or make an existing one even better, we'd love to talk it through.

Get in touch