About five years ago Etsy added support for Mustache as a template language. Mustache_Engine was chosen as the renderer, but required a bit of customization for our environment:
- Translation support – Prior to message catalogs, continued support for
<msg />tags was necessary. Rather than re-implement translation support, the implementation uses a two-part render cycle; the first pass leverages theSmartyrenderer for only translations, and the second pass renders the language-specific Mustache. An implementation ofMustache_Loaderwas written for this purpose. - Deterministic runtime compilation – Out of the box templates are compiled on demand. To avoid the performance hit in production, templates are built during deploys for all languages and never compiled at runtime. Where and how compilation cache artifacts are persisted are implemented in subclasses of
Mustache_Engine.
The primary interface for rendering Mustache templates. At its most basic, exposes a render(string $template, array $data) method.
Tpl_Mustache_Manager – Receives an instance of Mustache_Engine and Tpl and sets up the Mustache_Loader relationship. Also the layer at which UiToolkit_Transformer lives.
Why write our own mustache engine? The Mustache_Engine only actually does one thing: cache. The rest of the work (i.e. compilation and rendering) is delegated to other sibling classes (i.e. Mustache_Parser, Mustache_Tokenizer, Mustache_Compiler).
The problem with the design of the original Mustache_Engine is that it assumes that caching happens after the Mustache_Loader, instead of applying caching around the loader. We get two benefits by writing our own engine:
- More efficient file loading
- Predictable cache paradigm
When we speak of cache'ing, there are two varieties:
- File cache – compiled Mustache source and written to disk (optional)
- Object cache – instance of compiled Mustache source
How the object cache is populated depends on the implementation of loadForObjectCache; whether a file cache is employed is conditional on the implementation.
Shared across implementations is Tpl_Mustache_Engine, an abstract subclass of the Mustache_Engine library.
Implementations of Tpl_Mustache_Engine include:
Tpl_Mustache_Engine_Dynamic– Engine used for compiling Mustache templates on-the-fly. Does NOT write to a file cache. Usesevalto evaluate compiled source in memory. Should NOT be used for production code.Tpl_Mustache_Engine_Compile– Engine used only in the context of pre-compiling Mustache templates. Writes and loads compiled Mustache source to and from the filesystem.Tpl_Mustache_Engine_ReadOnly– Engine used for loading pre-compiled Mustache templates. Assumes all templates have been pre-compiled in advance of being used.
Use the Tpl_Mustache_Engine_Factory to instantiate an implementation of one of the engines with correct defaults applied.
There is decent unit test coverage in Tpl_Mustache_EngineTest which codifies and verifies these environment-specific concerns.
Tpl_Mustache_Loader_SmartyLoader – Loader implementation which delegates to Tpl for reading the localized template off the filesystem or Smarty cache.
For deployment to production, templates are pre-compiled during the deploy process.
The main script that handles that is bin/compile_mustache. To parallelize the distinct language builds, it is kicked off by bin/compile_mustache_parallel. The compilation work is all done by the MustacheCompiler_Compiler which traverses the filesystem, compiles the templates and writes the compiled cache to disk utilizing Tpl_Mustache_Manager and Tpl_Mustache_Engine_Compile.
Not sure when this happens (e.g. git post-commit, deploy, try, etc) but there is a linting script in bin/mustache_lint which calls on MustacheCompiler_Lint_Runner to lint for evidence of bad syntax, inaccessible markup, and Smarty in Mustache templates. Note that MustacheCompiler_Lint_CompileSniff uses Tpl_Mustache_Manager and Tpl_Mustache_Engine_Compile to capture failed Mustache compiles before running the sniffs
{{.}}vs{{{.}}}– Part of the original Edge Side Rendering project disabled the default escape behavior. Sadly this implementation detail has propogated into our current Neu implementation.