Saltar a contenido

Migración a PNPM

Describiremos a continuación los pasos a seguir para realizar la migración un proyecto de React o Next desde NPM a PNPM, junto con las consideraciones necesarias a tener en cuenta al realizar éste proceso.

Primero debemos recordar que en todo proyecto contamos con la carpeta node_modules, donde están almacenadas las dependencias que utiliza el proyecto, y un archivo lock con las versiones específicas de las dependencias que han sido instaladas, como así también las versiones de las llamadas peer dependencies, es decir, aquellas dependencias o librerías internas que son requeridas por las dependencias que nosotros tenemos instaladas explícitamente.

Pasos a seguir para migrar un proyecto de NPM a PNPM

A continuación enumeraremos los pasos que se deberán seguir para realizar la migración de un proyecto de React o Next desde NPM a PNPM:

  1. Instalar PNPM de forma global en la PC en caso de que todavía no lo tengan instalado, ejecutando el siguiente comando en la terminal:

    npm install -g pnpm
    
  2. Borrar carpeta node_modules del proyecto a migrar, en caso de que se hayan instalado las dependencias previamente.

  3. Migrar archivo lock que almacena las versiones de las dependencias instaladas y que se utiliza con NPM, package-lock.json, a su par en PNPM ejecutando el siguiente comando:

    pnpm import
    

    Se creará un nuevo archivo pnpm-lock.yaml que tendrá la información de las dependencias que se tienen instaladas en el proyecto.

  4. Eliminar archivo package-lock.json.

  5. Instalar dependencias utilizando PNPM.

    A la hora de instalar dependencias en un proyecto, podemos realizar dos tipos de instalaciones: congelando y respetando las versiones que figuran en nuestro archivo lock o realizando una instalación limpia que puede llegar a actualizar alguna de las versiones previamente instaladas.

    Cuando utilizamos NPM como manejador de paquetes, los comandos a ejecutar para realizar éstas dos instalaciones son:

    // Instalación limpia
    npm install
    
    // Instalación respetando versiones de archivo lock
    npm ci 
    

    Sus pares cuando se utiliza PNPM son los siguientes:

    // Instalación limpia
    pnpm install
    
    // Instalación respetando versiones de archivo lock
    pnpm i --frozen-lockfile 
    
  6. Reemplazar scripts que tengamos en el proyecto que se ejecuten utilizando NPM de forma explícita (paso opcional).

    Info

    Para lo único que es 100% necesario respetar el manejador de paquetes que tenemos configurado en nuestro proyecto es para instalar dependencias. El resto de ejecuciones, básicas o personalizadas (start, build, test, etc. ), se pueden ejecutar utilizando los comandos de cualquier PKG (NPM, PNPM o YARN) sin generar ningún inconveniente, por ese motivo este paso se menciona como opcional.

    En el caso de un proyecto de React o Next que se haya creado utilizando su respectivo template desde el front-cli, se deberían modificar los scripts relacionados a Eslint, reemplazando npm run por pnpm:

    // ...
    
    "lint": "pnpm eslint && pnpm stylelint",
    "lint:fix": "pnpm eslint:fix && pnpm stylelint:fix",
    
    // ...
    

Con éstos pasos realizados, ya deberíamos haber podido migrar nuestro proyecto satisfactoriamente e instalado sus respectivas dependencias.

Ahora bien, hay ciertas consideraciones a tener en cuenta cuando se migra un proyecto a PNPM, las cuales serán mencionadas a continuación.

Consideraciones a tener en cuenta al migrar un proyecto de NPM a PNPM

Instalar dependencias con el manejador de paquetes equivocado

Cómo mencionamos previamente, en el único momento que se debe respetar obligatoriamente el manejador de paquetes configurado en el proyecto, es al momento de instalar las dependencias.

Si llega a suceder que nos equivocamos e instalamos una o varias dependencias de otra forma (por ejemplo tenemos configurado PNPM e instalamos dependencias con el comando NPM install) se deberá:

  1. Eliminar carpeta node_modules.
  2. Reinstalar las dependencias con el comando correcto respetando el manejador de paquetes elegido.

Peer Dependencies

Cuando migramos un proyecto a PNPM se debe tener en cuenta que éste manejador de paquetes hostea o instala las dependencias forma diferente a cómo lo realiza NPM.

Sin entrar mucho detalle a cerca de éste apartado (dejamos el link a la documentación de PNPM por si desean saber más sobre como trabaja este manejador de paquetes) mencionamos ésta cuestión porque puede suceder que al migrar nuestro proyecto aparezcan errores mencionando que hay dependencias que no se encuentran en la carpeta node_modules pero que son requeridas para levantar o buildear la aplicación. Por lo general, estos errores podrían provenir de peer dependencies internas que utilizan las dependencias que nosotros instalamos explícitamente.

