paint-brush
Cómo crear un motor de plantillas usando JavaScriptpor@shadowtime2000
12,583 lecturas
12,583 lecturas

Cómo crear un motor de plantillas usando JavaScript

por shadowtime20004m2020/12/20
Read on Terminal Reader
Read this story w/o Javascript

Demasiado Largo; Para Leer

Un tutorial sobre cómo crear un motor de plantillas JS con análisis y precompilación.
featured image - Cómo crear un motor de plantillas usando JavaScript
shadowtime2000 HackerNoon profile picture

Si te gusta este artículo, te sugiero que le eches un vistazo a mis otros en mi blog .

Hola, soy yo, @shadowtime2000 , uno de los mantenedores de Eta , un motor de plantillas rápido e integrable. En este tutorial, mostraré cómo crear un motor de plantilla de JavaScript isomorfo (navegador/nodo).

El diseño

El diseño inicial del motor de plantillas será bastante simple. Simplemente interpolará valores de un

 data
objeto. usará
 {{valueName}}
para interpolar valores.

Representación sencilla

Primero, creemos una función de representación simple que toma la plantilla y los datos y representará el valor.

 var render = ( template, data ) => { return template.replace( /{{(.*?)}}/g , (match) => { return data[match.split( /{{|}}/ ).filter( Boolean )[ 0 ]] }) }

Básicamente, todo lo que hace es buscar cualquier cosa que esté entre corchetes y lo reemplaza con el nombre dentro

 data
. Puede escribir sus plantillas de esta manera y las tomará del objeto de datos.

 Hi, my name is {{name}} !
 render( "Hi, my name is {{name}}!" , { name : "shadowtime2000" });

Pero hay un problema, no puedes tener espacios en las interpolaciones.

 render( "Hi, my name is {{ name }}!" , { name : "shadowtime2000" }) /* Hi, my name is undefined! */

Esto requiere que tenga espacios dentro del objeto de datos, que no está tan limpio. Podemos hacer que permita espacios recortando los espacios en blanco iniciales y finales del nombre de los datos antes de la interpolación.

 var render = ( template, data ) => { return template.replace( /{{(.*?)}}/g , (match) => { return data[match.split( /{{|}}/ ).filter( Boolean )[ 0 ].trim()] }) }

Esto es bastante bueno, pero para plantillas más grandes no sería tan rápido porque tiene que analizarlo todo el tiempo. Es por eso que muchos motores de plantillas admiten la compilación, donde la plantilla se compila en una función JS más rápida que puede tomar los datos e interpolarlos. Agreguemos compilación a nuestro motor de plantillas, pero antes de hacerlo, debemos agregar una función de análisis especial.

análisis

Dado que el análisis puede ser un poco aburrido, reutilicemos un poco de código de otro motor de plantilla JS. Habría usado el motor de análisis Eta, pero se ha optimizado extremadamente y puede ser bastante confuso para las personas. Por lo tanto, usemos otro código de análisis de motor de plantilla JS popular, mde/ejs . Recuerde atribuirlos al motor de análisis.

 var parse = ( template ) => { let result = /{{(.*?)}}/g .exec(template); const arr = []; let firstPos; while (result) { firstPos = result.index; if (firstPos !== 0 ) { arr.push(template.substring( 0 , firstPos)); template = template.slice(firstPos); } arr.push(result[ 0 ]); template = template.slice(result[ 0 ].length); result = /{{(.*?)}}/g .exec(template); } if (template) arr.push(template); return arr; }

Lo que esto hace básicamente es repetir la ejecución del patrón de expresiones regulares en la plantilla y agregar las cosas a una estructura de datos. Así es como se vería esa estructura de datos:

 [ "Hi my name is " , "{{ name }}" , "!" ]

Ahora que hemos terminado el análisis, pasemos a la compilación.

Compilacion

Echemos un vistazo rápido a lo que generaría la compilación. Imagina que ingresas esta plantilla:

 Hi my name is {{ name }} !

Te dará esta función:

 function ( data ) { return "Hi my name is " +data.name+ "!" ; }

Primero creemos una función para analizar y luego creemos una cadena que se pueda usar. Primero tenemos que analizar la plantilla.

 const compileToString = ( template ) => { const ast = template; }

También tenemos que crear una cadena que se utilizará como función.

 const compileToString = ( template ) => { const ast = template; let fnStr = `""` ; }

La razón por la que usamos comillas al principio es porque cuando se compilan las plantillas y demás, todas comenzarán con un

 +
. Ahora tenemos que iterar sobre el AST.

 const compileToString = ( template ) => { const ast = template; let fnStr = `""` ; ast.map( t => { // checking to see if it is an interpolation if (t.startsWith( "{{" ) && t.endsWith( "}}" )) { // append it to fnStr fnStr += `+data. ${t.split( /{{|}}/ ).filter( Boolean )[ 0 ].trim()} ` ; } else { // append the string to the fnStr fnStr += `+" ${t} "` ; } }); }

La parte final de esta función es devolver la cadena de función.

 const compileToString = ( template ) => { const ast = template; let fnStr = `""` ; ast.map( t => { // checking to see if it is an interpolation if (t.startsWith( "{{" ) && t.endsWith( "}}" )) { // append it to fnStr fnStr += `+data. ${t.split( /{{|}}/ ).filter( Boolean )[ 0 ].trim()} ` ; } else { // append the string to the fnStr fnStr += `+" ${t} "` ; } }); return fnStr; }

Entonces, si toma esta plantilla:

 Hi my name is {{ name }} !

Devolverá esto:

 "" + "Hello my name is " +data.name+ "!"

Ahora que esto está hecho, crear una función de compilación es relativamente simple.

 const compile = ( template ) => { return new Function ( "data" , "return " + compileToString(template)) }

Ahora hemos completado la compilación de nuestro motor de plantillas.

Terminando

En este tutorial mostré cómo:

  1. Implementar una función de renderizado simple
  2. Comprender un motor de análisis adaptado de EJS
  3. Iterar sobre el AST para crear funciones de plantilla compiladas rápidamente