Publicar una aplicación Angular en GitHub Pages

GitHub Pages es un servicio ofrecido por GitHub donde podemos alojar nuestros proyectos guardados en repositorios. Permite usar integración continua para el despliegue, pudiéndose ejecutar este cada vez que se dispare un evento como, por ejemplo, hacer push en una rama en concreto.

Vamos a alojar una sencilla aplicación Angular que se desplegará cada vez que hagamos push en la rama master.

Crear la aplicación Angular

Creamos el proyecto aceptamos las opciones que nos da por defecto (no es algo importante para este ejemplo).

ng new angular_deploy

Cambiamos al directorio e iniciamos el servidor de desarrollo para hacer algunos cambios.

cd angular_deploy | npm start

Borramos todo el contenido de app.component.html, dejando solamente el router-outltet

<router-outlet></router-outlet>

Borramos la inicialización de la variable title en app.component.ts

import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterOutlet } from '@angular/router';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [CommonModule, RouterOutlet],
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {}

Borramos los tests unitarios relacionados con la variable title de app.component.ts, el fichero app.component.spec.ts quedaría así:

import { TestBed } from '@angular/core/testing';
import { AppComponent } from './app.component';

describe('AppComponent', () => {
  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [AppComponent],
    }).compileComponents();
  });

  it('should create the app', () => {
    const fixture = TestBed.createComponent(AppComponent);
    const app = fixture.componentInstance;
    expect(app).toBeTruthy();
  });
});

Creamos dos componentes, uno que hará de página de inicio y otro que simulará una posible función del programa. Habrá navegación entre los dos componentes. Los ficheros los creamos en el directorio app

home.component.ts

import { Component, inject } from "@angular/core";
import { Router } from "@angular/router";

@Component({
  selector: 'home-component',
  template: `
    <h1>HOME</h1>
    <p>
      <button (click)="navigateDashboard()">Go to Dashboard</button>
    </p>
  `,
  styles: [
    `
      * { font-family: Arial, Helvetica, sans-serif}
    `
  ],
  standalone: true
})
export class HomeComponent {
  private router = inject(Router);

  navigateDashboard(): void {
    this.router.navigate(['dashboard']);
  }
}

dashboard.component.ts

import { Component, inject } from "@angular/core";
import { Router } from "@angular/router";

@Component({
  selector: 'dashboard-component',
  template: `
    <h1>DASHBOARD</h1>
    <p>
      <button (click)="navigateHome()">Go to Home</button>
    </p>
  `,
  styles: [
    `
      * { font-family: Arial, Helvetica, sans-serif}
    `
  ],
  standalone: true
})
export class DashboardComponent {
  private router = inject(Router);

  navigateHome(): void {
    this.router.navigate(['/']);
  }
}

Editamos el fichero app.routes.ts para añadir la rutas que permitan navegar a los nuevos componentes y una de inicio que redireccione a /home:

7. Editamos las rutas de la aplicación para añadir rutas a los components y una ruta de inicio que redireccione a /home:

import { Routes } from '@angular/router';
import { HomeComponent } from './home.component';
import { DashboardComponent } from './dashboard.component';

export const routes: Routes = [
  { path: 'home', component: HomeComponent },
  { path: 'dashboard', component: DashboardComponent },
  { path: '', redirectTo: '/home', pathMatch: 'full'},
]

Nos quedará una estructura de directorios como esta:

Añadimos un script en el package.json para ejecutar los tests usando un navegador headless, para que no inicie ninguna aplicación de escritorio, y sin que se quede iniciado pendiente de los cambios:

"test:headless": "ng test --browsers=ChromeHeadless --watch=false"

La ejecución de los tests usando este script en consola se vería así:

Crear el repositorio

Accedemos a nuestro repositorio de GitHub y creamos un nuevo proyecto con nombre angular_deploy:

Añadimos el repositorio remoto a nuestro local y hacemos el primer commit.

git remote add origin [url del repositorio proporcionada por GitHub]

git add .

git commit -m "first commit"

git push -u origin master

Desplegar la aplicación

Para desplegar la aplicación tenemos que acceder a la opción Settings de la barra de opciones del respositorio:

Dentro de Settings, en el menú lateral, pulsamos sobre Pages:

Se mostrarán las opciones para desplegar. Se puede desplegar desde una rama del repositorio, la que se muestra por defecto, o usando GitHub Actions.

Si elegimos usar esta opción deberemos indicar primero la rama donde esta la versión que queremos desplegar:

Y el directorio donde está el contenido que se desplegará, pudiendo elegir entre el directorio raíz o uno llamado docs:

Si usamos esta opción tendremos que crear un script para compilar la aplicación pasándole el parámetro para indicar el destino, por ejemplo:

"build": "ng build --output-path docs ---base-href ./"

Posteriormente, se han de subir los cambios al repositorio y se desplegará la aplicación usando los ficheros añadidos la repositorio.

Este método es un poco engorroso, ya que, a cada cambio en la aplicación, tenemos que compilar antes de hacer el push al respositorio. Usaremos GitHub Actions que nos permitirá desplegar la aplicación por cada push. Cambiamos la opción para deplegar:

Se mostrarán la opciones para generar el fichero .yml que define las tareas a realizar para el despliegue. Se puede elegir entre alguno de los ejemplos que tiene GitHub disponibles, pulsando sobre browse all workflows, o crear un propio a partir de un fichero en blanco, pulsando sobre create your own.

Al pulsar sobre la opción para crear uno propio se abrirá una página para editar el fichero:

Podemos editar el fichero de manera online, y, finalizados los cambios, hacer un commit y push hacia el repositorio. Para este ejemplo, editaremos el fichero en nuestro local usando el editor de código que usemos normalmente.

En el directorio raíz del proyecto creamos un directorio con el nombre .github, que contendrá otro directorio con el nombre workflows, que, a su vez contendrán un fichero de extensión .yml, que contendrá las instrucciones para desplegar el proyecto. Para este ejemplo usaremos el nombre angular_deploy.yml. Quedaría así:

El fichero angular_deploy.yml

Empezamos añadiendo el nombre del trabajo. Este aparecerá en la lista de trabajos del repositorio, en la pantalla de acciones.

name: Build and Deploy

Lo siguiente serán los eventos que provocarán la ejecución del despliegue. Vamos a usar dos:

  • workflow_dispatch: para poder lanzar el ejecución del despliegue manualmente.
  • push: para que se lance el despliegue cuando se haga un push. Le pasaremos el parámetro branches para indicarle las ramas que afectan a este evento.
on:
  workflow_dispatch:

  push:
    branches:
      - master

Al usar el evento worflow_dispatch se mostrará el desplegable Run workflow, desde el que podremos ejecutar manualmente el despliegue:

Seguidamente añadimos la lista de trabajos que formarán en despliegue. Para este caso usaremos dos:

  • build: para testear y compilar la apliación
  • deploy: para desplegar la aplicación compilada

Build estará definido por un entorno donde ejecutarse y una serie de pasos a ejecutar.

El entorno donde ejecutarse lo definimos con runs-on, pasándole como valor ubuntu-latest. Se puede consultar una lista de los entornos disponibles para ejecutar los trabajos en el siguiente enlace:

https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#choosing-github-hosted-runners

Los pasos estarán definidos por un nombre, mediante name, y una cláusula uses, en caso de usar una acción, o run si vamos a ejecutar algún comando del sistema operativo. Serán los siguientes:

Checkout: se encargará de comprobar que el trabajo tiene acceso al repositorio. Usaremos actions/checkout@v4

Setup Node.js: configura Node.js en el entorno de trabajo. Usaremos actions/setup-node@v3, pasándole el parámetro node-version con el valor 18. El parámetro node-version admite tanto un solo valor, que indica la versión a usar, como una lista de estos.

Install dependencies: una vez configurador Node.js, mediante la cláusula run podemos ejecutar la instalación de dependencias usando npm i.

Run tests: instaladas las dependencias ya podremos ejecutar los tests, de la misma manera que el paso anterior pero ejecutando npm run test:headless.

Build: si pasa los tests se procederá al compilado de la aplicación, ejecutando npm run build:prod.

Upload artifact: finalmente usaremos la acción actions/upload-pages-artifact@v2, que empaquetará y subirá la aplicación para que pueda ser desplegada en GitHub Pages. Le indicaremos dos parámetros:

  • path: el directorio donde encontrar los ficheros a empaquetar. En el caso de esta aplicación, y al usar Angular 17, la ruta sería: ./dist/angular_deploy/browser.
  • retention-days: el tiempo de expiración del artefacto, una vez empaquetado y subido. Indicaremos 0 para que se sustituya cada vez que ejecutemos el trabajo.

