Plantillas Avanzadas de Javascript

En Javascript podemos crear funciones que controlen plantillas de texto, es decir, una función a la que directamente se le pase un texto con inyecciones de código y esta sea capaz de procesar manualmente. En este artículo explicaremos como definir estas funciones y sacar el mayor provecho.

Plantillas de Texto

Una plantilla de texto es una cadena de texto que usa la comilla especial en lugar de la comilla doble o simple. Esta comilla especial es la comilla inversa, y podemos inyectar variables directamente sobre ella. En el siguiente ejemplo se muestra una cadena de texto que inyecta el nombre y la edad de una persona para generar un texto formateado.

const nombre = "Dragon Nomada";
const edad = 123;

const texto = `Hola ${nombre}, ¿Es cierto que tienes ${edad} años?`;

console.log(texto); // Hola Dragon Nomada, ¿Es cierto que tienes 123 años?

En el ejemplo anterior, hemos generado una variable llamada texto, a partir de la inyección de otras dos variables, sobre una plantilla de texto.

Partes de la Plantilla de Texto

Para entender mejor el funcionamiento de las plantillas de texto, debemos separar mentalmente las partes que componen la plantilla. Es decir, una plantilla se separará en las partes de texto y las inyecciones, cada parte de texto será delimitada por cada inyección. El el siguiente ejemplo, se muestra conceptualmente la separación de la plantilla.

`Hola ${name}, ¿Es cierto que tienes ${age} años?`

// TEXTO INYECCIÓN TEXTO INYECCIÓN TEXTO

// TEXTO:       "Hola "
// INYECCIÓN:   name 
// TEXTO:       ", Es cierto que tienes "
// INYECCIÓN:   age
// TEXTO:       " años?"

// strings: 
["Hola ", ", Es cierto que tienes ", " años?"]

// values: 
[name, age]

Para analizar el código anterior, debemos partir mentalmente textos e inyecciones. Los textos serán agrupados como los “strings” de la plantilla y las inyecciones serán agrupadas como los “values” de la plantilla.

Función Procesadora de Plantillas de Textos

Dado que cualquier plantilla podrá separarse en “strings” y “values”, podemos definir una función que reciba para una plantilla de texto, las partes de texto y los valores inyectados. Esto nos permitirá devolver un valor basado en la plantilla.

Por ejemplo, en el siguiente código se muestra una función que recibe las partes de texto y los valores inyectados y devuelve la suma de los valores inyectados y la concatenación de las partes de texto.

function ConcatenaYSuma(strings, ...values) {
  const suma = values.reduce((suma, value) => suma + value, 0);
  const texto = strings.join("*");
  return [texto, suma];
}

const [texto, suma] = ConcatenaYSuma`a ${1} b ${2} c ${2 ** 10} d ${Math.cos(0)}`;

console.log(texto); // "a * b * c * d *"

console.log(suma); // 1 + 2 + 1024 + 1 = 1028

Analicemos por parte el código:

  1. Se define una función llamada ConcatenaYSuma, que recibe los “strings” y los “values” de la plantilla. Estos últimos en forma esparcida.
  2. Se ejecuta la función ConcatenaYSuma poniendo directamente la plantilla a evaluar sobre la función. A esto le llamaremos: “Llamada especial a Plantillas Funcionales”, o en términos prácticos “Procesar la plantilla”.
  3. La función nos devuelve un arreglo de [texto, suma] con los valores procesados de la plantilla. Entonces deconstruimos dichos valores en las variables de igual nombre [texto, suma].
  4. Al imprimir el texto concatenado, observamos que ha unido cada parte separada por las inyecciones y las ha unido bajo el separador asterisco. Nota que al final hay una inyección y esta es considerada. Entonces, cada asterisco es donde debería ir cada valor.
  5. Al imprimir la suma calculada, obtenemos la suma de todos los valores inyectados, si algún valor inyectado no fuera un número, entonces produciría un NaN (Not a Number).

Aplicación: Plantilla generadora de objetos

Veamos un ejemplo de aplicación al uso de plantillas. Definamos un procesador de plantillas capaz de construir un objeto, tomando las claves como el valor anterior al valor inyectado. Por ejemplo, la plantilla a ${123} b ${true} debería producir el objeto { a: 123, b: true }.

