Unlimited Plugins, WordPress themes, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Code
  2. Web Development

Construye tu propio generador de Yeoman

by
Difficulty:IntermediateLength:LongLanguages:

Spanish (Español) translation by Mayra Reynoso (you can also view the original English article)

Los generadores de Yeoman son los que le dan flexibilidad a su plataforma, permitiéndote reutilizar la misma herramienta para cualquier tipo de proyecto en el que puedas estar trabajando, JavaScript u otro, y eso sin mencionar la enorme biblioteca de más de 400 generadores aportados por la comunidad. Aunque a veces, puedes tener alguna configuración específica que te guste emplear en tus propios proyectos y para situaciones como esta, tiene sentido abstraer toda el boilerplate en tu propio generador.

En este artículo, vamos a construir un generador Yeoman que nos permitirá construir un sitio de una sola página, una fila a la vez, es un ejemplo bastante simple, pero nos permitirá cubrir muchas de las características más interesantes que ofrece Yeoman.

Preparándonos 

Voy a suponer que tienes la configuración Node.js, si no, puedes obtener la instalación desde aquí. Además de eso, tendremos que tener Yeoman instalado, así como el generador para la creación de generadores. Puedes lograr esto vía el siguiente comando a npm:

Finalmente, necesitamos crear el proyecto real, así que navega a la carpeta de tu elección y ejecuta:

Esto iniciará el generador y te hará algunas preguntas como el nombre del proyecto y tu cuenta de GitHub, para este artículo, voy a nombrar a mi generador one page.

La estructura de archivos

No te alarmes por el gran número de archivos generados por el comando, todo tendrá sentido en un momento.

File Tree for Yeoman GeneratorFile Tree for Yeoman GeneratorFile Tree for Yeoman Generator

El primer par de archivos son dotfiles para varias cosas como Git y Travis CI, tenemos un archivo package.json para el generador en sí, un archivo readme y una carpeta para pruebas. Además de eso, cada comando en nuestro generador obtiene una carpeta con un archivo index.js y una carpeta para plantillas.

El archivo index.js necesita exportar el objeto generador que será ejecutado por Yeoman. Voy a borrar todo dentro del generador para que podamos empezar de cero, así debe verse el archivo después de esto:

Un generador Yeoman puede extenderse desde dos opciones preconstruidos diferentes: el generador Base, del que se puede ver que lo hereda, o el generador NamedBase, que en realidad es lo mismo, excepto que agrega un único parámetro que el usuario puede establecer cuando llama a tu generador. No importa demasiado cuál elijas, siempre puedes añadir parámetros manualmente si necesitas más.

Todo lo que este código está haciendo es crear este objeto generador y exportarlo, Yeoman recuperará el objeto exportado y lo ejecutará. La forma en que se ejecuta, es primero llamando al método constructor para configurar el objeto y, a continuación, pasará por todos los métodos que creaste en este objeto (en el orden en que los creaste) y ejecutarlos uno a la vez. Esto significa que realmente no importa como llames a las funciones y te da la flexibilidad de nombrarlas como cosas que tengan sentido para ti.

Ahora, la otra cosa que debes saber es que Yeoman tiene sus propias funciones para tratar con el sistema de archivos que en verdad te ayudan con las rutas. Todas las funciones que normalmente usarías como mkdir, read, write, copy, etc., se han proporcionado, pero Yeoman utilizará el directorio de plantillas en la carpeta de este comando como la ruta para leer datos y la carpeta en la que el usuario está ejecutando el generador como la ruta raíz para la salida de archivos. Esto significa que ni siquiera tienes que pensar en las rutas completas cuando trabajas con archivos, simplemente puedes ejecutar copy y yeoman se encargará de las dos distintas ubicaciones por ti.

Obtener información

Yeoman te permite agregar preguntas a tu generador para que puedas recibir información del usuario y personalizar su comportamiento adecuadamente. Vamos a tener dos preguntas en nuestro generador. La primera es el nombre del proyecto y la segunda es si se incluye o no una sección de prueba como ejemplo.

Para lograr esto, agregaremos una función que solicitará al usuario estas dos piezas de información y luego almacenaremos los resultados en nuestro propio objeto:

La primera línea dentro de la función establece una variable done del método async del objeto. Yeoman intenta ejecutar los métodos en el orden en que se definen, pero si ejecuta cualquier código async, la función se cerrará antes de que se complete el verdadero trabajo y Yeoman iniciará la siguiente función antes de tiempo. Para evitar esto, puedes llamar al método async, que hará un callback y, después, Yeoman esperará para pasar a la siguiente función hasta que se ejecute ese callback, lo cual puedes ver que lo hace al final, después de incitar al usuario.

La siguiente línea donde acabamos de registrar yeoman, ese es el logotipo de Yeoman que viste cuando corrimos el generador Yeoman un poco antes. Después, definimos una lista de indicaciones, cada indicación tiene un tipo, un nombre y un mensaje. Si no se especificó ningún tipo, el valor predeterminado será 'input', que es para la entrada de texto estándar. Hay una gran cantidad de tipos de entrada interesantes como listas, casillas de verificación y contraseñas, puedes ver la lista completa aquí. En nuestra aplicación, estamos usando una entrada de texto y un tipo de entrada 'confirm' (sí/no).

Con la matriz de preguntas lista, podemos pasarla al método prompt junto con una función de callback. El primer parámetro de la función de callback es la lista de respuestas recibidas del usuario. Luego, adjuntamos esas propiedades a nuestro objeto principal y llamamos al método done para pasar a la siguiente función.

Scaffolding nuestra aplicación

Nuestra aplicación tiene el esqueleto exterior, que incluirá el encabezado, menú, pie de página y algunos CSS, y luego tenemos el contenido interno que irá en su propio directorio junto con un archivo CSS personalizado por sección. Esto te permitirá establecer propiedades globales en el archivo principal y personalizar cada fila por sí misma.

Comencemos con el archivo de encabezado, simplemente crea un nuevo archivo en el directorio de plantillas llamado _header.html con lo siguiente:

Los nombres de archivo no necesitan comenzar con un guión bajo, pero es una práctica recomendada para diferenciar el nombre del nombre de salida final para que puedas saber con cuál estás tratando. Asimismo, puedes ver que además de incluir nuestro archivo CSS principal, también estoy incluyendo la Semantic UI Library como una cuadrícula para filas y para el menú (por no mencionar el gran estilo).

Otra cosa que puedes haber notado, es que utilicé un marcador de posición estilo EJS para el título y se rellenará en tiempo de ejecución. Yeoman utiliza plantillas de Underscore (bueno guión de IO) y como tal puedes crear tus archivos así y los datos se generarán en tiempo de ejecución.

A continuación, vamos a crear el pie de página (pon esto en un archivo llamado _footer.html en la carpeta de plantillas):

Simplemente cierra el documento HTML para nosotros, ya que no tendremos ningún JavaScript en nuestra aplicación. El último archivo HTML necesario para el scaffolding externo es el menú, vamos a generar los enlaces reales en tiempo de ejecución, pero podemos escribir el contenedor externo en un archivo de plantilla llamado _menu.html:

Es un div simple con algunas clases proporcionadas por la Semantic UI. Vamos a tirar de los enlaces reales basados en las secciones generadas más adelante. A continuación, vamos a crear el archivo _main.css:

Sólo un poco de código para ayudar con el posicionamiento del menú y cambiar la fuente, pero aquí es donde puedes poner los estilos predeterminados que desees en todo el documento. Ahora tenemos que hacer plantillas para las secciones individuales y los archivos CSS que lo acompañan, estos son archivos bastante básicos ya que los dejaremos bastante en blanco para que el usuario los personalice, así que dentro de _section.html añade lo siguiente:

Sólo un div externo con un identificador único que generaremos basado en el nombre de la sección y una clase para el Semantic grid system. Luego dentro, acabo de agregar una etiqueta H1 que de nuevo sólo contendrá el nombre de la sección. Después, vamos a crear el archivo CSS, así que crea un archivo llamado section.css con lo siguiente:

Esto es más bien un marcador de posición con el mismo identificador que el contenedor externo, pero es común cambiar el fondo de cada fila para separar el contenido, por lo que agregué esa propiedad para mayor comodidad. Ahora, sólo para completar, vamos a crear un archivo llamado _gruntfile.js pero lo llenaremos más adelante y vamos a llenar el archivo _package.json que se proporcionó:

Es un poco spoiler en cuanto a lo que vamos a hacer más adelante, pero son las dependencias que necesitaremos para nuestras tareas Grunt.

Con todos estos archivos listos, vamos a agregar los métodos para el scaffold de un nuevo proyecto. Así que de regreso en nuestro archivo index.js, justo después del método promptUser, vamos a agregar una función para crear todas las carpetas que necesitaremos:

Agregué una carpeta de la app y dentro, dos directorios más: css y sections, aquí es donde el usuario final construirá su aplicación. También creé una carpeta build que es donde las diferentes secciones y archivos CSS se compilarán juntos e integrados en un solo archivo.

A continuación, agreguemos otro método para copiar algunas de nuestras plantillas:

Aquí usamos dos nuevos métodos, copy y template, que son bastante similares en función. copy tomará el archivo del directorio de plantillas y lo moverá a la carpeta de salida, utilizando las rutas proporcionadas. template hace lo mismo, excepto que antes de escribir en la carpeta de salida se ejecutará a través de la función de plantillas de Underscore junto con el contexto con el fin de rellenar los marcadores de posición.

No copié el menú todavía porque para eso, necesitamos generar los enlaces, pero no podemos generar los enlaces hasta que agreguemos la sección de demostración. Así que el siguiente método que creemos, agregará la sección de demostración:

Otra función con la que puedes no estar familiarizado es la función classify, que te proporciona Underscore Strings. Lo que hace es que toma una cadena y crea una versión "clase" de ella, eliminará cosas como espacios y creará una versión CamelCase, adecuada para cosas como clases HTML e IDs; underscored hace lo mismo, excepto en lugar de hacerlo CamelCase los hace snake_case. Además de eso, todas son cosas que hemos hecho en la función anterior, el único otro detalle que vale la pena mencionar es que estamos anteponiendo una marca de tiempo, tanto para mantener los archivos únicos, como para ordenar. Cuando cargamos los archivos, están en orden alfabético por lo que tener el tiempo como prefijo los mantendrá en orden.

El último archivo que necesita ser copiado es el archivo menú.html, pero para ello, necesitamos generar los enlaces. El problema es que realmente no sabemos en esta etapa qué archivos se generaron antes o si el usuario agregó secciones manualmente. Así que para construir el menú, necesitamos leer desde la ruta de salida y después de leer las secciones que existen allí, necesitamos construir los enlaces del menú. Hay un puñado de nuevas funciones que aún no hemos utilizado, por lo que iremos línea por línea:

La primera línea lee en el HTML externo del menú y luego creé una cadena de plantilla que podemos usar para cada vínculo. Después de eso, utilicé la función de expand, que acepta una ruta de recursos, en relación con la carpeta en la que se llamó el generador (la ruta de salida) y devuelve una matriz de todos los archivos que coinciden con el patrón proporcionado, en nuestro caso esto devolverá todos los archivos HTML de sección.

Luego recorremos la lista de archivos y para cada uno, usamos Underscore Strings para eliminar la marca de tiempo y la extensión de archivo para que nos quedemos solo con el nombre del archivo. El método humanize tomará cosas que con CamelCase o caracteres como guiones bajos y guiones y los convertirá en espacios, para que obtengas una cadena legible por humanos. También capitalizará la primera letra de la cuerda que funcionará muy bien para nuestro menú. Con el nombre separado, creamos un objeto de contexto junto con el ID que usamos antes, para que los enlaces realmente nos lleven a las secciones correctas.

Ahora tenemos dos nuevas funciones que no hemos visto antes, engine y append. engine toma una cadena de plantilla como el primer parámetro y un objeto de contexto como el segundo y lo ejecutará a través del motor de plantillas y devuelve los resultados. append acepta una cadena HTML como el primer parámetro, un selector como el segundo y algo para insertar como el tercer parámetro. Lo que hará es insertar el contenido proporcionado al final de todos los elementos que coinciden con el selector. Aquí estamos agregando el enlace al final del menu div.