Si se encuentran frente a éste caso hay varias soluciones que se pueden llevar a cabo, dos de ellas pueden ser:

  • Indicarle a PNPM que instale siempre las peer dependencies que considere necesario:

    Se puede agregar, al igual que se hace con NPM, un archivo llamado .npmrc en la base de nuestro proyecto con la siguiente instrucción:

    auto-install-peers = true
    

    Luego eliminar la carpeta node_modules y volver a realizar la instalación de las dependencias.

  • Instalar explícitamente en el proyecto la dependencia en cuestión que se menciona como necesaria en el error.

Muchas veces la segunda opción puede llegar a ser la más recomendada, ya que evitamos de ésta forma instalar dependencias extras que quizás no son 100% necesarias y que sí se terminan instalando cuando PNPM detecta el comando de auto-install-peers.

En el caso de haber creado un proyecto utilizando el template de React o de Next del front-cli en versiones anteriores a la actualización realizada en Junio de 2023, hay algunas dependencias que hemos comprobado que puedelle llegar a ser necesario instalar explícitamente y se las listamos a continuación:

Dockerfile

Para poder buildear nuestras aplicaciones y crear la correspondiente imagen de Docker, se ha actualizado el archivo Dockerfile para que se pueda utilizar en los proyectos de forma general sin importar el manejador de paquetes que se tenga configurado (NPM, PNPM o YARN).

Este archivo tiene algunas diferencias dependendiendo si es para utilizar en un proyecto de React o de Next Js. A continuación dejaremos el código actualizado para que puedan actualizar su archivo Dockerfile:

FROM ghcr.io/architecture-it/react:node-18 AS deps
WORKDIR /app

RUN apk add --no-cache libc6-compat

COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./

RUN \
  if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
  elif [ -f package-lock.json ]; then npm ci --no-progress --silent --maxsockets 1; \
  elif [ -f pnpm-lock.yaml ]; then yarn global add pnpm && pnpm i --frozen-lockfile; \
  else \
    echo "ERROR: Falta archivo lockfile. Ver más en https://architecture-it.github.io/docs/Platform/Front/#manejo-de-dependencias"; \
    exit 1; \
  fi

FROM ghcr.io/architecture-it/react:node-18 AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .

RUN yarn build

FROM ghcr.io/architecture-it/nginx:latest
COPY --from=builder /app/build .
# WORKAROUND si se tiene problemas con permisos, (Evitar)
# RUN touch ./public/__ENV.js && chmod 777 ./public/__ENV.js

CMD ["/bin/sh", "-c", "react-env -d ./ -- && nginx -g \"daemon off;\""]
FROM ghcr.io/architecture-it/react:node-16 AS deps
WORKDIR /app

RUN apk add --no-cache libc6-compat

COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./

RUN \
  if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
  elif [ -f package-lock.json ]; then npm ci --no-progress --silent --maxsockets 1; \
  elif [ -f pnpm-lock.yaml ]; then yarn global add pnpm && pnpm i --frozen-lockfile; \
  else \
    echo "ERROR: Falta archivo lockfile. Ver más en https://architecture-it.github.io/docs/Platform/Front/#manejo-de-dependencias"; \
    exit 1; \
  fi

FROM node:16-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .

RUN yarn build

FROM ghcr.io/architecture-it/nextjs:latest AS runner
COPY --from=builder /app/next.config.js ./
COPY --from=builder --chown=nextjs:nodejs /app/public ./public
COPY --from=builder /app/package.json ./package.json
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

# Tenerlo en cuenta como posible workarround
# RUN chmod -R 777 ./public 

CMD ["/bin/sh", "-c", "react-env --prefix NEXT_PUBLIC -- && node server.js"]

Migración de una librería de React de NPM a PNPM

En el caso de que necesitemos migrar una librería de React, ya sea una librería funcional o de componentes de UI (que utilice Storybook), los pasos a seguir para migrar de NPM a PNPM son exactamente los mismos que los mencionados al principio.

Las consideraciones con respecto a las posibles dependencias faltantes luego de migrar también puede suceder en éste caso.

Asimismo va a ser necesario actualizar el archivo de Dockerfile y los respectivos Workflows de la librería, para que se puedan utilizar con cualquier manejador de paquetes.

Dockerfile

Si la librería es de componentes UI y utiliza Storybook, para poder ser desplegada se deberá actualizar el archivo Dockerfile con el código que dejamos a continuación:

FROM ghcr.io/architecture-it/react:node-16 as builder
WORKDIR /app

COPY . .

RUN \  
  if [ -f yarn.lock ]; then yarn --frozen-lockfile; \  
  elif [ -f package-lock.json ]; then npm ci --no-audit --silent --no-optional; \
  elif [ -f pnpm-lock.yaml ]; then yarn global add pnpm && pnpm i --frozen-lockfile; \
  else \
    echo "ERROR: Falta archivo lockfile. Ver más en https://architecture-it.github.io/docs/Platform/Front/#manejo-de-dependencias"; \
    exit 1; \
  fi

RUN yarn build-storybook

FROM ghcr.io/architecture-it/nginx:latest

