Plantillas con Plop

29 de octubre de 2022

Coffee by Louis Hansel
Photo by Louis Hansel on Unsplash
Todos hemos estado en una situación similar, en la cual de forma constante generamos nuevos archivos de código y a veces incluso caemos en la tentación de duplicar un archivo y cambiar su contenido base.

De igual forma, hemos definido una arquitectura base junto a nuestro equipo que solicitamos mantener y que a veces no es fácil de hacerlo.

Para ello, podemos utilizar una herramienta pequeña, pero poderosa, llamada Plop.

Plop es (…) un “micro-generator framework”. Ahora, lo llamo así porque es una pequeña herramienta que le brinda una forma simple de generar código o cualquier otro tipo de archivos de texto plano de forma consistente. Vemos, todos creamos estructuras y patrones en nuestro código (rutas, controladores, componentes, ayudantes, etc.). Estos patrones cambian y mejoran con el tiempo, por lo que cuando necesita crear un NUEVO patrón, no siempre es fácil encontrar los archivos en su base de código que representan la “mejor práctica” actual. Es ahí donde plop lo salva.

#.Iniciando con Plop

Para comenzar, vamos a crear un proyecto simple e instalar Plop como una dependencia de desarrollo en nuestro proyecto.

mkdir test-plop
cd test-plop
npm init -y
npm install plop --save-dev

Con esto, ya tenemos plop instalado en nuestro proyecto y podemos comenzar a utilizarlo. Para ello, vamos a crear un archivo llamado plopfile.js en la raíz de nuestro proyecto.

module.exports = function (plop) {
  plop.setGenerator('basics', {
      description: 'this is a skeleton plopfile',
      prompts: [],
      actions: []
  });
};

También agregaremos un script en nuestro package.json para ejecutar plop:

// ...
{
  "scripts": {
    "plop": "plop"
  }
}
// ...

Y lo ejecutamos solo para asegurarnos que todo está funcionando como debería:

npm run plop
plop-test@1.0.0 plop
plop

#.Generando el primer generador: un componente de React

Digamos que nuestro equipo ha decidido utilizar React y ha definido una arquitectura base para nuestros componentes. En este caso, vamos a crear un generador que nos permita crear un componente de React con la siguiente estructura:

  • Utilizará la extensión .jsx.
  • Utilizará PropTypes para la definición de tipos.
  • Tendrá un nombre en PascalCase.
  • Tendrá un directorio con el mismo nombre.
  • Tendrá un archivo index.jsx que exportará el componente.
  • Tendrá un archivo [ComponentName].copy.json que contendrá el texto plano que utiliza el componente.
  • Tendrá un archivo [ComponentName].module.scss que exportará los estilos del componente.

Para ello, vamos a modificar nuestro archivo plopfile.js y agregaremos el nombre de nuestro generador, una descripción y las preguntas que queremos hacerle al usuario para la generación de nuestros componentes.

module.exports = function (plop) {
  plop.setGenerator('component', {
    description: 'Create a new component',
    prompts: [
      {
        type: 'input',
        name: 'name',
        message: 'What is your component name?',
      },
    ],
    actions: [
      {
        type: 'add',
        path: 'src/components/{{pascalCase name}}/index.jsx',
        templateFile: 'plop-templates/component/index.jsx.hbs',
      },
      {
        type: 'add',
        path: 'src/components/{{pascalCase name}}/{{pascalCase name}}.copy.json',
        templateFile: 'plop-templates/component/copy.js.hbs',
      },
      {
        type: 'add',
        path: 'src/components/{{pascalCase name}}/{{pascalCase name}}.module.scss',
        templateFile: 'plop-templates/component/component.module.scss.hbs',
      },
    ],
  });
};

Vemos que estamos haciendo referencia a un directorio llamado plop-templates que aún no existe. Este directorio contendrá los archivos que utilizaremos como plantillas para la generación de nuestros componentes. Para ello, vamos a crearlo y agregar los archivos que utilizaremos como plantillas. También crearemos el directorio src/components que contendrá nuestros componentes.

mkdir plop-templates
mkdir src
mkdir src/components

Y creamos nuestros templates o plantillas para la generación de nuestras 3 plantillas. Estas plantillas se escriben en Handlebars.

// plop-templates/component/index.jsx.hbs
import React from 'react';
import PropTypes from 'prop-types';
import COPY from './{{pascalCase name}}.copy.json';

import styles from './{{pascalCase name}}.module.scss';

const {{pascalCase name}} = () => {
  return (
    <div className={styles.container}>
      <h1>{COPY.title}</h1>
    </div>
  );
};

{{pascalCase name}}.propTypes = {
  // property: PropTypes.string.isRequired
};

export default {{pascalCase name}};
// plop-templates/component/copy.js.hbs
{
  "title": "{{pascalCase name}} title"
}
// plop-templates/component/component.module.scss.hbs
.container {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  height: 100vh;
}

Con esto, podemos proceder a ejecutar nuestro generador y ver el resultado.

npm run plop

> plop-test@1.0.0 plop
> plop

? What is your component name? Test
✔  ++ /src/components/Test/index.jsx
✔  ++ /src/components/Test/Test.copy.json
✔  ++ /src/components/Test/Test.module.scss

Y si exploramos el archivo generado, veremos que el name que ingresamos en la consola se ha utilizado en la generación de nuestros archivos, y utilizando PascalCase.

// src/components/Test/index.jsx
import React from 'react';
import PropTypes from 'prop-types';
import COPY from './Test.copy.json';

import styles from './Test.module.scss';

const Test = () => {
  return (
    <div className={styles.container}>
      <h1>{COPY.title}</h1>
    </div>
  );
};

Test.propTypes = {
  // property: PropTypes.string.isRequired
};

export default Test;

#.Próximos pasos

Con Plop podemos generar cualquier tipo de archivo, desde componentes de React hasta archivos de configuración, stories o hasta tests.

Igualmente, Plop permite la creación de prompts personalizados, los que por debajo utilizan Inquirer.js. Esto nos permite crear preguntas que nos permitan generar archivos de acuerdo a las respuestas que el usuario ingrese o incluso un flujo de preguntas, validaciones, etc.

Por ejemplo, si quisieramos agregar una validación para que el nombre del componente contenta al menos 3 caracteres, podríamos hacerlo de la siguiente manera y agregaríamos el siguiente prompt, llamado name:

const name = {
  type: 'input',
  name: 'name',
  message: 'What is your component name?',
  validate: (input) => {
    if (input.length >= 3) {
      return true;
    }

    return 'name should be at east 3 characters long.';
  },
};

module.exports = function (plop) {
  plop.setGenerator("components", {
    description: "generate a component",
    prompts: [name],
// ... el resto quedaría igual

y al ejecutarlo, podemos ver la nueva validación:

npm run plop

> plop-test@1.0.0 plop
> plop

? What is your component name? a
>> name should be at east 3 characters long.

El resto, funcionaría exactamente igual, solo que agregamos una capa nueva para la validación. De ahí en adelante, el cielo es el límite 🌁🙌.