Refactoring symfony.com front-end
June 12, 2017 • Published by Javier Eguiluz
The current symfony.com web site was created before the release of Symfony 2.0 back in July 2011. Although the code is continuously updated to the most recent stable version (Symfony 3.3 at the time of writing this blog post), the application is showing its age in some parts. That's why we decided to revamp its front-end simplifying the templates and managing the web assets differently.
Our front-end needs are simple, so we use a pretty traditional setup based on Bootstrap 3 and a bunch of SCSS and JavaScript files. This worked well for us at the beginning, but it was becoming harder and harder to maintain lately.
The refactoring process took us almost two weeks and involved 50 commits
changing 219 files (mostly .html.twig
and .scss
). We added or changed
6,209 lines of code and removed 10,291 lines. In this article, we explain some
of the most relevant changes made during the refactorization.
New asset organization
Previously, we had tens of small SCSS files divided by their purpose:
code.scss
, typography.scss
, forms.scss
, etc. Besides making it hard
to reuse styles, this approach complicates maintenance because it's hard to find
all the styles involved in the design of a given page element.
This is a typical developer error: splitting something into lots of smaller pieces believing that this "modular" design is better, but ending up with a hard to maintain mess.
Now we define all the common styles in a big app.scss
file and we have
dedicated files for pages with special needs: home.scss
, download.scss
,
etc. This makes the design massively simpler to maintain and helps us creating a
more consistent design, because it's easier to reuse the same styles for
different elements.
New design philosophy
The previous design was "Desktop first" and the new one is "Mobile first", which is something that we wanted to change since a long time ago. Any feature is now designed for and tested on smartphones first, and then we adjust things for larger devices if needed.
The result is that symfony.com contents now adapt nicely to any device. For example, the Symfony Roadmap page, where you can find information about the current and upcoming Symfony versions, now shows a vertical roadmap on smartphones and a horizontal roadmap on larger devices. See the before/after comparison of this page:
In order to avoid complicating the design too much, we decided to define just
two responsive breakpoints: 768px
for tablets and small desktops and
992px
for the rest of devices.
New CSS styles
Previously, we didn't use any specific CSS methodology and most of our selectors
relied on nested HTML id
attributes (e.g. #p-7-2.post #comments #add-comment
).
The new design uses HTML class
attributes exclusively and it's based on the
BEM methodology. We don't apply BEM strictly because it can rapidly become
too verbose, but BEM has helped us creating a more modular and easier to maintain
design.
Another nice improvement was including third-party dependencies in a more granular fashion. Instead of including the entire Bootstrap 3 framework, we now pick the exact Bootstrap files that we need:
// app.scss @import "~bootstrap-sass/assets/stylesheets/bootstrap/variables"; @import "~bootstrap-sass/assets/stylesheets/bootstrap/mixins"; @import "~bootstrap-sass/assets/stylesheets/bootstrap/normalize"; @import "~bootstrap-sass/assets/stylesheets/bootstrap/grid"; // ...
The last change that allowed us to simplify styles a lot are the utility CSS
classes that set the margin properties (e.g. .m-t-0
means margin-top: 0
,
.m-b-15
means margin-bottom: 15px
, etc.)
Although they are a bit controversial and some people think that they can bloat your CSS, in our case we covered all our needs with just 10 utility classes, which in turn saved us lots of useless custom CSS classes that only set margins or paddings. These utility classes are coming to Bootstrap 4 too.
New workflow
At the beginning, we used Assetic to manage symfony.com assets. However, a few months ago, we removed it and started the transition to JavaScript-based asset management. As most non-JavaScript developers, we were confused by the amount of tools available, but at the end we settled on using Webpack.
Webpack is a nice tool to bundle your styles, scripts and images, process them and generate the final CSS and JavaScript files. However, at first Webpack is tough to grasp. Luckily, we had an ally: Ryan Weaver. During the past months, Ryan has been secretly working on a new JavaScript tool to manage web assets.
This new tool, called Webpack Encore, is a simpler way to integrate Webpack into your application. It wraps Webpack, giving you a clean & powerful API for bundling JavaScript modules, pre-processing CSS & JS and compiling and minifying assets.
We've been using this tool in production on symfony.com for the past couple of months and I must say that it's a delight to use. Moreover, this new tool will become the officially recommended way to manage assets on Symfony applications. Do you want to use it in your own projects? You won't have to wait much longer because it will be published this week.
New Twig templates
The previous Twig templates were pretty good, but we made some changes to them to simplify things using modern Twig features. These are some of the tricks we used and which you can use in your own projects too:
Null coalesce operator: introduced in Twig 1.28, it provides the same ??
operator as defined by PHP 7. It's a nice and concise replacement of the
default
filter:
{% set version = version_label ?? version_number ?? 'current' %} {# equivalent to: #} {% set version = version_label|default(version_number)|default('current') %} {# also equivalent to: #} {% set version = version_label is defined ? version_label : ... %}
Don't split templates into lots of fragments: splitting templates into tiny
fragments and using include()
to include them in the template can hurt
performance. It also complicates maintenance, because it's harder to find where
the contents are defined.
Create fragments only when some part of a template is truly reused in several
templates. When including fragments, prefer the include()
function to the
include
tag and always use the Twig namespace syntax, which is faster than
the traditional bundle syntax:
{# the recommended way to include template fragments #} {{ include('@App/blog/_list_comments.html.twig') }} {# this bundle notation makes the application slower #} {{ include('AppBundle:blog:_list_comments.html.twig') }}
Check for block existence: another feature added in Twig 1.28 is the support
of is defined
operator for blocks, which is useful to check for their
existence in highly dynamic templates:
{% if block('intro') is defined %} <section> {{ block('intro') }} </section> {% endblock %}
Define custom Twig namespaces: during the redesign, we replaced a custom icon
font with proper SVG files for each icon. Referring to those files in templates
is boring (e.g. images/icons/arrow.svg
, bundles/blog/images/icons/arrow.svg
)
so we used custom Twig namespaces to store all icons under the icons
namespace and embed them using the source() Twig function:
{# Twig namespaces create concise and beautiful templates #} <i class="icon">{{ source('@icons/arrow.svg') }}</i>
Don't care about white spaces in HTML code: our work as developers is to create maintainable Twig templates, not to generate perfect looking HTML code. HTML is consumed by browsers not users, and it's mangled, minified and compressed before delivering it to the browser, so never mind about it:
{# this is beautiful and easy to maintain #} <li class="{{ current == item.slug ? 'selected' }}" ...> {# this is a mess and complicates everything for no good reason #} <li{% if current == item.slug %} class="selected"{% endif %} ...>
The result
Combining all the changes and techniques explained above, the result of the refactorization was amazing. The symfony.com web site looks and feels the same, but all the design issues are gone, the site is fully responsive and "mobile first", and the performance has improved dramatically: before, every symfony.com page downloaded a 194KB app.css file (before gzipping it); now, the common app.css file weights just 59KB, a whopping 70% decrease!
Although the purpose of the refactoring wasn't to change the visual design of the site, we took this opportunity to make some minor changes, especially on the documentation section. For example, notes, tips and warnings now are easier to recognize:
Help the Symfony project!
As with any Open-Source project, contributing code or documentation is the most common way to help, but we also have a wide range of sponsoring opportunities.
Comments are closed.
To ensure that comments stay relevant, they are closed for old posts.
"Ryan has been secretly working on a new JavaScript tool to manage web assets.
This new tool is a simpler way to integrate Webpack into your application."
Another "new JS tool", once again, and again, and it never ends :(
A Webpack wrapper, nice, but why not a Webpack *bootstraper* with nice and cool documentation inside?
What were you thinking exactly when you chose to "create another JS tool" instead of "using a famous JS tool"?
I mean, a long time ago when I discovered Gulp, I found it to be very boring to setup because we had to write our tasks manually, it was a pain in the ass. I tried to create a Gulp wrapper ( https://github.com/Pierstoval/Gryp.js ) but actually it consumed a lot of performances, and was not easy to setup either.
My solution was instead to propose an already setup Gulp configuration suitable for most websites ( https://github.com/Orbitale/Gulpfile ) that I've been using for lots of projects since, as it's based on a single config array, and it fits "most" web apps needs. And still, you can add your custom Gulp tasks in a blink of an eye because, hey, it's basically a Gulpfile, so just write in it and you're set.
I'm not a Webpack user, but the first glance of it made me fear about the future of frontend setup bootstraping: Webpack seems over-complicated *at first sight*, so if we have a "Webpack wrapper" I'm afraid we'll see users using this "new JS tool" and be lost whenever a Webpack error occurs because everything is run "behind the scenes". That's what a wrapper does.
This is a "hot-reaction", but maybe I'll be convinced by your tool when it releases during the week :)
Meanwhile, I can tell you that the tool is simple to setup, a delight to use, it has great documentation, super useful error messages, etc.
@Alex I agree with what you said! The new tool will simply help generate Webpack's standard configuration format... and then Webpack will handle everything normally. We really wanted to stay thin and let Webpack do all the work (we even use all the same wording & terminology as webpack - we don't want to abstract you away from it).
Anyways, I hope you'll like it when it's released :).
Cheers!
Feels Symfony is alive on every front :)
I prefer to inline the simple logic with HTML tags and move complex logic out of Twig to the PHP controllers or services. Thanks!
besides that, thank you for the rework and explanations!
I think this is a typo in "icon namespace". The example below uses @icons namespace.