bookmark_borderConfigurar un pipeline Jenkins para una aplicación Angular

Vamos a configurar un pipeline de Jenkins para desplegar una aplicación Angular en un contenedor Docker. Como casi siempre vamos a utilizar versiones LTS (Long Term Service). En el momento de escribir este articulo las versiones LTS que tenemos son las siguientes:

En que lo vamos a utilizar

  • Ubuntu server 22.04 LTS.
  • Jenkins Server 2 LTS
  • OpenJDK 17
  • Node 18 LTS
  • Angular 15
  • Docker 20.10

    Instalación Angular en el servidor

    Lo primero que vamos a hacer es instalar node en el servidor de Jenkins

    • Agrega la llave de Nodejs, y ejecuta el instalador de paquetes apt. Siempre consulta la llave correcta tomada de la pagina oficial de node.
    $ curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash - &&\
    sudo apt-get install -y nodejs
    • Verifica que se haya instalado correctamente node
    $ node -v
    v18.16.0
    • Ahora instalamos Angular 15, y verificamos su instalación. (si no le pones la versión instalas la ultima versión que tenga en el npm)
    $ sudo npm install -g @angular/cli@15.2.8
    ...
    $ ng version
         _                      _                 ____ _     ___
        / \   _ __   __ _ _   _| | __ _ _ __     / ___| |   |_ _|
       / △ \ | '_ \ / _` | | | | |/ _` | '__|   | |   | |    | |
      / ___ \| | | | (_| | |_| | | (_| | |      | |___| |___ | |
     /_/   \_\_| |_|\__, |\__,_|_|\__,_|_|       \____|_____|___|
                    |___/
        
    Angular CLI: 15.2.8
    Node: 18.16.0
    Package Manager: npm 9.6.6
    OS: linux x64
    
    Angular: 16.0.1
    ... animations, common, compiler, compiler-cli, core, forms
    ... platform-browser, platform-browser-dynamic, router
    
    Package                         Version
    ---------------------------------------------------------
    @angular-devkit/architect       0.1600.1
    @angular-devkit/build-angular   16.0.1
    @angular-devkit/core            16.0.1
    @angular-devkit/schematics      15.2.8 (cli-only)
    @schematics/angular             15.2.8 (cli-only)
    rxjs                            7.8.1
    typescript                      5.0.4
    
    • Para este articulo vamos crear una app de prueba, y la vamos a subir a un repositorio para que de alli lo tome Jenkins.
    $ ng new angular-app-jenkins
    ...
    Successfully initialized git.
    
    $ cd angular-app-jenkins
    $ git init 
    $ git add .
    $ git commit -am "Version inicial"
    $ git remote add origin https://gitlab.com/luidasa/angular-app-jenkins
    • Creamos el archivo Dockerfile en el proyecto de Angular, para tener las instrucciones de empaquetamiento.
    ### STAGE 1:BUILD ###
    # Defining a node image to be used as giving it an alias of "build"
    # Which version of Node image to use depends on project dependencies 
    # This is needed to build and compile our code 
    # while generating the docker image
    FROM node:18.16-alpine AS build
    # Create a Virtual directory inside the docker image
    WORKDIR /dist/src/app
    # Copy files to virtual directory
    # COPY package.json package-lock.json ./
    # Run command in Virtual directory
    # Install npm 9.6.6 que es al momento de escribir este articulo la versión mas reciente.
    RUN npm install -g npm@9.6.6
    RUN npm cache clean --force
    # Copy files from local machine to virtual directory in docker image
    COPY . .
    RUN npm install
    RUN npm run build --omit=dev
    
    
    ### STAGE 2:RUN ###
    # Defining nginx image to be used
    FROM nginx:latest AS ngi
    # Copying compiled code and nginx config to different folder
    # NOTE: This path may change according to your project's output folder 
    COPY --from=build /dist/src/app/dist/domain-app-name /usr/share/nginx/html
    COPY ./nginx.conf /etc/nginx/conf.d/default.conf
    # Exposing a port, here it means that inside the container 
    # the app will be using Port 80 while running
    EXPOSE 80
    • Creamos el archivo Jenkinsfile en el proyecto de Angular
    /* Requires the Docker Pipeline plugin */
    pipeline {
        agent any
        environment {
          VERSION = '1.0'
        }    
        stages {
          stage('build') {
            steps {
              git url: 'https://gitlab.com/luidasa/angular-app-jenkins.git', branch: 'master', credentialsId: 'gitlab-luidasa'
              sh '/usr/bin/ng build --prod'
            }
          }
          stage('build image') {
            steps {
              sh 'docker build -t luidasa/angular-app-jenkins:${VERSION}.${BUILD_NUMBER} .'
            }
          }
          stage('push image to hub') {
            steps {
              withDockerRegistry([credentialsId: "dockerhub", url: ""]) {
                sh 'docker push luidasa/angular-app-jenkins:${VERSION}.${BUILD_NUMBER}'
              }
            }
          }
        }
    }
    • Por ultimo, configuramos jenkins

    bookmark_borderInstalacion de Node JS 10 en Ubuntu Server 20.04

    Para este articulo vamos a instalar nodejs 10 en Ubuntu Server, aunque la fecha de termino de soporte de Node termina en mayo del 2021, lo voy a instalar debido a que la aplicación se desarrollo con esta versión y no se han hecho las pruebas para migrar a la versión mas reciente LTS. Recuerda siempre programar con versiones LTS si es que vas a implementarlo en produccion.

    Procedimiento

    • Actualiza el gestor de paquetes
    $ sudo apt update
    • Verifica que tengas instalado curl
    $ sudo apt install curl
    Reading package lists... Done
    Building dependency tree       
    Reading state information... Done
    curl is already the newest version (7.68.0-1ubuntu2.5).
    0 upgraded, 0 newly installed, 0 to remove and 1 not upgraded.
    • Descarga el paquete desde nodesource, te va a marcar que esta desactualizado y que debes de actualizar tu aplicación.
    $ curl -sL https://deb.nodesource.com/setup_10.x | sudo -E bash -
    
    ================================================================================
    ================================================================================
    
                                  DEPRECATION WARNING                            
    
      Node.js 10.x is no longer actively supported!
    
      You will not receive security or critical stability updates for this version.
    
      You should migrate to a supported version of Node.js as soon as possible.
      Use the installation script that corresponds to the version of Node.js you
      wish to install. e.g.
    
       * https://deb.nodesource.com/setup_12.x — Node.js 12 LTS "Erbium"
       * https://deb.nodesource.com/setup_14.x — Node.js 14 LTS "Fermium" (recommended)
       * https://deb.nodesource.com/setup_15.x — Node.js 15 "Fifteen"
       * https://deb.nodesource.com/setup_16.x — Node.js 16 "Gallium"
    
      Please see https://github.com/nodejs/Release for details about which
      version may be appropriate for you.
    
      The NodeSource Node.js distributions repository contains
      information both about supported versions of Node.js and supported Linux
      distributions. To learn more about usage, see the repository:
        https://github.com/nodesource/distributions
    
    ================================================================================
    ================================================================================
    
    • Una vez descargado el instalador, puedes instalarlo con el gestor de paquete apt.
    $ sudo apt install nodejs
    Reading package lists... Done
    Building dependency tree       
    Reading state information... Done
    The following NEW packages will be installed:
      nodejs
    0 upgraded, 1 newly installed, 0 to remove and 1 not upgraded.
    
    • Verifica que se haya instalado correctamente
    $ node --version
    v10.24.1

    Referencias

    https://joshtronic.com/2018/05/08/how-to-install-nodejs-10-on-ubuntu-1804-lts/

    bookmark_borderCrear una plantilla de proyecto utilizando webpack

    Configuración del proyecto de webpack

    • Para crear el esqueleto de proyectos de webpack. (Si se utiliza un framework de javascript esto no es necesario). Verifica que tengas el nodeJs y el npm instalados.
    $ node --version
    v12.13.1
    $ npm --version
    
    • Para generar el package.json del webpack, vamos a ejecutar el comando npm init, si ponemos la opción -y pone todas opciones por default y solo genera el package.json, si no se pone la opción entonces vas a tener que contestar el cuestionario.
    $ npm init
    This utility will walk you through creating a package.json file.
    It only covers the most common items, and tries to guess sensible defaults.
    
    See `npm help init` for definitive documentation on these fields
    and exactly what they do.
    
    Use `npm install <pkg>` afterwards to install a package and
    save it as a dependency in the package.json file.
    
    Press ^C at any time to quit.
    package name: (webpack-inicial) 
    version: (1.0.0) 
    git repository: 
    keywords: 
    license: (ISC) 
    About to write to /home/ldsalazarg/Documentos/cursos/javascript-moderno/04-webpack/package.json:
    
    {
      "name": "webpack-inicial",
      "version": "1.0.0",
      "description": "Este es un cascaron de un proyecto de webpack",
      "main": "index.js",
      "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1"
      },
      "author": "Luis David Salazar",
      "license": "ISC"
    }
    
    
    Is this OK? (yes)
    • Instalar el webpack y el webpack-cli –save-dev. Es una dependencia de desarrollo
    $ npm install webpack webpack-cli --save-dev
    npm notice created a lockfile as package-lock.json. You should commit this file.
    npm WARN webpack-inicial@1.0.0 No repository field.
    
    + webpack-cli@4.5.0
    + webpack@5.21.2
    added 124 packages from 161 contributors and audited 124 packages in 9.087s
    
    14 packages are looking for funding
      run `npm fund` for details
    
    found 0 vulnerabilities
    • Agrega la opción build en el package.json del proyecto.
     "scripts": {
            "test": "echo \"Error: no test specified\" && exit 1",
            "build": "webpack"
        },
    • Ahora ejecuta el script con la siguiente linea de comando npm run build. Recuerda que la estructura del proyecto debe de estar de la siguiente forma para que pueda funcionar con las configuraciones por default:
    $ npm run build
    
    > webpack-inicial@1.0.0 build /home/ldsalazarg/Documentos/cursos/javascript-moderno/04-webpack
    > webpack
    asset main.js 420 bytes [compared for emit] [minimized] (name: main)
    runtime modules 663 bytes 3 modules
    cacheable modules 275 bytes
      ./src/index.js 88 bytes [built] [code generated]
      ./src/js/funciones.js 187 bytes [built] [code generated]
    WARNING in configuration
    The 'mode' option has not been set, webpack will fallback to 'production' for this value.
    Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
    You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/configuration/mode/
    webpack 5.21.2 compiled with 1 warning in 197 ms
    • Recuerda que para utilizar una funcion o clase en un modulo externo debe de estar etiquetado con la palabra export.
    export const saludar = (nombre) => {
        console.log('creando una etiqueta');
        const h1 = document.createElement('h1');
        h1.innerText = `Hola ${ nombre }`;
        document.body.append(h1);
    };
    • El archivo de configuracion de webpack por default tiene el nombre de webpack.config.js. Para continuar vamos a crearlo vacio, y vamos a importar unos modulos que nos van a ayudar a copiar los archivos html y a renombrar los archivos destino con un hash para que no se almacene en el cache de cada maquina. Los dos paquetes que debemos de importar son html-loader y html-webpack-plugin
    const HtmlWebPackPlugin = require('html-webpack-plugin');
    
    module.exports = {
    
        mode: 'development',
        module: {
            rules: [{
                test: /\.html$/,
                loader: 'html-loader',
                options: {
                    attributes: false,
                    minimize: false,
                }
            }]
        },
        plugins: [
            new HtmlWebPackPlugin({
                template: './src/index.html',
                filename: './index.html'
            }),
        ]
    }
    • Una vez configurado volvemos a generar el bundle. Nota cambia la opción del minimize a true si deseas que el webpack minimize el javascript, quitando espacios y comentarios.
    $ npm i -D html-loader html-webpack-plugin
    npm WARN webpack-inicial@1.0.0 No repository field.
    
    + html-loader@1.3.2
    + html-webpack-plugin@5.1.0
    added 62 packages from 28 contributors and audited 186 packages in 4.768s
    
    23 packages are looking for funding
      run `npm fund` for details
    
    found 0 vulnerabilities
    • Para configurar que los cambios que uno haga en el codigo debemos de importar el paquete webpack-dev-server, y configurarlo. Para importarlo ejecutamos la sentencia:
    $ npm i -D webpack-dev-server
    npm WARN deprecated chokidar@2.1.8: Chokidar 2 will break on node v14+. Upgrade to chokidar 3 with 15x less dependencies.
    npm WARN deprecated fsevents@1.2.13: fsevents 1 will break on node v14+ and could be using insecure binaries. Upgrade to fsevents 2.
    npm WARN deprecated urix@0.1.0: Please see https://github.com/lydell/urix#deprecated
    npm WARN deprecated resolve-url@0.2.1: https://github.com/lydell/resolve-url#deprecated
    npm WARN optional SKIPPING OPTIONAL DEPENDENCY: fsevents@^1.2.7 (node_modules/chokidar/node_modules/fsevents):
    npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for fsevents@1.2.13: wanted {"os":"darwin","arch":"any"} (current: {"os":"linux","arch":"x64"})
    npm WARN webpack-inicial@1.0.0 No repository field.
    
    + webpack-dev-server@3.11.2
    added 370 packages from 229 contributors and audited 557 packages in 15.785s
    
    35 packages are looking for funding
      run `npm fund` for details
    
    found 0 vulnerabilities
    • para configurarlo, agregamos el script start con el nombre del paquete.
    ...
        "mode": "developments",
        "scripts": {
            "test": "echo \"Error: no test specified\" && exit 1",
            "build": "webpack",
            "start": "webpack-dev-server --open --port 8081"
        },
    ...
    • y lo ejecutamos con el compando npn start, y se queda a la escucha del servidor. NOTA. No funciono a la primera y tuve que instalar la versión 3.3.12 de webpack-cli para que funcionara, aunque despues lo volvi a desinstalar y lo volvi a instalar pero ahora en el orden siguiente.
      • Primero instale el webpack-cli, y
      • posteriormente instale el webpack.
    $ npm start
    
    > webpack-inicial@1.0.0 start /home/ldsalazarg/Documentos/cursos/javascript-moderno/04-webpack
    > webpack-dev-server --open
    
    ℹ 「wds」: Project is running at http://localhost:8080/
    ℹ 「wds」: webpack output is served from /
    ℹ 「wds」: Content not from webpack is served from /home/ldsalazarg/Documentos/cursos/javascript-moderno/04-webpack
    ℹ 「wdm」: wait until bundle finished: /
    ℹ 「wdm」: asset main.js 915 KiB [emitted] (name: main)
    asset ./index.html 608 bytes [emitted]
    runtime modules 1.25 KiB 6 modules
    modules by path ./node_modules/ 865 KiB
      modules by path ./node_modules/webpack-dev-server/client/ 20.9 KiB 10 modules
      modules by path ./node_modules/html-entities/lib/*.js 61 KiB 5 modules
      modules by path ./node_modules/webpack/hot/ 1.58 KiB 3 modules
      modules by path ./node_modules/url/ 37.4 KiB 3 modules
      modules by path ./node_modules/querystring/*.js 4.51 KiB
        ./node_modules/querystring/index.js 127 bytes [built] [code generated]
        ./node_modules/querystring/decode.js 2.34 KiB [built] [code generated]
        ./node_modules/querystring/encode.js 2.04 KiB [built] [code generated]
    modules by path ./src/ 631 bytes
      ./src/index.js 437 bytes [built] [code generated]
      ./src/js/funciones.js 194 bytes [built] [code generated]
    webpack 5.21.2 compiled successfully in 861 ms
    ℹ 「wdm」: Compiled successfully.
    • Ahora cada vez que cambio alguna codigo de javascript se distribuye en la aplicación utilizando el servidor de desarrollo que acabamos de levantar.

    Manejo de los activos en el proyecto.

    Tenemos 2 formas de manejar las hojas de estilo, por modulo o global al proyecto.

    Vamos a comenzar importando dos componentes, el css-loader y el style-loader, entre ambos nos van a permitir cargar los estilos cuando se necesiten.

    $ npm i -D css-loader style-loader
    npm WARN webpack-cli@3.3.12 requires a peer of webpack@4.x.x but none is installed. You must install peer dependencies yourself.
    npm WARN webpack-inicial@1.0.0 No repository field.
    npm WARN optional SKIPPING OPTIONAL DEPENDENCY: fsevents@1.2.13 (node_modules/fsevents):
    npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for fsevents@1.2.13: wanted {"os":"darwin","arch":"any"} (current: {"os":"linux","arch":"x64"})
    
    + style-loader@2.0.0
    + css-loader@5.0.2
    added 18 packages from 13 contributors and audited 576 packages in 3.614s
    
    34 packages are looking for funding
      run `npm fund` for details
    
    found 0 vulnerabilities
    • Para agregar aplicar de los estilos de una forma global se utiliza, el plugin mini-css-extract-plugin, y debe de existir un archivo styles.css en el directorio src.
    $ npm i -D mini-css-extract-plugin
    npm WARN webpack-cli@3.3.12 requires a peer of webpack@4.x.x but none is installed. You must install peer dependencies yourself.
    npm WARN webpack-inicial@1.0.0 No repository field.
    npm WARN optional SKIPPING OPTIONAL DEPENDENCY: fsevents@1.2.13 (node_modules/fsevents):
    npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for fsevents@1.2.13: wanted {"os":"darwin","arch":"any"} (current: {"os":"linux","arch":"x64"})
    
    + mini-css-extract-plugin@1.3.6
    added 2 packages from 1 contributor and audited 579 packages in 2.94s
    
    35 packages are looking for funding
      run `npm fund` for details
    
    found 0 vulnerabilities
    • Despues de instalar debemos de hacer la configuración en el webpack.config.js
    const HtmlWebPackPlugin = require('html-webpack-plugin');
    const MiniCssExtractPlugin = require('mini-css-extract-plugin');
    
    ...
        module: {
            rules: [{
                    test: /\.css$/,
                    exclude: /styles.css$/,
                    use: [
                        'style-loader',
                        'css-loader'
                    ]
                },
                {
                    test: /styles.css$/,
                    use: [
                        MiniCssExtractPlugin.loader,
                        'css-loader'
                    ]
                },
    ...
        plugins: [
            new HtmlWebPackPlugin({
                template: './src/index.html',
                filename: './index.html'
            }),
            new MiniCssExtractPlugin({
                filename: '[name].[contenthash].css',
                ignoreOrder: false
            }),
        ]
    }
    • Y para optimizar el tamaño de los css, debemos de instalar el plugin optimize-css-assets-webpack-plugin, con la siguiente sentencia.
    $ npm i -D optimize-css-assets-webpack-plugin --save
    npm WARN webpack-cli@3.3.12 requires a peer of webpack@4.x.x but none is installed. You must install peer dependencies yourself.
    npm WARN optimize-css-assets-webpack-plugin@5.0.4 requires a peer of webpack@^4.0.0 but none is installed. You must install peer dependencies yourself.
    npm WARN webpack-inicial@1.0.0 No repository field.
    npm WARN optional SKIPPING OPTIONAL DEPENDENCY: fsevents@1.2.13 (node_modules/fsevents):
    npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for fsevents@1.2.13: wanted {"os":"darwin","arch":"any"} (current: {"os":"linux","arch":"x64"})
    
    + optimize-css-assets-webpack-plugin@5.0.4
    added 218 packages from 66 contributors and audited 797 packages in 11.616s
    
    50 packages are looking for funding
      run `npm fund` for details
    
    found 0 vulnerabilities
    • Este plugin tambien debe de configurarse en el webpack.config.js. A continuación te muestro las modificaciones.