Wayfair Tech Blog

Rendering Mustache templates with PHP

For the past couple years, Wayfair's front-end stack has relied heavily on Mustache templates. They've let our growing front-end team focus on the front-end. They allow us to share more code between server and client as we push towards a Tungsten-powered future.

Anyone who's seen a Mustache template knows that they’re pretty simple to write. Rendering them can be another story. We began using a pure PHP implementation. This got us off the ground, but as we expanded our use of templates, we ran into the unfortunate truth that such a library could never be faster than the pure PHP pages we were hoping to replace. Yet, we wanted to make it work, for the mentioned organizational and architectural reasons.

To understand why rendering Mustache in PHP is slow, first you have to understand what goes into rendering Mustache. Consider a canonical template/data example:
Hello {{name}} You have just won {{value}} dollars! {{#in_ca}} Well, {{taxed_value}} dollars, after taxes. {{/in_ca}} { "name": "Chris", "value": 10000, "taxed_value": 10000 - (10000 * 0.4), "in_ca": true }

This template must be tokenized, then each token must be rendered. Mustache is simple enough that tokenizing mostly amounts to splitting on curlies. Not a big deal for PHP. The real trick is in replacing {{name}} with the correct content. This is fine when you have a flat set of key/value pairs. Consider this example:
{{inner}} {{#outer}}{{inner}}{{/outer}} { "inner": 1, "outer": { "inner": 2 } }

The output should be "12". When rendering the {{#outer}} section, the renderer must know which "inner" to display. This is typically implemented by turning the data hash into a stack. When entering/exiting sections, data is pushed/popped. To get a value, start at the top and descend until you find a match.

This is an easy operation to describe, but it makes for some slow PHP. It was the major performance bottleneck with the Mustache implementation we first used, and it's an issue with another popular implementation.

Bearing this in mind, we sought a more radical solution. Enter php-mustache, a C++ implementation of Mustache as a PHP extension. C++ is much better than PHP at traversing stacks. Witness this before/after from when we first deployed php-mustache:

php-mustache performance

This chart shows the render time for the product grid on our browse pages (for example, Beds). It's a complex mustache template with a lot of data and a lot of nesting. The X-axis is clock time, the Y-axis is render time in milliseconds.

This kind of lift allowed us to justify making Mustache a standard instead of an occasional tool. And, courtesy of the open source world, we didn't even have to write it. However, it became something of a double-edged sword. As Wayfair operates stores in multiple countries, we have to localize a lot of strings. We started handling this by building all of them in PHP and loading them into the template. This led to some thick code in some cases, which occasionally created friction around using Mustache. The typical i18n solution for Mustache involves lambdas, which unfortunately were not implemented in php-mustache... until now! If you're a performance-minded Mustache user, we hope you'll check it out.