Sistema de templates realizado en Node.js

Este es un sistema sencillo de templates realizado con Node que hice como paso intermedio para adaptar nuestro ecosistema de plantillas a Twig, me permitió ir limpiando código duplicado e identificando bloques de forma rápida. La escribí desde cero y aunque es muy sencilla, funciona realmente bien. Está pensada para trabajar con plantillas php y necesita las siguientes carpetas:

/templates es rellenada automáticamente con las plantillas generadas.

 /sources los archivos de texto con la extensión .tpl que generan las templates
/base  Las plantillas base de las que derivan las demás.
 /mods  los módulos php para trabajar con los bloques

FORMATO DE LAS PLANTILLAS BASE PHP:

Se dividen los bloques de código susceptible de ser modificado con comentarios php con un nombre arbitrario con el siguiente formato (se pueden anidar):

<?php#bloque?> 

    <?php#subbloque?>

    <?php#/subbloque?> 

<?php#/bloque?>
(Los tags de inicio y fin de bloque se borran de las templates .php generadas)
Si fuera necesario usarlos como “ancla” para añadir contenido se pueden poner bloques vacíos donde sea necesario sin problema, o añadir bloques en cualquier momento que surja la necesidad.
Para cada plantilla que queramos crear, se añade un archivo de texto con la extension .tpl en /sources, en la primera linea indicamos la template maestra, en las siguientes, las modificaciones que queremos hacerle.
El motor coge cada cada archivo  sources/*.tpl y lo transforma en un templates/*.php con el mismo nombre, relleno con el código de la plantilla base indicada y modificado con lo que se haya pedido en el archivo tpl.
 
FORMATO DEL ARCHIVO .TPL:
Primera linea>> el nombre (sin la extensión) de la plantilla base
Siguientes líneas>> modificaciones a la plantilla base.
Las modificaciones se indican así: bloque (>|+|-)  modulo, que indican sustituir, añadir o quitar elementos de la plantilla base o de los módulos cargados.
El motor interpreta linea a linea secuencialmente, modificando el código resultado de aplicar la linea anterior, o sea que si en una línea cargamos un módulo, en las siguientes podemos modificar sus bloques cargando nuevos módulos en ellos y así  hasta el infinito (o se acaben las lineas del archivo .tpl, lo primero que ocurra 🙂 ).

Una plantilla podría tener el siguiente contenido:

fr_base //coge la plantilla base/fr_base.php
head > head_orange // cambia el bloque head por el contenido de mods/head_orange.php
main + thumbs  // a continuación  del bloque “main” añade el modulo mods/thumbs.php
boton – //borra el bloque boton, que puede estar en cualquiera de los bloques anteriores

 
Lo que hace la rutina es leer los .tpl, cargar el código del php indicado en la primera linea y a continuación interpreta linea a linea el resto del archivo modificando el código del php en consecuencia. Básicamente lo que hace es buscar con una expresión regular el bloque indicado a la izquierda del operador y sustituirlo o añadir el archivo php indicado  ala derecha, o borrarlo sin mas.
Toda la magia del sistema está en esta Regexp:
RegExp("<\\?php#" + origen + "\\?>((?!<\\?php#\\/\\?>).|\\r\\n|\\r|\\n)*<\\?php#\\/" + origen + "\\?>");

“Origen” es el bloque indicado a la izquierda del operador (>, + o -), con esto seleccionamos el contenido del bloque y el resto es trivial.  Permite mantener los comentarios usuales en los phps porque se limita a buscar comentarios que estén cerrados con “/”

 A continuación, el código.  Son menos de 100 lineas de código y aun se podría optimizar más, aun tiene partes del prototipado, aunque no sé si volveré a el porque ya ha cumplido su cometido, y tampoco era su función complicarse mucho más.
var fs = require('fs');
var glob = require('glob');

compila();

function compila() {
    const util = require('util');
    const glob = util.promisify(require('glob'));
    glob('../cdn.landing.engine/sources/**/*.tpl', function(err, files) {
        files.forEach(function(element) {
            lee_plantilla(element);
        });
    });
}

function lee_plantilla(template) {
    fs.readFile(template, (err, data, isdone) => {
        data = data.toString().replace(" ", "");
        if (data.charAt(data.length - 1) != "\n\n") {
            data += "\n"
        }
        if (data.split(/[\r|\n]+/).length > 1) {
            var index = 0;
            data.split(/[\r|\n]+/).forEach(function(line) {
                index++;
                switch (index) {
                    case 1:
                        codigo = fs.readFileSync("..engine/sources/base/" + line + ".php");
                        break;
                    case data.split(/[\r|\n]+/).length:
                        escribe_php(codigo.toString(), template.split("/").pop())
                        break;
                    default:
                        codigo = interpreta(codigo.toString(), line, template);
                        break;
                }
            })
        } else {
            fs.readFile("../cdn.landing.engine/templates_sources/base/" + data.toString() + ".php", (err, data, isdone) => {
                escribe_php(data.toString(), template.split("/").pop())
            })
        }
    })
}

function escribe_php(contenido, destino) {
    fs.writeFile("../engine/templates/" + destino.replace(".tpl", ".php"), limpiatags(contenido), function(err) {
        console.log("error" + err)
    });
}

function interpreta(codigo, linea, template) {
    if (linea.indexOf('+') > -1) {
        divisor = linea.indexOf('+');
        var operador = "+";
    }
    if (linea.indexOf('>') > -1) {
        divisor = linea.indexOf('>');
        var operador = ">";
    }
    if (linea.indexOf('-') > -1) {
        divisor = linea.indexOf('-');
        var operador = "-";
    }
    var origen = linea.slice(0, divisor);
    var destino = linea.slice(divisor + 1, linea.length);
    var formula = new RegExp("<\\?php#" + origen + "\\?>((?!<\\?php#\\/\\?>).|\\r\\n|\\r|\\n)*<\\?php#\\/" + origen + "\\?>");
    var a = codigo.match(formula).slice(0, -1);
    new_code = fs.readFileSync("../engine/sources/mods/" + destino + ".php");
    switch (operador) {
        case "+":
            return codigo.replace(a, (a + new_code));
            break;
        case ">":
            return codigo.replace(a, new_code);
            break;
        case "-":
            return codigo.replace(a, "");
            break;
    }
}

function limpiatags(codigo) {
    var formula = new RegExp("<\\?php#((?!>).)*\\?>", "g");
    var a = codigo.match(formula);
    if (a) {
            for (var i = 0; i < a.length; i++) {
                codigo = codigo.replace(a[i], "")
            }
    }
     return codigo.replace(/^(?:[\t ]*(?:\r?\n|\r))+/gm, "")
}

Leave a Reply

Your email address will not be published. Required fields are marked *