Creando generadores (scaffolds) con Plop
Tags: productivity, spanish, tooling, webdev
Published: 2022-04-29 15:30:10

En todo proyecto, más pronto que tarde, necesitamos un generador que nos ayude a **mantener la consistencia** entre los archivos que vamos creando (componentes, tests, páginas, etc.). Un generador no solo es necesario para esto, sino para **evitar trabajos repetitivos** a base de *copy & paste* que normalmente **se pueden automatizar**.

[Yeoman](https://yeoman.io/) es una de las herramientas más completas que podemos utilizar para crear generadores, pero a veces simplemente no necesitamos tanto o queremos tener nuestros templates dentro del proyecto en el que los vamos a usar para poder editarlos más fácilmente.

[Plop](https://plopjs.com/) es una herramienta que nos permite crear nuestros propios generadores a partir de unos templates [Handlebars](https://handlebarsjs.com/) y un archivo de configuración en el que establecemos las preguntas que haremos al usuario mediante [Inquirer](https://www.npmjs.com/package/inquirer). Y ya está. **No necesitamos más que templates y preguntas** para rellenarlos.

## Creando un generador

Vamos a ver cómo podemos crear un generador con Plop fácilmente con el ejemplo de un componente.

Lo primero que vamos a necesitar, obviamente, es **instalar Plop** como dependencia de desarrollo:

```bash
npm i -D plop
``` 

Nuestros componentes se encuentran dentro de `src/components/ComponentName/` y dentro de cada una de las carpetas de nuestros componentes tenemos un archivo con el nombre del componente, un archivo de test y un index que sirve como entry point. 

Estructura del proyecto:

```bash
├── package.json
├── src
│   ├── index.ts
│   ├── components
│   │   └── MyComponent
│   │       ├── MyComponent.test.tsx
│   │       ├── MyComponent.tsx
│   │       └── index.ts
```

Somos libres de crear el archivo de configuración de Plop y sus templates donde nos dé la gana, y de hecho la documentación de Plop nos sugiere que creemos el archivo `plopfile.js` de configuración en la raíz del proyecto, pero para mantener todo más organizado y localizable, los vamos a crear dentro de una carpeta llamada generators (también podría llamarse templates, scaffolds, etc.).

La estructura de nuestro proyecto quedará así:

```bash
├── package.json
├── generators
│   ├── component
│   │   ├── component.hbs
│   │   ├── component.test.hbs
│   │   └── index.hbs
│   └── plopfile.js
├── src
│   ├── index.ts
│   ├── components
│   │   └── MyComponent
│   │       ├── MyComponent.test.tsx
│   │       ├── MyComponent.tsx
│   │       └── index.ts
```

### Los templates

Lo primero que vamos a necesitar son los templates en formato Handlebars.  Handlebars nos permite usar mucho más que variables (condicionales, bucles, etc.), pero nuestro generador es tan simple que solo utiliza la variable del nombre del componente y un [helper (`{{pascalCase}}`) que nos proporciona el propio Plop](https://plopjs.com/documentation/#built-in-helpers).

```js
{{!-- component.hbs --}}
export interface {{pascalCase name}}Props {
  /** Prop description */
  someProp?: string;
}

/**
 * Edit me!
 */
export const {{pascalCase name}} = ({}: {{pascalCase name}}Props) => {
  return <p>Hi!</p>
};
```

El resultado de transformar el código anterior sería el siguiente para un componente llamado `MyComponent`, `my-component` o `myComponent`:

```ts
// MyComponent.tsx
export interface MyComponentProps {
  /** Prop description */
  someProp?: string;
}

/**
 * Edit me!
 */
export const MyComponent = ({}: MyComponentProps) => {
  return <p>Hi!</p>
};
```

### Las preguntas y acciones

Una vez que tenemos preparados los templates, podemos pasar a configurar las preguntas que haremos para obtener las variables que utilizamos en ellos.  Para esto utilizaremos el archivo de configuración de Plop.

En este archivo podemos cargar otros generadores o bien configurar directamente las preguntas y acciones, que es lo que vamos a hacer en nuestro ejemplo, ya que de momento solo tenemos un generador.

Para las preguntas utilizaremos el [tipo de Inquirer](https://github.com/SBoudrias/Inquirer.js/#prompt-types) que nos interese dependiendo de si queremos obtener un input, una opción de entre varias, un yes o no, etc.

En nuestro caso solo necesitamos una pregunta de tipo input para que el usuario escriba el nombre del componente y además no necesitamos preocuparnos de validar el formato porque el helper `{{pascalCase}}` que utilizamos se ocupa de ello:

```js
// plopfile.js
module.exports = (plop) => {
  plop.setGenerator('component', {
    description: 'Creates a new component',
    prompts: [
      {
        type: 'input',
        name: 'name',
        message: 'Component name'
      }
    ],
    actions: []
  })
}
```

Por último necesitamos especificar las acciones que queremos ejecutar una vez que hemos obtenido los parámetros necesarios a partir de las respuestas del usuario. 

En nuestro caso solo necesitamos añadir nuevos archivos, para lo que utilizaremos acciones de tipo [`add`](https://plopjs.com/documentation/#add) especificando el destino mediante `path` y el archivo utilizado como template mediante `templateFile`.

```js
// plopfile.js
module.exports = (plop) => {
  plop.setGenerator('component', {
    description: 'Creates a new component',
    prompts: [
      {
        type: 'input',
        name: 'name',
        message: 'Component name'
      }
    ],
    actions: [
      {
        type: 'add',
        path: '../src/components/{{pascalCase name}}/{{pascalCase name}}.tsx',
        templateFile: 'component/component.hbs'
      },
      {
        type: 'add',
        path: '../src/components/{{pascalCase name}}/{{pascalCase name}}.test.tsx',
        templateFile: 'component/component.test.hbs'
      },
      {
        type: 'add',
        path: '../src/components/{{pascalCase name}}/index.ts',
        templateFile: 'component/index.hbs'
      }
    ]
  })
}
``` 

Por último solo nos faltaría añadir un **npm-script** para facilitar la ejecución de nuestro generador mediante un comando. Como hemos creado el archivo de configuración de Plop en una ubicación distinta a la raíz, necesitamos especificar la ubicación de este archivo como parámetro:

```json
{
  "scripts": {
    "generate": "plop --plopfile ./generators/plopfile.js"
  }
}
```

De forma bastante sencilla y rápida podemos tener generadores que podemos actualizar fácilmente en cualquier momento según las necesidades que vayan surgiendo. Plop además no se limita a crear archivos, sino que también **puede actualizar los existentes**, con lo que nos puede servir para mantener nuestra base de código homogénea siempre que hagamos cambios en los templates.

That's all. Happy templating!