Porting a Contrib Theme to Drupal 8: Get Twig-gy With It
NOTE, Because of the ever evolving nature of Drupal 8, some of the contents in this post may be out of date.
I've been pretty busy the past several months in my free time porting my drupal contrib theme, Gratis, to Drupal 8. The port is going well and I've got most things working as they did in the Drupal 7 version but the process has involved a radical reworking of theme's architecture in part for two main reasons:
-
Twig is now the theme engine for Drupal 8, the PHPTemplate theme engine has gone away that's been a staple in Drupal themes for years.
-
As with most recent Drupal major version changes, the API has changed yet again for Drupal 8 and this has a direct impact on how you do things within a theming context.
Theme templates and Twig
What does all this mean? For one, you can no longer use PHP code in a theme template, it's all about Twig. Twig simplifies the theme layer and is easy to understand. In a Drupal 8 theme, the old template.php file now becomes your theme's .theme file that holds any special PHP code that's then rendered with Twig variables in twig theme templates. Twig syntax is also easy to understand.
So for example in Drupal 7's page.tpl.php, we have basic code that renders a block region:
<?php if (!empty($page['preface_first'])): ?>
<?php print render($page['preface_first']); ?>
<?php endif; ?>
With Twig and Drupal 8, in our page.html.twig template, we can do this as:
{% if page.preface_first %}
{{ page.preface_first }}
{% endif %}
So as you can see, there's no more "tipple fips" (.tpl.php files) with Twig. The .html.twig naming convention is now consistent throughout for theme templates. In general, Twig greatly simplifies the way variables are rendered. So where you used to have print render($page['some_var'])
or print render($content['some_var'])
, these now just become {{ page.some_var }}
{{ content.some_var }}
or simply {{ some_var }}
depending on usage and location.
Here are the key file naming conventions that have changed in Drupal 8:
Drupal 7 > Drupal 8
MYTHEME<span style="color: #339933;">.</span>info <span style="color: #339933;">></span> MYTHEME<span style="color: #339933;">.</span>info<span style="color: #339933;">.</span>yml
theme<span style="color: #0000ff;">'s template.php > MYTHEME.theme</span>
template_name<span style="color: #339933;">.</span>tpl<span style="color: #339933;">.</span>php <span style="color: #339933;">></span> template_name<span style="color: #339933;">.</span>html<span style="color: #339933;">.</span>twig
I was surprised that theme-settings.php did not have a name change either as it works hand in hand with what used to be template.php. Not much has changed for theme-settings.php either, it generally works with Drupal 7 code unchanged though I could not find an API page for function system_theme_settings
for Drupal 8.
Another big change in templates is that attributes and classes arrays have been combined together into an attribute object. Before you could render global classes and attributes for body, nodes and comments as:
class="<span style="color: #000000; font-weight: bold;"><?php</span> <span style="color: #b1b100;">print</span> <span style="color: #000088;">$classes</span><span style="color: #339933;">;</span> <span style="color: #000000; font-weight: bold;">?></span>"<span style="color: #000000; font-weight: bold;"><?php</span> <span style="color: #b1b100;">print</span> <span style="color: #000088;">$attributes</span><span style="color: #339933;">;</span> <span style="color: #000000; font-weight: bold;">?></span>
… which if converted verbatim to Twig would be:
<span style="color: #000000; font-weight: bold;">class</span><span style="color: #339933;">=</span><span style="color: #0000ff;">"{{ classes }}"</span><span style="color: #009900;">{</span><span style="color: #009900;">{</span> attributes <span style="color: #009900;">}</span><span style="color: #009900;">}</span>
However, that does not quite work, with the new way, it's actually called as:
<span style="color: #000000; font-weight: bold;">class</span><span style="color: #339933;">=</span><span style="color: #0000ff;">"{{ attributes.class }}"</span> <span style="color: #009900;">{</span><span style="color: #009900;">{</span> attributes <span style="color: #009900;">}</span><span style="color: #009900;">}</span>
For shorthand, you can simply do:
<span style="color: #009900;">{</span><span style="color: #009900;">{</span> attributes <span style="color: #009900;">}</span><span style="color: #009900;">}</span>
… without printing classes specifically, this one variable does everything. I prefer the former method as it allows you to tag on your own theme class if you want to such as clearfix
or whatever.
Theme info files
The other significant change with regard to Drupal 8 themes is the info file. It's now written in YAML and parsed by the Symfony YAML component. Converting Drupal 7's .info file was again fairly trivial. I had a look at Bartik's implementation and used that as a guide to learn enough for Gratis' conversion. For example what used to be:
; Stylesheets
stylesheets[all][] = css/style.css
would now be:
stylesheets:
all:
- css/style.css
One other issue I ran into while porting Gratis is that there seemed to be a lot of class collisions between the new admin toolbar in Drupal 8 and Gratis. It seems that the toolbar uses some very general classes such as ul.menu
for theming. In the end, I simply got more specific with the classes in my theme to avoid some of those collisions but I'm still sorting that out a bit.
In general, I did not find a ton of documentation for Drupal 8 themes, there's just not a lot out there yet so I found myself really digging into to Drupal 8's core files and themes to see how things ticked. PHPStorm, my IDE of choice, came in handy for targeted searches of functions and code. This provided me with huge insight and in a way, i'm really glad that was the case.
API changes
Where converting your theme templates to Twig is fairly trivial, updating custom code from template.php to MYTHEME.theme is not. The mainstay of converting these functions are that some are deprecated so they still work but will go away eventually. Other functions just plain will not work and throw errors as the API has changed for those entirely or there's new or different ones you'll need to substitute. PHPStorm has a nice visual indicator of deprecated functions in Drupal 8, that really comes in handy.
For example drupal_add_js
and drupal_add_css
are deprecated so you'll need to use the #attached
method now for adding any custom JS to your theme. Drupal 8 is also very lean which means it does not load a lot of Javascript by default for anonymous users so you'll need to use hook_library_info
now to create some dependancies for loading things like jQuery.once or even drupal.js for that matter. hook_library_info
my yet morph into being done in a config YML file so if you thought the API freeze for Drupal 8 was supposed to have been set in July, 2013, think again, it still seems to be a moving target. This makes it much trickier for contrib developers. The best bet is to follow any related issues for those types of things.
In the next part of this article, I'll get more into all these API changes and give some example code of how I converted the old functions.
Tags
- Drupal8
- Theming
- Twig
- Drupal Planet
- Architecture
Resources
- Gratis (Drupal 8 dev version released now)
- Attributes and classes arrays have been combined together into Attribute object
- Replace hook_library_info() by *.library.yml file
- Reduce dependency on jQuery
- Twig
- Twig and the new theme layer in Drupal 8
- Prefix all toolbar classes to prevent theme clashes
- .info files are now .info.yml files