Por último, pero no menos importante, tenemos el método de write que tomará nuestra cadena HTML calculada y la escribirá en el archivo. Ahora, sólo para terminar aquí, vamos a agregar un método para ejecutar npm:

El primer parámetro para npmInstall son las rutas, pero puedes dejar este espacio en blanco, además de que sólo estoy imprimiendo un mensaje al final, diciéndole al usuario que la aplicación está lista.

Por lo tanto, como un resumen rápido, cuando se ejecuta nuestro generador, se le pedirá al usuario el nombre del proyecto y si incluir o no una sección de demostración. Después de eso, pasará a crear la estructura de carpetas y a copiar todos los archivos necesarios para nuestro proyecto. Una vez hecho esto, se ejecutará npm para instalar las dependencias que definimos y mostrará el mensaje que acabamos de poner.

Esto es casi todo lo que el generador principal necesita hacer, pero no es tan útil sin las tareas Grunt. Actualmente, es sólo un montón de archivos separados, pero con el fin de desarrollar correctamente las secciones, necesitamos una manera de previsualizarlos como un solo archivo y también necesitaremos la capacidad de compilar los resultados. Así que abre _gruntfile.js desde el directorio de plantillas y comencemos:

Hasta ahora, esto es sólo el boilerplate estándar, necesitamos exportar una función y dentro incluimos las tres dependencias que agregamos al archivo package.json, y luego registramos las tareas. Tendremos una tarea de serve que actuará como un servidor y nos permitirá ver los archivos separados juntos mientras desarrollamos nuestro sitio y luego tenemos el comando build que combinará todo el HTML y CSS en nuestra aplicación para su implementación. También agregué la última línea, así que si simplemente corres grunt por sí mismo, asumirá que quieres compilar.

Ahora, comencemos con la configuración para el comando build, ya que es mucho más corto:

Para el HTML, solo vamos a concatenar todos los archivos HTML juntos en el orden especificado en la carpeta de compilación bajo el nombre index.html. Con el CSS, también queremos minificarlo, por lo que estamos usando el plugin cssmin y estamos especificando que queremos generar un archivo llamado main.css dentro de la carpeta build/css y queremos que incluya las versiones minificadas de todos los archivos CSS en nuestro proyecto.

Conectar servidor

A continuación, tenemos que agregar la configuración para el servidor Connect, connect proporciona una gran cantidad de gran middleware para servir archivos estáticos o directorios, pero aquí tenemos que combinar todos los archivos primero, por lo que vamos a tener que crear algunos controladores personalizados. Para empezar, pongamos la configuración base en:

La opción keepalive le dice a Grunt que mantenga el servidor en ejecución, la opción open le indicará a Grunt que abra la URL en su navegador automáticamente cuando inicie el servidor (es más bien una preferencia personal) y la función de middleware está destinada a devolver una matriz de controladores de middleware para procesar las solicitudes.

En nuestra aplicación, básicamente hay dos recursos que necesitamos manejar, el recurso raíz (nuestro "index.html") en cuyo caso necesitamos combinar todos nuestros archivos HTML y devolverlos y luego el recurso "main.css", en cuyo caso queremos devolver todos los archivos CSS combinados. En cuanto a cualquier otra cosa, podemos devolver un 404.

Así que vamos a empezar creando una matriz para el middleware y vamos a agregar el primero (esto va dentro de la función de la propiedad middleware, donde coloqué el comentario):

Hemos cambiado de la API de Yeoman a la de Grunt, pero afortunadamente los nombres de comando son casi idénticos, todavía estamos usando read para leer archivos y expand para obtener la lista de archivos. Además de eso, todo lo que estamos haciendo es establecer el tipo de contenido y devolver la versión concatenada de todos los archivos HTML. Si no está familiarizado con un servidor basado en middleware, básicamente se ejecutará a través de la pila de middleware hasta que en algún lugar a lo largo de la línea, una función pueda manejar la solicitud.

En la primera línea, comprobamos si la solicitud es para la raíz URL, si es que manejamos la solicitud, de lo contrario llamamos a next() que pasará la solicitud a lo largo de línea al siguiente middleware.