Deploy estará definido por una serie de parámetros y un paso para desplegar la aplicación.

Mediante el parámetro needs le indicamos que, para ejecutarse Deploy necesita que Build se haya completado.

Definimos permisos de escritura para pages e id-token, mediante permissions, para escribir en GitHub Pages y verificar que el despliegue se realiza desde una fuente apropiada, respectivamente.

Definimos el nombre del entorno y la url donde se desplegará la aplicación mediante los parámetros name y url de environment.

Al igual que para Build, definimos el runs-on con ubuntu-latest.

Definimos un único paso con el nombre Deploy to GitHub Pages que usará la acción actions/deploy-pages@v2.0.4 y al que le asignaremos un id, que ayudará a acceder información del flujo de trabajo mediante el contexto.

El fichero angular_deploy.yml quedaría así:

name: Build and Deploy

on:
  workflow_dispatch:

  push:
    branches:
      - master

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: 18

      - name: Install dependencies
        run: npm i

      - name: Run tests
        run: npm run test:headless

      - name: Build
        run: npm run build:prod

      - name: Upload artifact
        uses: actions/upload-pages-artifact@v2
        with:
          path: ./dist/angular_deploy/browser
          retention-days: 0

  deploy:
    needs: build

    permissions:
      pages: write
      id-token: write
    
    environment:
      name: 'github-pages'
      url: ${{ steps.deployment.outputs.page_url }}
    
    runs-on: ubuntu-latest

    steps:
      - name: Deploy to GitHub Pages
        id: deployment
        uses: actions/deploy-pages@v2.0.4

Hacemos commit y push para subir el fichero al repositorio y se ejecutará el despliegue.

En la lista de despliegues de la seccion Actions podemos ver una lista. Pulsando sobre cualquier elemento de la lista veremos una página donde se mostrará el flujo de los trabajos, pudiendo ver un detalle de cada un pulsando sobre su nombre. Tanto un vez ejecutado como cuando está ejecutandose.

Una vez compilada la aplicación, en enlace aparece debajo del trabajo de despliegue:

Añadir una página 404.html

Si cargamos la aplicación en el navegador, por defecto, redireccionará a la ruta /home de la aplicación. En este estado, si refrescamos la página, se mostrará el error 404.

Para evitar esto vamos a crear una página 404 con una redirección al inicio de la aplicación. También se podría hacer una página informando del error y añadir un enlace que llevase al inicio. Para este ejemplo nos quedamos sólo con la redirección.

El contenido del fichero será simplemente un tag script con la línea para redireccionar:

<script>
  window.location = '/angular_deploy';
</script>

Este fichero tenemos que copiarlo del directorio ./src al directorio de compilación. Para esto vamos a usar el paquete de npm copyfiles, para no usar comandos propios del sistema operativo que no existan en otro (por ejemplo, desarrollamos en Windows pero desplegamos en Linux) y asegurarnos que siempre funcione. La url del paquete es:

https://www.npmjs.com/package/copyfiles

Copy-files lo podemos instalar como dependencia de desarrollo:

npm i copyfiles --save-dev

Añadimos el siguiente script al package.json:

"copy-not-found": "copyfiles -f ./src/404.html ./dist/angular_deploy/browser"

El parámetro -f es para que no copie la estructura de directorios en el destino, sólo los ficheros. La ruta de destino ha de ser la ruta de salida del comando build.

Actualizamos el script build:prod para que ejecute copy-not-found al finalizar la compilación:

"build:prod": "ng build --base-href=./ &amp;&amp; npm run copy-not-found"

Subimos los cambios al repositorio y, una vez se haya desplegado, ya tendríamos la redirección disponible en caso de un error 404.

Repositorio de código

El código usado en este artículo esta disponible en el siguiente repositorio:

https://github.com/theguitxo/angular_deploy

Esta web utiliza cookies propias para su correcto funcionamiento. Contiene enlaces a sitios web de terceros con políticas de privacidad ajenas que podrás aceptar o no cuando accedas a ellos. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Ver
Privacidad