Micro moteur de template

Je travaille à l'intégration d'un site de e-commerce en ce moment, qui contient quelques composants en javascript. Comme je ne m'occupe que de l'intégration, je n'écrit que du javascript vanilla, sans librairies qui aurait pu m'être utiles ici comme React ou Polymer. Sauf que je suis arrivé à un point où un composant générait une vue en javascript. Grâce aux templates literal, le template reste lisible dans le javascript, mais j'étais quand même inquiet quand à la maintenabilité de cette partie. J'aurais préféré que ce template reste là où est sa place : dans un fichier html. Alors j'ai bidouillé une classe de templating à coup de vilain eval, et ça a marché.

Donc au départ, nous avions ceci :

class MyComponent {  
  function generateElement () {
    const tags = ['foo', 'bar', 'babar', 'lelefan'];
    const displayButton = true;
    const el = document.createElement('div');
    el.innerHTML = `
      <div
        class="mycomponent-ctn">
        <p
          class="mycomponent-text">Hello World</p>
        <ul>
          ${tags.map(tag => `<li>${tag}</li>`).join('')}
        </ul>
        ${displayBtn ?
        `<button>OK</button>`:
        ''}
      </div>
    `;
    document.body.appendChild(el);
  }
}

Sauf que ce gros pavé de html perdu au milieu d'une classe javascript, ça fait tâche et on n'est pas à l'abri de tout casser en cherchant à changer une classe css. Voici ce que j'ai réussi à avoir à l'aide de mon micro template renderer :

<template  
  id="my-tpl">
  <div
    class="mycomponent-ctn">
    <p
      class="mycomponent-text">Hello World</p>
    <ul>
      ${data.tags.map(tag => `<li>${tag}</li>`).join('')}
    </ul>
    ${data.displayBtn ?
    `<button>OK</button>`:
    ''}
  </div>
</template>  
import {TemplateRenderer} from './template-renderer';

class MyComponent {  
  function generateElement () {
    const tpl = new TemplateRenderer(document.querySelector('#my-tpl'));
    document.body.appendChild(tpl.render({
      tags: ['foo', 'bar', 'babar', 'lelefan'],
      displayButton: true
    }));
  }
}

Et voici donc la classe magique qui fait tout le travail :

export class TemplateRenderer {  
  constructor(template) {
    this.template = template;
  }

  render(data = {}) {
    const newEl = document.createElement('div');
    newEl.innerHTML = this._interpolate(this.template.innerHTML, data);
    return newEl;
  }

  _interpolate(tpl, _data) {
    const data = _data;
    tpl = tpl
      .replace(/&gt;/g, '>')
      .replace(/'/g, '\'');
    try {
      return eval('`' + tpl + '`');
    } catch (e) {
      console.error(e);
      return '';
    }
  }
}

Oui, c'est assez vilain, ça utilise du eval et puis ça ne marche qu'avec les browsers qui gèrent ES6 et les strings literal puisque ça passe par un eval… Mais c'est intéressant de voir qu'on peut aujourd'hui faire du templating avec 3 lignes de code seulement.

Hadrien

Hi, I'm a french Web Lead Developer, Front End Architect from Toulouse, France. I've worked for 7 years for Overblog then 2 years with AngularJS. Now, I'm a great fan of Aurelia.io.

Toulouse, France https://hadrien.eu