function makeObject(strings, ...values) {
  let object = {};
  for (let i = 0; i < strings.length; i++) {
    const key = strings[i].trim();
    if (key) {
      const value = values[i];
       object[key] = value;
    }
  }
  return object;
}

const person = makeObject`nombre ${"Dragon Nomada"} age ${123}`;

const test = makeObject`foo ${() => `Happy`} bar ${false}`;

console.log(person); // { "nombre": "Dragon Nomada", "age": 123 }
console.log(test); // { "foo": [Function], "bar": false }
console.log(test.foo()) // "Happy"

Uso Avanzado: Procesador de Estilos CSS

Una de las aplicaciones avanzadas para dominar las plantillas de texto, es construir un generador de estilos. Para entender el ejemplo, se debe dominar el uso de meta-funciones o funciones de orden superior, ya que en el código se apreciará su uso (una función que devuelve otra función).

function css(strings, ...values) {
  return context => {
    let text = "";
    for (let i = 0; i < strings.length; i++) {
      text += strings[i];
      if (typeof values[i] === "function") {
        text += `${values[i](context || {})}`;
      } else {
        text += `${values[i] || ""}`;
      }
    }
    return text;
  }
}

const BoxStyle = css`
  padding: ${context => `${context.padding || "0px"}`};
  margin: ${context => `${context.margin || "0px"}`};
  box-sizing: border-box;
`

const FlexRow = css`
  diplay: flex;
  flex-flow: ${context => context.reverse ? "row-reverse" : "row"} ${context => context.wrap ? "wrap" : "nowrap"};
  justify-content: ${context => context.justify || "between"};
  align-items: ${context => context.items || "center"};
  align-content: ${context => context.content || "center"};
`;

BoxStyle({
  padding: "12px"
})
//   padding: 12px;
//   margin: 0px;
//   box-sizing: border-box;

BoxStyle({
  padding: "8px",
  margin: "24px"
})
//   padding: 8px;
//   margin: 24px;
//   box-sizing: border-box;

FlexRow({
  justify: "center",
  items: "end"
})  
//   diplay: flex;
//   flex-flow: row nowrap;
//   justify-content: center;
//   align-items: end;
//   align-content: center;

Y así en 14 líneas de código, podemos definir un procesador de estilos, el cuál permita inyectar mediante objetos los valores que serán reemplazados en la plantilla.

Conclusiones

Las Plantillas de Texto en Javascript nos permiten generar textos a los que podemos inyectarles valores. Podemos definir una Función Procesadora de Plantillas de Texto para poder generar código avanzado. Así es como funciona GraphQL. Las plantillas nos permiten definir modelos de texto, e interactuar con valores inyectados en tiempo de ejecución. Esta mezcla nos lleva a una nueva dimensión de programación, es decir, a una metaprogramación.

Las aplicaciones son enormes, tan sólo con algunas ideas básicas, podemos generar un pseudolenguaje propio, que por ejemplo, construya programas completos, como se muestra a continuación.

const app = createApp`
  source: https://randomuser.me/api
  query: ${config => config.query}
  context: ${data => data.results[0]}
  id: ${config.id}
  view:
  --
    <h1 onclick="@click">
      Hola me llamo #firstname y tengo #age años
    </h1>
  --
  data:
  --
    #firstname ${user => `${user.name.first} ${user.name.last}`}
    #age ${user => user.age}
  --
  actions: 
  --
    @click ${(event, user) => alert(`Hola ${user.name.first}`)}
  --
`

const instanceOne = app({
  id: "my-app-1"
  query: "seed=123"
});

document.body.appendChild(instanceOne.root);

instanceOne.fetch();

const instanceTwo = app({
  id: "my-app-2"
  query: "seed=456"
});

document.body.appendChild(instanceTwo.root);

instanceOne.fetch();

En el ejemplo anterior, hemos definido un procesador de interfaces, el cual toma las diferentes partes de una aplicación, como la fuente de datos, la definición de la vista, los valores y eventos generados. Al final se pueden crear instancias de aplicaciones de forma rápida.

Ahora ya sabes cómo puedes crear tu próximo framework. Espero que te haya gustado el artículo.

1 Comment.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *