Introducción
¿Por qué Monorepo?
Hoy en día, la rápida evolución del desarrollo de software es innegable. Los equipos crecen y los proyectos tienden a ser más complejos. Las empresas invierten importantes recursos en mantener una base de código distribuida compuesta por numerosos fragmentos. Aquí es donde entra en juego el monorepositorio: un repositorio único y unificado que reúne todo el código. Lejos de ser una tendencia, los monorepositorios se han convertido recientemente en un enfoque arquitectónico para albergar toda la base de código en un solo lugar. Los equipos obtienen un mejor intercambio de contexto, una colaboración fluida y una herramienta que fomenta la reutilización del código de forma natural.
Configuración de espacios de trabajo de hilo
Nota: A lo largo de este artículo, siempre que se menciona "Yarn", se refiere específicamente a Yarn v4, la última versión que ofrece capacidades mejoradas y un rendimiento mejorado.
¿Qué son los espacios de trabajo de Yarn?
Los espacios de trabajo son los paquetes del monorepositorio, a menudo llamados paquetes. Te ayudan a gestionar fácilmente varios paquetes en un único repositorio. Con los espacios de trabajo, puedes:
Comparte dependencias fácilmente:
Comparta dependencias comunes en todo su proyecto sin problemas.
Simplifique la gestión de dependencias:
Yarn vincula automáticamente paquetes locales, lo que reduce la duplicación y facilita el desarrollo.
Acelerar las instalaciones:
Benefíciese de las optimizaciones de rendimiento y los mecanismos de almacenamiento en caché de Yarn (es decir, plug'n'play integrado ).
Mejorar el control sobre Monorepo:
Define restricciones (reglas) y utiliza docenas de complementos disponibles para mantener la coherencia.
Si bien Yarn es el gestor elegido para este artículo gracias a su simplicidad, velocidad y amplias opciones de configuración, es importante tener en cuenta que la elección correcta depende de las necesidades específicas de su proyecto, las preferencias del equipo y el flujo de trabajo general. Por ejemplo, PNPM y Turborepo son otras herramientas modernas que ofrecen una amplia gama de funciones.
Configuración inicial
Configurar Yarn es un proceso sencillo. Sigue la guía oficial para instalar y configurar Yarn en tu proyecto: Guía de instalación de Yarn .
Una vez completada la instalación, pasemos a la configuración. Dado que usamos plug and play, debes asegurarte de que tu IDE reconozca correctamente las dependencias. Si usas VSCode, ejecuta:
# Typescript is required for VSCode SDK to set up correctly yarn add -D typescript@^5 yarn dlx @yarnpkg/sdks vscode
Si está utilizando otro editor de código, busque los SDK disponibles aquí: SDK del editor de Yarn .
En este punto, ya estás listo para comenzar a utilizar Yarn.
Organizando la Estructura de Monorepo
Ahora que el gestor de paquetes está configurado, es hora de diseñar una organización escalable del proyecto. Una estructura clara y bien definida no solo facilita la navegación por el repositorio, sino que también promueve una mejor reutilización del código. En este ejemplo, dividiremos el código base en tres categorías principales:
Aplicaciones :
- Cliente: Contiene los productos cliente finales e implementables.
- Servidor: contiene los productos de servidor finales e implementables.
Características :
- Cliente: Para widgets de UI independientes.
- Servidor: para piezas de lógica empresarial de backend independientes.
Bibliotecas :
Las casas compartían código, como componentes del sistema de diseño, constantes, recursos y utilidades. Esta es la zona libre de contexto donde se almacena la lógica reutilizable.
Para demostrar la eficacia de esta estructura de carpetas, comencemos añadiendo estas carpetas principales a la lista de espacios de trabajo de Yarn. En el archivo raíz package.json, agregue lo siguiente:
"workspaces": [ "apps/**", "features/**", "libs/**" ]
Esta configuración indica a Yarn que trate los paquetes de estas carpetas como paquetes locales. Las instalaciones posteriores garantizarán que las dependencias de cada paquete estén correctamente configuradas y vinculadas.
Base de código de arranque
En esta sección, repasaremos un ejemplo de código base mínimo que ilustra cómo arrancar el monorepositorio. En lugar de incluir fragmentos de código completos, proporcionaré ejemplos breves con enlaces a los archivos completos del repositorio creado específicamente para este artículo .
Aplicación de servidor de arranque
Comenzamos con una API Express sencilla para la autenticación de usuarios. Esta aplicación de servidor expone un único punto final ( /auth/signIn
) que utiliza un controlador de otro paquete.
import express from "express"; import cors from "cors"; import { signInHandler } from "@robust-monorepo-yarn-nx-changesets/sign-in-handler"; const app = express(); const port = process.env.PORT || 1234; app.use(express.json()); app.use( cors({ origin: process.env.CORS_ORIGIN || "http://localhost:3000", }) ); app.post("/auth/signIn", signInHandler); app.listen(port, () => { console.log(`Server is running at http://localhost:${port}`); });
Como puede ver, el punto final /auth/signIn
usa un controlador importado de otro paquete. Esto nos lleva al siguiente componente: la función de servidor.
Función de servidor de arranque
La función de servidor encapsula la lógica de autenticación. En este paquete, definimos el controlador de inicio de sesión, que utiliza una utilidad de validación compartida de las bibliotecas.
import type { RequestHandler } from "express"; import { passwordValidator, usernameValidator, } from "@robust-monorepo-yarn-nx-changesets/validator"; const signInHandler: RequestHandler = (req, res) => { if (!req.body) { res.status(422).send("Request body is missing"); return; } if (typeof req.body !== "object") { res.status(422).send("Request body expected to be an object"); return; } const { username, password } = req.body; const usernameValidationResult = usernameValidator(username); if (typeof usernameValidationResult === "string") { res .status(422) .send("Invalid username format: " + usernameValidationResult); return; } const passwordValidationResult = passwordValidator(password); if (typeof passwordValidationResult === "string") { res .status(422) .send("Invalid password format: " + passwordValidationResult); return; } // Emulate a successful sign-in if (username === "test" && password === "test1234") { res.status(200).send("Sign in successful"); return; } return res.status(422).send("Username or password is incorrect"); }; export default signInHandler;
Este enfoque resume la lógica de autenticación en su propio paquete, lo que permite su desarrollo y mantenimiento independientes. Observe cómo se importan las utilidades de validación desde la biblioteca compartida .
Arranque de la aplicación cliente
A continuación, veamos el lado del cliente. En nuestra aplicación cliente, creamos un sitio web sencillo que permite la autenticación de usuarios mediante la API del servidor.
"use client"; import { SignInForm } from "@robust-monorepo-yarn-nx-changesets/sign-in-form"; const API_URL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:1234"; export default function Home() { const handleSubmit = async (username: string, password: string) => { const response = await fetch(`${API_URL}/auth/signIn`, { method: "POST", body: JSON.stringify({ username, password }), headers: { "Content-Type": "application/json", }, }); if (response.status === 200) { alert("Sign in successful"); return; } if (response.status === 422) { alert("Sign in failed: " + (await response.text())); return; } alert("Sign in failed"); }; return ( <div className="w-full h-screen overflow-hidden flex items-center justify-center"> <SignInForm onSubmit={handleSubmit} /> </div> ); }
En este ejemplo, el componente SignInForm
se importa desde un paquete de características del cliente, lo que nos lleva a nuestro componente final.
Función de cliente de arranque
El paquete de funciones del cliente proporciona el formulario de autenticación junto con la lógica de validación compartida. Esto evita la duplicación de código y garantiza la coherencia.
import { passwordValidator, usernameValidator, } from "@robust-monorepo-yarn-nx-changesets/validator"; interface SignInFormProps { onSubmit: (username: string, password: string) => void; } const SignInForm = ({ onSubmit }: SignInFormProps) => { const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => { event.preventDefault(); const username = (event.currentTarget[0] as HTMLInputElement).value; const usernameValidationResult = usernameValidator(username); if (typeof usernameValidationResult === "string") { alert(usernameValidationResult); return; } const password = (event.currentTarget[1] as HTMLInputElement).value; const passwordValidationResult = passwordValidator(password); if (typeof passwordValidationResult === "string") { alert(passwordValidationResult); return; } onSubmit(username!, password!); }; return ( <form onSubmit={handleSubmit}> <input type="text" placeholder="Username" /> <input type="password" placeholder="Password" /> <button type="submit">Submit</button> </form> ); }; export default SignInForm;
Aquí, vemos nuevamente el uso del validador de nuestras bibliotecas compartidas, lo que garantiza que la lógica de validación esté centralizada y sea fácil de mantener.
Eso es todo por nuestro ejemplo de código base mínimo. Tenga en cuenta que este código es una ilustración simplificada que muestra la estructura básica y la interconexión entre aplicaciones, funciones y bibliotecas en un repositorio mono. Puede ampliar estos ejemplos según sea necesario para adaptarlos a los requisitos específicos de su proyecto.
Ejecución de scripts con NX
Gestionar scripts en un repositorio único puede ser un desafío. Si bien Yarn permite ejecutar scripts en múltiples paquetes con diversas condiciones, puede requerir scripts personalizados para un control más granular. Aquí es donde entra en juego NX: proporciona una solución lista para usar para una ejecución de scripts eficiente y específica.
Introducción a NX
NX es un sistema de compilación optimizado para monorepositorios con capacidades avanzadas de integración continua (CI). Con NX, puede:
- Ejecute tareas de manera eficiente en paralelo : aproveche la simultaneidad para acelerar sus compilaciones.
- Identificar relaciones de dependencia : comprender las conexiones entre paquetes y scripts.
- Almacenar en caché los resultados de la ejecución del script : evite el trabajo redundante almacenando en caché las salidas.
- Personalice el comportamiento con complementos: amplíe la funcionalidad a través de un rico ecosistema de complementos .
Ejecución de scripts dirigidos
Para aprovechar las capacidades de NX, primero necesitamos crear un archivo nx.json
para definir un conjunto de reglas para nuestros scripts. A continuación, se muestra un ejemplo de configuración:
{ "targetDefaults": { "build": { "dependsOn": [ "^build" ], "outputs": [ "{projectRoot}/dist" ], "cache": true }, "typecheck": { "dependsOn": [ "^build", "^typecheck" ] }, "lint": { "dependsOn": [ "^build", "^lint" ] } }, "defaultBase": "main" }
En términos sencillos, esta configuración significa:
Construir
El script
build
de un paquete depende de la compilación exitosa de sus dependencias y su salida se almacena en caché.Comprobación de tipos
El script
typecheck
de un paquete depende tanto de los scripts de compilación como de los de verificación de tipos de sus dependencias.Hilas
El script
lint
de un paquete depende tanto del script de compilación como del script de lint de sus dependencias.
Ahora, agreguemos scripts al package.json
:
"scripts": { "build:all": "yarn nx run-many -t build", "build:affected": "yarn nx affected -t build --base=${BASE:-origin/main} --head=${HEAD:-HEAD}", "typecheck:all": "yarn nx run-many -t typecheck", "typecheck:affected": "yarn nx affected -t typecheck --base=${BASE:-origin/main} --head=${HEAD:-HEAD}", "lint:all": "yarn nx run-many -t lint", "lint:affected": "yarn nx affected -t lint --base=${BASE:-origin/main} --head=${HEAD:-HEAD}", "quality:all": "yarn nx run-many --targets=typecheck,lint", "quality:affected": "yarn nx affected --targets=typecheck,lint --base=${BASE:-origin/main} --head=${HEAD:-HEAD}" }
Aquí definimos cuatro tipos de scripts de ejecución:
build: construye un paquete.
typecheck: comprueba los tipos del paquete.
pelusa: pelusa un paquete.
calidad: ejecuta tanto typecheck como lint.
Cada guión tiene dos variantes:
- all: ejecuta el script en todos los paquetes.
- Afectado: Ejecuta el script solo en paquetes afectados por cambios recientes. Las variables de entorno
BASE
yHEAD
permiten especificar un rango (por defecto,origin/main
y elHEAD
actual), lo que permite una ejecución granular en las solicitudes de extracción. Esto puede ahorrar tiempo y recursos considerablemente.
Gestión de dependencias circulares
NX también proporciona un comando integrado para generar un gráfico de dependencias, lo que puede ayudar a detectar ciclos de dependencia. El siguiente script utiliza la salida del gráfico de NX para buscar dependencias circulares y falla si se encuentran.
Cree un archivo en scripts/check-circulardeps.mjs
con el siguiente contenido:
import { execSync } from "child_process"; import path from "path"; import fs from "fs"; const hasCycle = (node, graph, visited, stack, path) => { if (!visited.has(node)) { visited.add(node); stack.add(node); path.push(node); const dependencies = graph.dependencies[node] || []; for (const dep of dependencies) { const depNode = dep.target; if ( !visited.has(depNode) && hasCycle(depNode, graph, visited, stack, path) ) { return true; } if (stack.has(depNode)) { path.push(depNode); return true; } } } stack.delete(node); path.pop(); return false; }; const getGraph = () => { const cwd = process.cwd(); const tempOutputFilePath = path.join(cwd, "nx-graph.json"); execSync(`nx graph --file=${tempOutputFilePath}`, { encoding: "utf-8", }); const output = fs.readFileSync(tempOutputFilePath, "utf-8"); fs.rmSync(tempOutputFilePath); return JSON.parse(output).graph; }; const checkCircularDeps = () => { const graph = getGraph(); const visited = new Set(); const stack = new Set(); for (const node of Object.keys(graph.dependencies)) { const path = []; if (hasCycle(node, graph, visited, stack, path)) { console.error("🔴 Circular dependency detected:", path.join(" → ")); process.exit(1); } } console.log("✅ No circular dependencies detected."); }; checkCircularDeps();
Este guión:
- Ejecuta el comando NX para generar un gráfico de dependencia.
- Lee el gráfico de un archivo JSON temporal.
- Comprueba ciclos de forma recursiva.
- Registra un error y sale si se detecta una dependencia circular.
Validación de dependencias con restricciones de hilo
A medida que los proyectos crecen, mantener la coherencia entre las dependencias se vuelve un desafío. Aplicar reglas estrictas en cuanto a dependencias, versiones de Node y otras configuraciones es esencial para evitar deuda técnica innecesaria. Las restricciones de Yarn ofrecen una forma de automatizar estas validaciones.
Comprensión de las restricciones del hilo
Las restricciones de Yarn son el conjunto de reglas para los paquetes en tu monorepositorio. Una ventaja importante de usarlas es que eres el administrador de estas reglas. Por ejemplo, puedes crear una regla para forzar que todos los paquetes usen la misma versión de React. Una vez configurada, nunca tendrás problemas cuando una aplicación host no pueda usar una función o biblioteca con una versión de React superior.
Si bien migrar un monorepositorio grande a una nueva versión principal de una dependencia puede ser complejo, el uso de restricciones en última instancia aporta consistencia y estabilidad a todo el proyecto.
Imponiendo la coherencia
En nuestro repositorio de ejemplo, utilizamos un archivoyarn.config.cjs para garantizar la coherencia para:
Versión del nodo
Versión de hilo
Versiones de las dependencias
Para mayor flexibilidad durante las transiciones, puede definir exclusiones para omitir temporalmente ciertas comprobaciones. Por ejemplo:
const workspaceCheckExclusions = []; const dependencyCheckExclusions = [];
Estas constantes le permiten excluir espacios de trabajo o dependencias específicas del proceso de validación, lo que garantiza migraciones sin problemas cuando sea necesario.
Administración de versiones con conjuntos de cambios
Otro problema que puede surgir con el crecimiento del repositorio es la gestión y publicación de versiones. Los conjuntos de cambios ofrecen una solución elegante para automatizar este proceso, garantizando que cada cambio se rastree, versione y publique.
Introducción a los conjuntos de cambios
Changesets es una herramienta de código abierto diseñada para gestionar el control de versiones en repositorios monorepo. Simplifica el seguimiento de los cambios al asignarlos a documentos pequeños y legibles que capturan la intención del cambio. Estos documentos se denominan conjuntos de cambios. Sus principales ventajas incluyen:
Documentación clara
Cada conjunto de cambios describe los cambios realizados, lo que ayuda tanto a los desarrolladores como a los consumidores a comprender qué esperar en una nueva versión.
Control de versiones granular
Cada paquete se versiona de forma independiente, lo que garantiza que solo se actualicen los paquetes afectados. Esto minimiza el riesgo de actualizaciones de versiones vacías y rupturas de dependencias.
Amigable con la colaboración
Como cada cambio se registra a través de un conjunto de cambios, los equipos pueden revisar y aprobar las actualizaciones antes del lanzamiento real.
Automatización de lanzamientos
Una de las características más potentes de los conjuntos de cambios es la capacidad de automatizar el proceso. Puedes integrarlos en tu flujo de trabajo de CI/CD y olvidarte de los cambios manuales de versiones y la publicación en NPM.
Echa un vistazo al flujo de trabajo release.yaml en el repositorio de ejemplo. Incluye el paso create-release-pull-request-or-publish
. El paso, respaldado por la acción de GitHub "changesets/action", crea toda la magia. Solo necesitas configurar NPM_TOKEN
para publicar tus paquetes. Luego, cada envío a la rama main
:
Comprueba si hay algún documento de Changeset .
Si existen documentos de conjuntos de cambios, la acción crea una solicitud de extracción con las actualizaciones necesarias del registro de cambios y las actualizaciones de versión. Si no se detectan cambios, no ocurre nada.
Comprueba si hay paquetes listos para publicar .
Si los paquetes están listos para publicarse, la acción publica las nuevas versiones en NPM mediante el
NPM_TOKEN
proporcionado. Si no hay paquetes listos para publicar, la acción finaliza sin realizar cambios.
Al automatizar estas tareas, Changesets garantiza que sus versiones sean consistentes y confiables, lo que reduce la posibilidad de errores humanos y agiliza su flujo de trabajo de desarrollo.
Integración del flujo de trabajo con GitHub Actions
Esta sección profundiza en cómo aprovechar al máximo el potencial de la arquitectura que acabamos de crear. Mediante GitHub Actions, automatizaremos las comprobaciones de calidad de las solicitudes de registro, el lanzamiento de versiones de bibliotecas y funciones, y la implementación de aplicaciones. Nos centramos en maximizar la automatización, manteniendo la calidad del código y la granularidad de las tareas.
Verificar la calidad de las relaciones públicas
Para garantizar que el código de la solicitud de incorporación de cambios se mantenga consistente y estable, creamos un flujo de trabajo dedicado a quality.yaml . Este flujo de trabajo realiza varias tareas, como garantizar que no se introduzcan cambios manuales en la versión (ya que el control de versiones se gestiona mediante conjuntos de cambios).
- id: check_version name: Check version changes run: | BASE_BRANCH=${{ github.event.pull_request.base.ref }} git fetch origin $BASE_BRANCH CHANGED_FILES=$(git diff --name-only origin/$BASE_BRANCH HEAD) VERSION_CHANGED=false for FILE in $CHANGED_FILES; do if [[ $FILE == */package.json ]]; then if [ -f "$FILE" ]; then HEAD_VERSION=$(grep '"version":' "$FILE" | awk -F '"' '{print $4}') else continue fi HEAD_VERSION=$(cat $FILE | grep '"version":' | awk -F '"' '{print $4}') if git cat-file -e origin/$BASE_BRANCH:$FILE 2>/dev/null; then BASE_VERSION=$(git show origin/$BASE_BRANCH:$FILE | grep '"version":' | awk -F '"' '{print $4}') else BASE_VERSION=$HEAD_VERSION fi if [ "$BASE_VERSION" != "$HEAD_VERSION" ]; then VERSION_CHANGED=true echo "Version change detected in $FILE" fi fi done if [ "$VERSION_CHANGED" = true ]; then echo "Manual version changes are prohibited. Use changesets instead." exit 1 fi env: GITHUB_REF: ${{ github.ref }}
Además de esta verificación, el trabajo check-quality
instala dependencias, valida restricciones, verifica dependencias circulares y verifica la calidad general del código utilizando el script que definimos anteriormente con NX:
- id: install-dependencies name: Install dependencies run: yarn --immutable - id: check-constraints name: Check constraints run: yarn constraints - id: check-circulardeps name: Check circular dependencies run: yarn check-circulardeps:all - id: check-quality name: Check quality run: BASE=origin/${{ github.event.pull_request.base.ref }} yarn quality:affected
La comprobación de calidad está diseñada para ejecutarse únicamente en los paquetes afectados por la solicitud de extracción actual. La finalización correcta de estos trabajos indica que la solicitud de extracción está lista para fusionarse (además de recibir revisiones de código).
Si se requieren controles adicionales para su proyecto, puede actualizar su nx.json
y el script de calidad manteniendo el flujo de trabajo sin cambios.
Publicar bibliotecas y funciones
Tras la fusión de una solicitud de incorporación de cambios (PR), se activa el flujo de trabajo de lanzamiento (como se describe en el capítulo "Conjuntos de cambios"). Este flujo de trabajo compila los paquetes afectados y crea una PR con las actualizaciones de versión. Una vez aprobada y fusionada la PR, release.yaml se ejecuta de nuevo. Esta vez, en lugar de crear una PR, detecta los cambios de versión y publica los paquetes actualizados en NPM.
- id: build-packages name: Build packages run: yarn build:affected - id: create-release-pull-request-or-publish name: Create Release Pull Request or Publish to NPM uses: changesets/action@v1 with: version: yarn changeset version publish: yarn release commit: "chore: publish new release" title: "chore: publish new release" env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }} release-apps: needs: release-libs-features uses: ./.github/workflows/release-apps.yaml with: publishedPackages: ${{ needs.release-libs-features.outputs.publishedPackages }}
A continuación, se ejecuta una tarea llamada release-apps
, responsable de la implementación de aplicaciones. Recibe una lista de paquetes publicados del paso anterior y nos lleva al siguiente capítulo.
Publicar aplicaciones
La parte final del proceso de lanzamiento implica la implementación de las aplicaciones (no se publican en NPM, ya que se configuran private
en package.json
). El flujo de trabajo release-apps.yaml se activa automáticamente mediante release.yaml o puede ejecutarse directamente desde la pestaña Acciones de GitHub:
name: Release Apps on: workflow_call: inputs: publishedPackages: description: "List of published packages" required: false type: string default: "[]" workflow_dispatch: inputs: publishedPackages: description: "List of published packages (optional)" required: false type: string default: "[]"
Este flujo de trabajo acepta la entrada de publishedPackages
para determinar qué paquetes se han publicado. Mediante una estrategia matricial, verifica cada aplicación de la matriz para detectar la presencia de dependencias publicadas:
- id: check-dependency-published name: Check if any app dependency is published run: | PUBLISHED_PACKAGES="${{ inputs.publishedPackages }}" PACKAGE_NAME="${{ matrix.package }}" APP="${{ matrix.app }}" DEPENDENCIES=$(jq -r '.dependencies // {} | keys[]' "apps/$APP/package.json") for DEP in $DEPENDENCIES; do if echo "$PUBLISHED_PACKAGES" | grep -w "$DEP"; then echo "published=true" >> $GITHUB_OUTPUT exit 0 fi done echo "published=false" >> $GITHUB_OUTPUT
Esta comprobación es una condición para iniciar la implementación de una aplicación. La otra condición garantiza que se haya modificado la versión de la aplicación (lo que indica que es necesario volver a implementarla incluso si no se han actualizado las dependencias).
- id: check-version-change name: Check if app version has changed run: | APP="${{ matrix.app }}" PACKAGE_JSON_PATH="apps/$APP/package.json" CURRENT_VERSION=$(jq -r '.version' "$PACKAGE_JSON_PATH") PREVIOUS_VERSION=$(git show HEAD~1:"$PACKAGE_JSON_PATH" | jq -r '.version' || echo "") if [[ "$CURRENT_VERSION" == "$PREVIOUS_VERSION" ]]; then echo "changed=false" >> $GITHUB_OUTPUT else echo "changed=true" >> $GITHUB_OUTPUT fi
Finalmente, después de confirmar que la aplicación tiene dependencias actualizadas o su versión ha cambiado, el flujo de trabajo recupera la nueva versión y procede a compilar e implementar la aplicación:
- id: set-up-docker name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - id: get-app-version name: Get the app version from package.json run: echo "app-version=$(cat ./apps/${{ matrix.app }}/package.json | jq -r '.version')" >> $GITHUB_OUTPUT - id: build-image name: Build image if: steps.check-dependency-published.outputs.published == 'true' || steps.check-version-change.outputs.changed == 'true' uses: docker/build-push-action@v4 with: build-contexts: | workspace=./ context: "./apps/${{ matrix.app }}" load: true push: false tags: | ${{ matrix.app }}:v${{ steps.get-app-version.outputs.app-version }}
En este ejemplo, creamos la imagen de Docker sin subirla a un registro. En su flujo de trabajo de producción, reemplace este paso con el proceso de implementación real.
Conclusión
Resumen de las mejores prácticas
A lo largo de este artículo, exploramos la configuración de un monorepositorio robusto y las herramientas que ayudan a gestionarlo eficientemente. Al centralizar el código fuente, no solo se simplifica la gestión de dependencias, sino que también se optimiza la colaboración entre equipos. Demostramos cómo aprovechar Yarn para compartir dependencias, acelerar la instalación con PnP y mejorar la consistencia general del proyecto. Además, la integración de NX para la ejecución de scripts específicos garantiza una integración continua (CI) rápida y eficiente. Los conjuntos de cambios ayudaron a automatizar el control de versiones, reduciendo los errores manuales y agilizando los lanzamientos. Finalmente, creamos una canalización de CI/CD lista para producción con acciones de GitHub que realiza únicamente las tareas necesarias.
Próximos pasos
- Experimente y adapte : Comience configurando un monorepositorio a pequeña escala para probar estas prácticas recomendadas. Experimente con diferentes estructuras de carpetas y amplíe gradualmente para incluir más paquetes a medida que gane confianza.
- Integre herramientas adicionales : considere integrar herramientas complementarias como PNPM o Turborepo según los requisitos únicos de su proyecto y las preferencias de su equipo.
- Mejore los pipelines de CI/CD : ajuste sus flujos de trabajo de GitHub Actions para incluir controles de calidad adicionales, cobertura de código y análisis de seguridad adaptados a su proyecto.
- Comunidad y actualizaciones : Manténgase al día con las últimas versiones de Yarn, NX y conjuntos de cambios. Interactúe con la comunidad para compartir ideas y conocer las nuevas tendencias en la gestión de monorepositorios.
Recursos
Acceda al repositorio de ejemplo completo creado para esta guía. Explore la estructura del proyecto, ejemplos de código y scripts que muestran la configuración de monorepositorio en acción.
Consulte el paquete NPM publicado como parte de este proyecto. Estos paquetes demuestran el uso y la implementación en el mundo real de los conceptos analizados en el artículo.