A continuación, tenemos que entregar la solicitud a /css/main.css que, si recuerdas, es lo que configuramos en el encabezado:

Básicamente, esta es la misma función que antes, excepto para los archivos CSS. Por último, pero no menos importante, agregaré un mensaje 404 y devolveré la pila de middleware:

Esto significa que si alguna solicitud no se maneja con las dos funciones anteriores, entonces enviaremos este mensaje 404. Y eso es todo lo que hay, tenemos el generador que creará el proyecto y las tareas Grunt que nos permitirán previsualizar y construir nuestra aplicación. El último tema que quiero tratar brevemente es subgeneradores.

Subgeneradores

Ya hemos creado el generador principal que construye nuestra aplicación, pero Yeoman te permite crear tantos subgeneradores como desees utilizar después del scaffolding inicial. En nuestra aplicación, necesitamos uno para generar nuevas secciones. Para empezar, en realidad podemos utilizar un subgenerador desde el generator:generator para hacerle scaffold al archivo, para hacer esto, sólo tiene que ejecutar el siguiente comando desde dentro de nuestra carpeta onepage:

Esto creará una nueva carpeta llamada section con un archivo index.js y una carpeta templates al igual que nuestro generador principal (app). Vamos a vaciar el generador como lo hicimos la última vez y te tiene que quedar lo siguiente:

También puedes notar que estamos extendiendo desde la NamedBase no la Base regular, eso significa que tienes que proporcionar un parámetro de nombre cuando se llama al generador y luego puedes acceder a ese nombre usando this.name. Ahora este código es esencialmente las dos funciones que ya escribimos en el generador anterior: generateDemoSection y generateMenu, por lo que sólo tenemos que copiar esas dos funciones aquí. Reemplazaré el nombre del primero a algo más apropiado:

La única diferencia es que en lugar de escribir el nombre directamente, estoy usando la propiedad this.name. La única otra cosa es que necesitamos mover los archivos de plantilla _sections.html, _section.css y _menu.html de app/templates a section/templates, ya que es donde se verán los comandos de template/read.

Duplicación de código

El problema ahora es la duplicación de código. De regreso al archivo app/index.js, en lugar de que tenga el mismo código que el subgenerador, podemos llamar al subgenerador y pasarle el argumento de nombre. Puede eliminar generateMenu de app/index.js por completo y modificaremos generateDemoSection a lo siguiente:

Así que si el usuario quería crear la sección de demostración, entonces le damos invoke al subgenerador pasando el primer argumento que es el nombre. Si el usuario no quería la publicación de demostración, todavía tenemos que crear algo para el menú porque nuestras tareas Grunt intentarán leerlo, por lo que en la última sección, sólo escribimos una cadena vacía en el archivo de menú.

Nuestro generador finalmente está completo y ahora podemos probarlo.

Probandolo

Lo primero que tenemos que hacer es instalar nuestro generador en tu sistema. En lugar de instalar la gema regularmente, podemos usar el comando link para solo vincular la carpeta a la ruta de la gema, de esa manera podemos seguir haciendo cambios aquí sin necesidad de volver a instalarla cada vez. Desde el directorio del proyecto, simplemente ejecute el nom link.

El último paso es, de hecho, ejecutar el generador. Sólo tienes que crear una nueva carpeta y dentro ejecutar yo onepage esto correrá nuestro generador principal e instalará las dependencias vía npm. A continuación, podemos utilizar nuestras tareas Grunt para ver la página (servicio grunt) o agregar algunas secciones más con nuestro generador usando algo como yo onpage:section "Another section" y los nuevos archivos se agregarán.

Conclusión

En este artículo, cubrimos muchas de las características comunes, pero todavía hay opciones más interesantes para probar. Como probablemente puedes ver, se requiere un poco de boilerplate al construir un generador, pero ese es el punto, se hace todo una sola vez y luego puedes utilizarlo en todas tus aplicaciones.

Espero que te haya gustado leer este artículo, si tienes alguna pregunta o comentario, no dudes en dejarlos a continuación.

Advertisement
Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.