WP REST API Query Demo Theme 1.0.0

More template engines and a first working version.

More options

Moving forward from my last post I’ve filled in the gaps and added support for more template engine options in the REST Query Demonstration Theme.
It can now render the content area on the PHP-powered back-end side and of the JavaScript-powered front-end side using these template engines:

And Underscore?

While I had set my mind to support underscore.js as well (and its PHP version) I could not move the idea forward.
Required conditions for a template engine to make the cut are:

  • a PHP and JavaScript version should exist in stable enough version
  • the syntax helpers should not rely on any underlying language implementations
  • there should be no need for any helper for basic operations (conditionals, loops, escaping et cetera)

Underscore does not fulfills the second and third requirements outsourcing the underlying language conditionals, loops and so on.
The difference would require two distinct templates to be kept in sync and that defeats the purpose.

PHP Adapting

To be able to have an index file, the only template file in the theme, agnostic of the template engine used I had to wrap each template specific methods in adapter classes all implementing the rqd\Template\EngineInterface interface.
It uniforms the underlying template engine API and is meant to mock the API handlebars.php exposes.

<?php
namespace rqd\Template;

interface EngineInterface {

    /**
     * Returns the template rendered with the specified data.
     *
     * @param string $templateName
     * @param array  $data
     *
     * @return string
     */
    public function render( $templateName, array $data = array() );

    /**
     * Returns the string content of the specified template.
     *
     * @param string $templateName
     *
     * @return string
     */
    public function getTemplateContents( $templateName );
}

It’s in turn used in the index.php file like this:

// index.php

//...
<div id="content-area">
    <?php
    // get hold of the global 'restified' query
    global $wp_query;

    // extract data from each post in the query
    $posts = array_map( function ( $post ) {
        return $post->data;
    }, $wp_query->posts );

    // the `rqdTemplate::templateInclude` method set up the `$templateEngine` var so that we can use it here
    /** @var TemplateEngineInterface $templateEngine */
    $templateContents = $templateEngine->getTemplateContents( 'content' );

    // print the template to the page for the JS version of the template engine to use
    echo '<script type="text/template" id="tpl-content">', $templateContents, '</script>';

    // render the content initial state with PHP
    echo $templateEngine->render( 'content', [ 'posts' => $posts ] );
    ?>
</div>
//...

The code is meant to be clear rather than efficient and that hints at all the repetition to come.
The class in charge of making sure that $templateEngine variable will be ready to use is the rqd\Template\Control one (source code) with its templateInclude method filtering WordPress template_include filter:

<?php

namespace rqd\Template;

use Handlebars\Handlebars;
use Handlebars\Loader\FilesystemLoader;
use Jade\Jade;

class Control {

    /**
     * @var string
     */
    protected $root;

    public function __construct() {
        $this->root     = dirname( dirname( dirname( __FILE__ ) ) );
        $templateEngine = get_theme_mod( 'templateEngine', 'handlebars' );

        $this->templateEngine = $this->getTemplateEngineInstanceFor( $templateEngine );
    }

    /**
     * @param string $templateEngine
     *
     * @return TemplateEngineInterface
     */
    private function getTemplateEngineInstanceFor( $templateEngine ) {
        $map = [
            'handlebars' => 'getHandlebarsInstance',
            'mustache'   => 'getMustacheInstance',
            'smarty'     => 'getSmartyInstance',
            'twig'       => 'getTwigInstance',
            'jade'       => 'getJadeInstance',
        ];

        $templateEngine = isset( $map[ $templateEngine ] ) ? $templateEngine : 'handlebars';

        return $this->{$map[ $templateEngine ]}();
    }

    public function templateInclude( $template ) {
        // allow the template to use it in its scope
        $templateEngine = $this->templateEngine;

        // include the template file
        include $template;

        // prevent WordPress from loading the template again
        return false;
    }

    /**
     * @return HandlebarsTemplateEngine
     */
    protected function getHandlebarsInstance() {
        $handlebars = new Handlebars( [ 'loader' => new FilesystemLoader( $this->getHandlebarsTemplateFolder() ) ] );

        return new HandlebarsEngine( $handlebars );
    }

