Integración y Despliegue Continuo (CI/CD)#
El módulo DWEC se centra en la creación de páginas web en el lado del cliente. Este enfoque implica, en su mayoría, la programación en JavaScript para interactuar dinámicamente con el navegador y proporcionar una mejor experiencia de usuario. Sin embargo, el desarrollo web moderno abarca más que simplemente escribir código JavaScript.
En este libro hemos explorado tecnologías y metodologías modernas para el desarrollo frontend, como la programación funcional, la reactividad, los empaquetadores y las pruebas. Ahora vamos a abordar aspectos relacionados con el despliegue y la automatización de algunas de estas tareas. Si nos especializamos en estos temas y otros afines, nuestro perfil puede definirse como “DevOps”.
Para abordar la complejidad y las múltiples etapas involucradas en el desarrollo y despliegue de una aplicación web y mejorar la calidad del software resultante, se recurre a los procesos de Integración Continua y Despliegue Continuo (CI/CD). Estos procesos permiten automatizar y estandarizar muchas tareas asociadas con el desarrollo de software, desde la escritura de código hasta la entrega al usuario final.
Pasos Típicos en el Desarrollo Web#
Crear el proyecto: Iniciar un nuevo proyecto web utilizando herramientas como npm, lo que establece la estructura inicial del proyecto y crea el archivo
package.json
que contiene la información del proyecto y sus dependencias.Instalar dependencias: Utilizar npm o yarn para instalar las dependencias necesarias para el desarrollo del proyecto, tanto las dependencias de desarrollo (por ejemplo, herramientas de construcción, linters, pruebas) como las dependencias específicas del proyecto (por ejemplo, frameworks o librerías JavaScript).
Instalar y configurar frameworks o librerías: Instalar y configurar cualquier framework o librería adicional necesaria para el desarrollo de la aplicación web, como React, Vue.js o Angular, junto con cualquier complemento o biblioteca asociada.
Escribir código correcto: Desarrollar el código de la aplicación web siguiendo las mejores prácticas y estándares de codificación, lo que implica escribir código limpio, modular y mantenible que cumpla con los requisitos del proyecto.
Utilizar linters: Configurar y utilizar linters como ESLint para asegurar que el código JavaScript cumpla con las convenciones de estilo y las reglas de calidad definidas, ayudando a detectar y corregir errores, así como a mantener la coherencia en el código base.
Escribir pruebas (Test-Driven Development - TDD): Implementar pruebas unitarias y de integración para validar el comportamiento del código y garantizar su correcto funcionamiento, utilizando el enfoque de desarrollo guiado por pruebas (TDD) para mejorar la calidad del software y su mantenibilidad.
Crear el repositorio git y gestionarlo: Inicializar un repositorio git para controlar el historial de cambios en el código fuente y facilitar la colaboración entre miembros del equipo, utilizando comandos git para crear commits, ramas y fusiones según sea necesario.
Realizar pruebas de integración: Ejecutar pruebas de integración para asegurar que los diferentes componentes de la aplicación funcionen correctamente juntos y que no se produzcan conflictos o errores de interoperabilidad.
Crear el bundle: Utilizar herramientas de construcción como Vite, Webpack o Parcel para crear un bundle optimizado de los activos de la aplicación, incluidos los archivos HTML, CSS, JavaScript y recursos estáticos.
Configurar el servidor: Configurar un servidor web para servir los archivos estáticos de la aplicación y manejar solicitudes HTTP, lo que puede incluir la configuración de servidores locales para desarrollo y pruebas, así como la configuración de servidores de producción para el despliegue.
Subir los archivos estáticos al servidor: Transferir los archivos estáticos resultantes del proceso de construcción (bundle) al servidor de producción o a un entorno de pruebas, lo que puede implicar la implementación manual o automatizada de los archivos en el servidor remoto.
Poner en producción: Realizar pruebas finales en el entorno de producción para asegurarse de que la aplicación web funcione como se espera y, si todo está correcto, lanzarla para que los usuarios finales la utilicen.
Como se puede ver, es muy difícil que los programadores lo hagan todo bien todo el tiempo. La parte del despliegue se trata con más profundidad en el módulo de despliegue del ciclo, pero es preciso desplegar rudimentariamente o en alguna plataforma alternativa que simplifique esta tarea. De esta manera se puede ver todo el proceso sin perder demasiado tiempo. La integración continua es una metodología que se apoya en unas tecnologías muy diversas. Vamos a explorar las tecnologías y poco a poco ir configurando todas las herramientas de un proyecto para automatizar las tareas descritas.
En este manual vamos a explorar algunas técnicas para implementar el CI/CD en clase. En un proyecto real se mantiene la metodología, ampliando algunos aspectos. Lo que nadie nos garantiza es que se usará Vercel, Vitest, EsLint, Github… Porque cada empresa tiene un proceso personalizado y mucho más complejo.
Vercel#
Vercel es una plataforma que permite desplegar y gestionar aplicaciones web de manera sencilla. Este manual tratamos el proceso de alta en Vercel, la creación de un proyecto con Vite y el despliegue de la aplicación en la nube utilizando Vercel.
Hay muchas alternativas de este estilo como Firebase, Netlify, Heroku…
Es muy sencillo darse de alta con el usuario de Github. De esta manera, también se queda enlazado después.
A continuación hay que instalar el CLI y dar de alta el proyecto:
sudo npm i -g vercel
vercel --cwd <nombre>
Aquí muestra un asistente que hay que seguir cuidadosamente. En un momento del asistente pide hacer login con Github y los comandos para hacer el build. En principio puede que los detecte, si no, ponemos los típicos de Vite
.
Linting y Formatting#
El linting y el formateo son esenciales para mantener el código consistente y limpio. Cada miembro del equipo debe seguir las mismas reglas y convenciones al escribir código. La consistencia en la base de código es fundamental para:
No generar confusión sobre cómo escribir un fragmento de código específico en la aplicación cuando se incorpora un nuevo miembro del equipo.
No tener que documentar múltiples formas de hacer lo mismo.
El linter más conocido de Javascript es ESLint. Para ponerlo en funcionamiento:
npm install eslint --save-dev
npm init @eslint/config@latest
Y luego instalamos la extensión de ESLint en el VSCode.
El linter marca como errores también los fallos en estilo. Desde cosas como no declarar correctamente variables que pueden provocar problemas en el futuro a reglas de estilo como los espacios que hay que dejar. Esas reglas de estilo están definidas en su configuración y se pueden modificar al gusto. Algunas colecciones de reglas conocidas y recomendables son las de Google y AirBnB que están codificadas en EsLint por defecto.
Con la configuración anterior, tenemos el código lleno de “errores”, pero nos deja usarlo. No obstante, si queremos que no se pueda poner código en producción que no cumpla las reglas de estilo, podemos crear un Hook de pre-commit que evalue el linter y no deje ejecutar el push si no se cumplen.
Los “hooks” en Git son scripts personalizados que se ejecutan automáticamente en respuesta a eventos específicos, como confirmar cambios o fusionar ramas, proporcionando automatización y personalización del flujo de trabajo de Git. Se almacenan en la carpeta
.git/hooks
y pueden realizar acciones antes o después de eventos como pre-commit, post-commit, pre-push, entre otros.
Hay una aplicación que simplifica la creación de estos Hooks, se llama Husky.
npm install --save-dev husky
npx husky init
Estos comandos instalan Husky y crean una carpeta .husky
donde hay un archivo pre-commit
en el que podemos añadir comandos a ejecutar en ese Hook. Es similar a los scripts que se pueden poner en package.json
. Con estos comandos podemos ejecutar Eslint, tests o lo que se necesite.
En nuestro caso:
npx eslint
Al automatizar el Linter, estamos evitando tener que comprobarlo cada vez, lo cual nos aproxima a un ciclo CI/CD.
Además de pasar el linter, podemos formatear el código con Prettier:
Instalar:
npm install --save-dev --save-exact prettier
Comando para el pre-commit:
npx prettier . --write
Unit Testing#
Siendo totalmente rigurosos con estas metodologías, deberíamos aplicar técnicas TDD en nuestro desarrollo. Por tanto, los test unitarios forman parte de la etapa de desarrollo, incluso son anteriores a la creación del código funcional. Generalmente se configuran y programan los test y se mantiene una terminal o una web abierta mientras se programa. De esta manera, cualquier cambio en el código se testa y de un vistazo vemos si sigue funcionando o si todavía no funciona nuestro código.
Por otra parte, no podemos confiar en que todos los desarrolladores van a hacer caso siempre a los tests o que no se van a equivocar justo antes de hacer un “commit”. Por tanto, podemos añadir los tests a un hook como en la sección anterior para evitar hacer el commit o podemos configurar GitHub Actions para testar en el propio repositorio. Esto nos permitirá también mantener un registro de los “commits” y hacer otras acciones como hacer el build y posteriormente poner en producción.
Instalar los tests#
En este ejemplo, vamos a instalar Vitest, ya que proporciona un soporte nativo a módulos ESM y una interfaz por terminal cómoda para lo que necesitamos. Además, funciona perfectamente en GitHub Actions.
npm install -D vitest
En un fichero que tenga la palabra .test.js
pondremos los test que nos interesan.
import { expect, test } from 'vitest';
...
test('test description', () => {
...
});
Si queremos ejecutar los tests en la terminal local, pondremos en el package.json
:
{
"scripts": {
"test": "vitest"
}
}
Luego ya lo ejecutamos:
npm run test
Configurar los tests en GitHub Actions#
Seguiremos este tutorial: https://docs.github.com/en/actions/quickstart#creating-your-first-workflow Si lo hacemos más o menos hasta el final, los push provocarán que se ejecute el action y muestre el resultado, pero no hace ningún test. En nuestro caso, para hacer los test, podemos modificar el .yaml
para añadirlos:
name: GitHub Actions Demo
run-name: ${{ github.actor }} is testing out GitHub Actions 🚀
on: [push]
jobs:
Explore-GitHub-Actions:
runs-on: ubuntu-latest
steps:
- run: echo "🎉 The job was automatically triggered by a ${{ github.event_name }} event."
- run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by GitHub!"
- run: echo "🔎 The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}."
- name: Check out repository code
uses: actions/checkout@v4
- run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner."
- run: echo "🖥️ The workflow is now ready to test your code on the runner."
- name: List files in the repository
run: |
ls ${{ github.workspace }}
- run: echo "🍏 This job's status is ${{ job.status }}."
Test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: ['20.x']
steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- name: Install dependencies frontend
working-directory: .
run: npm ci
- name: npm test
working-directory: .
run: npm test
Si no pasa el test mostrará un error en la pestaña de actions y si lo pasa, el push se ejecuta.
Integration Tests#
Las herramientas para hacer tests de integración pueden ser las mismas que los unitarios o alguna más especializada. Estas herramientas, en el frontend pueden probar cosas como:
La interacción y navegación del usuario.
El funcionamiento de los formularios.
La renderización de vistas.
La comunicación con la API.
En este caso, normalmente es necesario falsear (mocking) las peticiones a la red. Para ello, podemos usar herramientas como msw. También se pueden mockear funciones y eventos, así como usar espías para ver si se han ejecutado y cuantas veces.
End 2 End Testing#
Este tipo de tests son los más complicados porque implica seguir todo el proceso de los usuarios para cada acción que se realice en la web. Pueden ser muy tediosos de hacer y mantener. Además, es muy difícil de simular todas las secuencias que seguirán los usuarios, incluso sus errores. El software recomendable puede ser Cypress.
Build and Deploy#
Esta tarea la podemos hacer con GitHub Actions, al igual que los test unitarios. En otros casos, esta tarea es simplificada por el servicio en el que hacemos el deploy.
En el caso de Vercel, se puede configurar para que se mantenga actualizado cuando se realiza push en Github.