COPY --from=builder /app/storybook-static .

CMD ["/bin/sh", "-c", "nginx -g \"daemon off;\""]

Workflows

Los workflows también deberán ser actualizados, para adaptarlos a cualquier manejador de paquetes que se desee utilizar.

Para una librería de React (funcional o de componentes) podemos llegar a agregar algunos de los siguientes cuatros workflows:

  • Sonarqube: realiza un análisis general de la aplicación con Sonarqube.
  • Release: permite realizar el release de la librería de forma automática utilizando semantic-release y conventional commits.
  • Library Test: realiza un análisis de los test unitarios y de integración de la librería y genera el reporte de coverage.
  • Visual Test: analiza y ejecuta los test visuales configurados en la librería (Loki test).

Info

Las configuraciones y nombres de las ramas especificadas en los workflows son completamente personalizables y adaptables a las necesidades de su proyecto, como así también cuando se desea ejecutar cada workflow (on push y/o on pull request)

A continuación dejamos los códigos de éstos cuatro workflows:

name: Sonarqube

on:
  workflow_dispatch: # For run manually
  pull_request:
    branches: [ develop, main ]
  push: 
    branches: [ main ]

jobs:
  build:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [18.x, 20.x]
        # See supported Node.js release schedule at https://nodejs.org/en/about/releases/
    steps:
    - uses: actions/checkout@v3
    - uses: architecture-it/actions@react-base
      with:
        matrix_version: ${{ matrix.node-version }}
        skip_test: false
        ci: true
        packages_token: ${{ secrets.ARQUITECTURA_DEPLOY }}
        # fontawesome_token: ${{ secrets.NPM_FONTAWESOME_TOKEN }}
  sonarqube:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: SonarQube Analyzer
        uses: architecture-it/actions@sonarqube
        with:
          sonar_url: ${{ secrets.SONAR_HOST_URL }}
          sonar_token: ${{ secrets.SONAR_TOKEN }}
          skip_test: ${{ env.SKIP_TEST }}
          sonar_code: 'Js'
name: Release

on:
  workflow_dispatch: # For run manually
  push:
    branches: [ develop, main ]

env:
  SKIP_TEST: 'false'
  CI: true

jobs:
  release:
    name: Release
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v3
        with:
          token: ${{ secrets.ARQUITECTURA_DEPLOY }}
      - uses: architecture-it/actions@react-base-library
        with:
          matrix_version: ${{ matrix.node-version }}
          skip_test: ${{ env.SKIP_TEST }}
          # secrets enabled in all organizations
          packages_token: ${{ secrets.ARQUITECTURA_DEPLOY }}
          # fontawesome_token: ${{ secrets.NPM_FONTAWESOME_KEY }}
      - name: Build
        run: yarn run build
      - name: Release Library
        uses: architecture-it/actions@release-npm-library
        with:
          github_token: ${{ secrets.ARQUITECTURA_DEPLOY }}
name: Library Test

on:
  workflow_dispatch: # For run manually
  pull_request:
    branches: [ development, main ]

env:
  SKIP_TEST: 'false'
  CI: true

jobs:
  build:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [16.x]
    steps:
      - name: Checkout
        uses: actions/checkout@v3
      - uses: architecture-it/actions@react-base-library
        with:
          matrix_version: ${{ matrix.node-version }}
          skip_test: ${{ env.SKIP_TEST }}
          # secrets enabled in all organizations
          packages_token: ${{ secrets.ARQUITECTURA_DEPLOY }}
          fontawesome_token: ${{ secrets.NPM_FONTAWESOME_KEY }}
      - name: Unit tests
        if: ${{ env.SKIP_TEST != 'true' }}
        run: yarn run test
        env:
          CI: ${{ env.CI == 'true' }}
      - name: Storybook Smoke Test
        run: yarn run storybook:smoke-test
      - name: Coverage
        uses: artiomtr/jest-coverage-report-action@v2.0-rc.5
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          skip-step: install
name: Visual Regression Testing

on:
  workflow_dispatch: # For run manually
  pull_request:
    branches: [ development, main ]

env:
  SKIP_TEST: 'false'
  CI: true

jobs:
  build:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [16.x]
    steps:
      - name: Checkout
        uses: actions/checkout@v3
      - uses: architecture-it/actions@react-base-library
        with:
          matrix_version: ${{ matrix.node-version }}
          skip_test: ${{ env.SKIP_TEST }}
          # secrets enabled in all organizations
          packages_token: ${{ secrets.ARQUITECTURA_DEPLOY }}
          fontawesome_token: ${{ secrets.NPM_FONTAWESOME_KEY }}
      - name: Unit tests
        if: ${{ env.SKIP_TEST != 'true' }}
        run: yarn run test
        env:
          CI: ${{ env.CI == 'true' }}
      - name: Build Storybook
        run: yarn run build-storybook
      - name: Visual Regression Testing based on build fo storybook
        run: yarn run loki:ci