    /**
     * @return string
     */
    protected function getHandlebarsTemplateFolder() {
        return $templatesFolder = $this->root . '/templates/handlebars/';
    }

    protected function getMustacheInstance() {
        $mustache = new \Mustache_Engine( [ 'loader' => new  \Mustache_Loader_FilesystemLoader( $this->getMustacheTemplateFolder() ) ] );

        return new MustacheEngine( $mustache );
    }

    protected function getMustacheTemplateFolder() {
        return $this->root . '/templates/mustache/';
    }

    // and so on...
}

Webpack to rule them all

While webpack would allow me to go crazy with code separation, usually a good practice, I’ve opted for verbosity and clarity on the JavaScript side to produce easily readable bundles (see them here) and leveraged webpack magic powers to essentially make the front-end development a joke:

// webpack.config.js

var path = require( 'path' );

module.exports = {
    entry: {
        'rqd-handlebars-bundle': './js/handlebars-app.js',
        'rqd-mustache-bundle': './js/mustache-app.js',
        'rqd-smarty-bundle': './js/jsmart-app.js',
        'rqd-twig-bundle': './js/twig-app.js',
        'rqd-jade-bundle': './js/jade-app.js',
    },
    output: {filename: './js/dist/[name].js'},
    module: {
        loaders: [
            {test: /\.js$/, loader: 'babel-loader', exclude: /(node_modules|vendor\/jsmart.min.js)/},
            {test: /\.css$/, loader: 'style!css'},
            {test: /\.scss$/, loaders: ['style', 'css', 'sass']}
        ]
    },
    resolve: {
        root: [
            path.resolve( './js/modules' ),
            path.resolve( './js/vendor' ),
        ],
        extensions: ['', '.min.js', '.js'],
    }
};

Each bundle will share 90% of the code with the others but following along require statements might not be that easy.
The template engines all exposed a fairly similar API on both ends and I had some minor issues only.
One of the gotchas being that some source vendor dependencies (jsmart.min.js above) should/could be excluded from babel parsing and checking.
Another advantage of webpack is its ability to build node.js modules into something that can be used stand-alone in a browser; this was the case for Jade and jSmart.

Findings

While I love the logic-less approach Handlebars endorses I’ve been amazed by Jade syntax, Handlebars’s take on the content template here:

// handlebars/content.handlebars

{{#if posts}}
    {{#each posts}}
        <article class="row">
            <header class="medium-4 columns text-right">
                <h4>{{{title.rendered}}}</h4>
            </header>
            <div class="medium-8 columns">
                <div class="callout">
                    {{{content.rendered}}}
                </div>
            </div>
        </article>
    {{/each}}
{{else}}
    <article class="row">
        <header class="columns text-center">
            <div class="callout">
                <h4>Nothing found.</h4>
                <p>You had one job...</p>
            </div>
        </header>
    </article>
{{/if}}

And its Jade counterpart:

// jade/content.jade

if posts
each post in posts
    article.row
        header.medium-4.columns.text-right
            h4!= post.title.rendered
        .medium-8.columns
            .callout!= post.content.rendered
else
    article.row
        header.columns.text-center
            .callout
                h4 Nothing found.
                p You had one job...

See all the content template implementations on the theme repository.

Would I use a template engine to build a WordPress REST API powered website?

Yes: definitely.
Having the same data format returned in a standard query thanks to the REST Query plugin makes this as smooth as I could make it freeing me from the chore of double templates and/or non gracefully degrading solutions. Add some caching to it and make it fly.
The need for an alternative template_include filtering process is versatile enough if applied with care. The template “hijacking” can still be a good WordPress citizen provided it’s hooked late in the filter like this:

$reasonablyLatePriority = 999;
add_filter( 'template_include', [ new Control(), 'templateInclude' ], $reasonablyLatePriority );

Should that not be enough then an extreme approach can be adopted:

add_filter( 'template_include', [ new Control(), 'templateInclude' ], PHP_INT_MAX );