JavaScript and CSS in eZ Publish 5
By: Ernesto Buenrostro | January 7, 2015 | eZ Publish development tips
A key component of a content management system or web application is the handling of JavaScript and CSS files, specifically around loading, combining, and minifying them. Loading fewer files and a smaller amount of data in each file leads to both server-side and client-side performance improvements. In eZ Publish 4 / legacy, this was handled nicely with an extension called ezjscore. Now in the eZ Publish 5 new stack, we have a Symfony tool called Assetic. In this post we'll introduce how Assetic works in eZ Publish 5.0 through 5.4.
Background
The ezjscore extension provides useful functions for:
- Ajax using YUI or jQuery
- JavaScript and CSS loading (compressing, concatenating files, generating reports of them in the debug output).
- Template functions/operators to include default scripts or specific scripts
In eZ Publish 5, there is no eZ-specific extension or bundle that provides this functionality. However, since eZ Publish 5 is built on top of the Symfony2 framework we can make use of bundles developed for Symfony2. In this case, we have the Assetic bundle.
Assetic is actually more powerful than ezjscore. It can process more than just JavaScript and CSS, as its approach is to use an extensible set of filters to process the files passed to it.
Some of what Assetic can do:
- Minify CSS and JS
- Optimize images
- Process and compile LESS/Coffee Script
- Transform URLs within your CSS files
Loading JS and CSS in Twig templates
To load JavaScript files in Twig templates, use the javascripts tag. In this tag, you can specify the filter to use, the destination folder, and the source files. Within the tag, you output the <script> tag, using the asset_url variable as the source attribute.
The "uglifyjs2" filter is for concatenating and minifying JavaScript files, and we'll touch upon that shortly. Note that we are using a question mark ("?") in front of the filter -- this means to use the filter only when outside of the development environment.
{% javascripts filter='?uglifyjs2' output='cms/js/*' '@MyBundle/Resources/public/js/libs/jquery.js' '@MyBundle/Resources/public/js/main.js' %} <script src="{{ asset_url }}"></script> {% endjavascripts %}
The stylesheets tag is very similar, except that it is for CSS files. Within the tag, you output the <link> tag, using the asset_url variable as the "href" attribute. Concatenating and minification of CSS files is handled by the "cssmin" filter, which we will explore next!
{% stylesheets filter='?cssmin' output='cms/css/*' '@MyBundle/Resources/public/css/main.css' %} <link href="{{ asset_url }}" rel="stylesheet" type="text/css"> {% endstylesheets %}
Filters and additional features
Assetic might be more powerful than ezjscore, but it is actually missing some of ezjscore's features out of the box. For example, the filters for concatenating and minifying JS and CSS as mentioned above need to be configured.
UglifyJS2 is a library for JavaScript minification that is run using Node.js. Thus, you need to install Node.js and then UglifyJS2.
To install Node.js on Debian or Ubuntu, you can run the following commands:
$ sudo apt-get update $ sudo apt-get install nodejs
To install UglifyJS2, first install npm, which is the Node.js package manager:
$ sudo apt-get install npm
Then, run this command:
$ npm install -g uglify-js
For minifying CSS files, we can use CssMin.
First, download CssMin and then extract it in the vendor/cssmin directory in your Symfony2 application:
$ tar zxvf cssmin-v3.0.1-source.tgz
In our config.yml file is the Assetic configuration where we can at last define our filters!
# Assetic Configuration assetic: debug: %kernel.debug% use_controller: %kernel.debug% bundles: [ MyBundle ] filters: cssrewrite: ~ cssmin: file: %kernel.root_dir%/../vendor/cssmin/CssMin.php uglifyjs2: # Modify this path if the uglifyjs executable is somewhere else bin: /usr/bin/uglifyjs
Another out of the box missing feature when compared to ezjscore is to create new names for the output files whenever the CSS or JavaScript is edited. Without this feature, you will have caching issues whenever you update code if the path to the CSS or JS files remains the same. The way to achieve this in Assetic is to create a "cache busting worker" that uses different file names each time new files are output.
Here is an example of the new class we need to add to enable this feature. You would place this file at MyBundle/BaseBundle/Factory/AssetFactory.php:
namespace MyBundle\BaseBundle\Factory; use Symfony\Bundle\AsseticBundle\Factory\AssetFactory as BaseAssetFactory; use Assetic\Factory\LazyAssetManager; use Assetic\Factory\Worker\CacheBustingWorker; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; use Symfony\Component\HttpKernel\KernelInterface; class AssetFactory extends BaseAssetFactory { public function __construct (KernelInterface $kernel, ContainerInterface $container, ParameterBagInterface $parameterBag, $baseDir, $debug = false ) { parent::__construct( $kernel, $container, $parameterBag, $baseDir, $debug ); // Add a new CacheBustingWorker $this->addWorker( new CacheBustingWorker( new LazyAssetManager( new BaseAssetFactory( $kernel, $container, $parameterBag, $baseDir, $debug ) ) ) ); } }
Dump the asset files!
The filters are run whenever you dump the asset files. You typically do this only when deploying new code, since this is when you'll have new JavaScript and/or CSS rules.
This is the standard command that you run from the root of your production site, which will parse your Twig templates, run the relevant Assetic filters, and output your files into the web directory:
$ php ezpublish/console assetic:dump --env=prod web