513 odczyty
513 odczyty

Budowanie solidnego monorepozytorium JS/TS: najlepsze praktyki z Yarn, NX i zestawami zmian

przez Teimur Gasanov22m2025/03/24
Read on Terminal Reader

Za długo; Czytać

W tym artykule wyjaśniono, jak zbudować solidne monorepo JS/TS przy użyciu Yarn v4 dla obszarów roboczych, NX dla wydajnego wykonywania skryptów i zarządzania zależnościami oraz Changesets dla automatycznego wersjonowania i wydań. Obejmuje on również strukturowanie kodu w aplikacjach, funkcjach i bibliotekach, a także integrację GitHub Actions dla CI/CD w celu zapewnienia jakości kodu i usprawnienia wdrożeń.
featured image - Budowanie solidnego monorepozytorium JS/TS: najlepsze praktyki z Yarn, NX i zestawami zmian
Teimur Gasanov HackerNoon profile picture
0-item
1-item

Wstęp

Dlaczego Monorepo?

Obecnie nie można zaprzeczyć szybkiej ewolucji rozwoju oprogramowania. Zespoły rosną, projekty są coraz bardziej złożone. Firmy przeznaczają znaczne zasoby na utrzymanie rozproszonej bazy kodu składającej się z wielu fragmentów. Wkracza monorepo — pojedyncze, ujednolicone repozytorium, które łączy cały kod. Monorepo, dalekie od trendu, stało się ostatnio podejściem architektonicznym do przechowywania całej bazy kodu w jednym miejscu. Zespoły uzyskują ulepszone współdzielenie kontekstu, płynną współpracę i narzędzie, które naturalnie zachęca do ponownego wykorzystywania kodu.

Konfigurowanie obszarów roboczych Yarn

Uwaga: W niniejszym artykule za każdym razem, gdy jest mowa o Yarn, chodzi konkretnie o Yarn v4 — najnowszą wersję oferującą rozszerzone możliwości i lepszą wydajność.

Czym są obszary robocze Yarn?

Obszary robocze to pakiety monorepo, często nazywane pakietami. Pomagają one zarządzać wieloma pakietami w jednym repozytorium bez wysiłku. Dzięki obszarom roboczym możesz:

  • Łatwe udostępnianie zależności:

    Bezproblemowo udostępniaj wspólne zależności w ramach swojego projektu.

  • Uprość zarządzanie zależnościami:

    Yarn automatycznie łączy lokalne pakiety, redukując duplikację i ułatwiając rozwój.

  • Przyspiesz instalacje:

    Skorzystaj z optymalizacji wydajności Yarn i mechanizmów buforowania (tj. wbudowanej funkcji plug'n'play ).

  • Popraw kontrolę nad Monorepo:

    Zdefiniuj ograniczenia (reguły) i wykorzystaj dziesiątki dostępnych wtyczek, aby zachować spójność.


Chociaż Yarn jest wybranym menedżerem dla tego artykułu ze względu na swoją prostotę, szybkość i rozbudowane opcje konfiguracji - ważne jest, aby pamiętać, że właściwy wybór zależy od konkretnych potrzeb projektu, preferencji zespołu i ogólnego przepływu pracy. Na przykład PNPM i Turborepo to inne nowoczesne narzędzia, które oferują szeroki zakres funkcji.

Konfiguracja początkowa

Konfiguracja Yarn jest prostym procesem. Postępuj zgodnie z oficjalnym przewodnikiem, aby zainstalować i skonfigurować Yarn w swoim projekcie: Yarn Installation Guide .

Po zakończeniu instalacji przejdźmy do konfiguracji. Ponieważ używamy plug'n'play, musisz upewnić się, że Twoje IDE poprawnie rozpoznaje zależności. Jeśli używasz VSCode, uruchom:

 # Typescript is required for VSCode SDK to set up correctly yarn add -D typescript@^5 yarn dlx @yarnpkg/sdks vscode

Jeśli używasz innego edytora kodu, sprawdź dostępne zestawy SDK tutaj: Yarn Editor SDKs .

W tym momencie możesz już zacząć używać Yarn.

Organizacja struktury Monorepo

Teraz, gdy menedżer pakietów jest skonfigurowany, czas zaprojektować skalowalną organizację projektu. Przejrzysta, dobrze zdefiniowana struktura nie tylko ułatwia nawigację po repozytorium, ale także promuje lepsze ponowne wykorzystanie kodu. W tym przykładzie podzielimy bazę kodu na trzy główne kategorie:

  • Aplikacje :

    • Klient: Zawiera ostateczne, możliwe do wdrożenia produkty klienckie.
    • Serwer: Zawiera ostateczne, gotowe do wdrożenia produkty serwerowe.
  • Cechy :

    • Klient: Dla samodzielnych widżetów interfejsu użytkownika.
    • Serwer: Dla samodzielnych elementów logiki biznesowej zaplecza.
  • Biblioteki :

    Domy współdzielą kod, taki jak komponenty systemu projektowego, stałe, zasoby i narzędzia. Jest to strefa bezkontekstowa do przechowywania logiki wielokrotnego użytku.


Aby zademonstrować moc tej struktury folderów, zacznijmy od dodania tych głównych folderów do listy obszarów roboczych Yarn. W swoim głównym pliku package.json dodaj następujące elementy:

 "workspaces": [ "apps/**", "features/**", "libs/**" ]

Ta konfiguracja mówi Yarn, aby traktował pakiety w tych folderach jako pakiety lokalne. Kolejne instalacje zapewnią, że zależności dla każdego pakietu zostaną prawidłowo skonfigurowane i połączone.

Bootstrapping bazy kodu

W tej sekcji przejdziemy przez minimalny przykład bazy kodu, który ilustruje, jak uruchomić monorepo. Zamiast dołączać pełne fragmenty kodu, podam krótkie przykłady z linkami do kompletnych plików w repozytorium utworzonym specjalnie na potrzeby tego artykułu .

Bootstrapping aplikacji serwera

Zaczynamy od prostego Express API do uwierzytelniania użytkowników. Ta aplikacja serwerowa udostępnia pojedynczy punkt końcowy ( /auth/signIn ), który wykorzystuje handler z innego pakietu.

 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}`); });

Link do pakietu


Jak widać, punkt końcowy /auth/signIn używa handlera zaimportowanego z innego pakietu. To prowadzi nas do naszego następnego komponentu: funkcji serwera.

Funkcja serwera Bootstrapping

Funkcja serwera obejmuje logikę uwierzytelniania. W tym pakiecie definiujemy obsługę logowania, która wykorzystuje współdzielone narzędzie walidacji z bibliotek.

 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;

Link do pakietu


To podejście podsumowuje logikę uwierzytelniania w ramach własnego pakietu, co pozwala na jego niezależne rozwijanie i utrzymywanie. Zwróć uwagę, jak narzędzia walidatora są importowane ze współdzielonej biblioteki .

Bootstrapping aplikacji klienckiej

Następnie przyjrzyjmy się stronie klienta. W naszej aplikacji klienckiej budujemy prostą witrynę, która umożliwia uwierzytelnianie użytkownika poprzez wywołanie API serwera.

 "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> ); }

Link do pakietu

W tym przykładzie komponent SignInForm został zaimportowany z pakietu funkcji klienta, co prowadzi nas do ostatniego komponentu.

Funkcja klienta bootstrappingowego

Pakiet funkcji klienta zapewnia formularz uwierzytelniania wraz ze wspólną logiką walidacji. Zapobiega to duplikowaniu kodu i zapewnia spójność.

 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;

Link do pakietu


Tutaj ponownie widzimy wykorzystanie walidatora z naszych współdzielonych bibliotek, co zapewnia scentralizowanie logiki walidacji i łatwą konserwację.


To wszystko w naszym przykładzie minimalnej bazy kodu. Pamiętaj, że ten kod jest uproszczoną ilustracją mającą na celu zademonstrowanie podstawowej struktury i połączeń między aplikacjami, funkcjami i bibliotekami w monorepo. Możesz rozszerzyć te przykłady w razie potrzeby, aby dopasować je do konkretnych wymagań swojego projektu.

Uruchamianie skryptów za pomocą NX

Zarządzanie skryptami w monorepo może być trudne. Podczas gdy Yarn pozwala uruchamiać skrypty w wielu pakietach przy użyciu różnych warunków, może wymagać niestandardowego skryptowania w celu uzyskania bardziej szczegółowej kontroli. Tutaj właśnie pojawia się NX: zapewnia gotowe rozwiązanie do wydajnego, ukierunkowanego wykonywania skryptów.

Wprowadzenie do NX

NX to system kompilacji zoptymalizowany dla monorepozytoriów z zaawansowanymi możliwościami CI. Dzięki NX możesz:

  • Efektywne wykonywanie zadań równolegle : Wykorzystaj współbieżność, aby przyspieszyć kompilacje.
  • Identyfikuj relacje zależności : Zrozum powiązania między pakietami i skryptami.
  • Buforuj wyniki wykonywania skryptu : unikaj powtarzającej się pracy poprzez buforowanie wyników.
  • Dostosuj zachowanie za pomocą wtyczek: Rozszerz funkcjonalność za pomocą bogatego ekosystemu wtyczek .

Celowane wykonywanie skryptów

Aby wykorzystać możliwości NX, najpierw musimy utworzyć plik nx.json , aby zdefiniować zestaw reguł dla naszych skryptów. Poniżej znajduje się przykładowa konfiguracja:

 { "targetDefaults": { "build": { "dependsOn": [ "^build" ], "outputs": [ "{projectRoot}/dist" ], "cache": true }, "typecheck": { "dependsOn": [ "^build", "^typecheck" ] }, "lint": { "dependsOn": [ "^build", "^lint" ] } }, "defaultBase": "main" }

Mówiąc prościej, ta konfiguracja oznacza:

  • Zbudować

    Skrypt build pakietu jest zależny od pomyślnego skompilowania jego zależności, a jego dane wyjściowe są buforowane.

  • Kontrola typu

    Skrypt typecheck pakietu zależy zarówno od skryptów kompilacji, jak i kontroli typu jego zależności.

  • Szarpie

    Skrypt lint dla pakietu zależy zarówno od skryptów kompilacji, jak i lint jego zależności.


Teraz dodajmy skrypty do 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}" }

Tutaj definiujemy cztery typy skryptów wykonawczych:

  • build: Buduje pakiet.

  • typecheck: Sprawdza typy pakietu.

  • lint: Kłaczki na opakowaniu.

  • jakość: Uruchamia zarówno kontrolę typu, jak i lint.


Każdy skrypt ma dwie wersje:

  • wszystkie: Uruchamia skrypt dla wszystkich pakietów.
  • affected: Uruchamia skrypt tylko w przypadku pakietów, których dotyczą ostatnie zmiany. Zmienne środowiskowe BASE i HEAD pozwalają określić zakres (domyślnie origin/main i bieżący HEAD ), umożliwiając szczegółowe wykonywanie żądań ściągnięcia. Może to znacznie zaoszczędzić czas i zasoby.

Zarządzanie zależnościami cyklicznymi

NX udostępnia również wbudowane polecenie do generowania grafu zależności, który może pomóc w wykrywaniu cykli zależności. Poniższy skrypt używa wyjścia grafu NX do sprawdzania zależności cyklicznych i kończy się niepowodzeniem, jeśli takie zostaną znalezione.

Utwórz plik o nazwie scripts/check-circulardeps.mjs z następującą zawartością:

 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();

Ten skrypt:

  • Wykonuje polecenie NX w celu wygenerowania grafu zależności.
  • Odczytuje wykres z tymczasowego pliku JSON.
  • Rekurencyjnie sprawdza cykle.
  • Rejestruje błąd i wychodzi, jeśli zostanie wykryta zależność cykliczna.

Sprawdzanie zależności za pomocą ograniczeń Yarn

W miarę rozwoju projektów utrzymanie spójności między zależnościami staje się wyzwaniem. Egzekwowanie ścisłych reguł dotyczących zależności, wersji Node i innych konfiguracji jest niezbędne, aby uniknąć niepotrzebnego długu technicznego. Ograniczenia Yarn oferują sposób na automatyzację tych walidacji.

Zrozumienie ograniczeń przędzy

Yarn Constraints to zbiór reguł dla pakietów w Twoim monorepo. Istotną zaletą ich używania jest to, że jesteś menedżerem tych reguł. Na przykład możesz utworzyć regułę, aby wymusić na wszystkich pakietach używanie tej samej wersji React. Po jej ustawieniu nigdy nie napotkasz problemu, gdy aplikacja hosta nie może użyć funkcji/biblioteki z wyższą wersją React.

Chociaż migracja dużego monorepo do nowej głównej wersji zależności może być skomplikowana, wykorzystanie ograniczeń ostatecznie zapewnia spójność i stabilność całego projektu.

Wymuszanie spójności

W naszym przykładowym repozytorium używamy plikuyarn.config.cjs w celu wymuszenia spójności dla:

  • Wersja węzła

  • Wersja włóczki

  • Wersje zależności


Aby zapewnić elastyczność podczas przejść, możesz zdefiniować wykluczenia, aby tymczasowo ominąć pewne kontrole. Na przykład:

 const workspaceCheckExclusions = []; const dependencyCheckExclusions = [];

Stałe te umożliwiają wykluczenie określonych obszarów roboczych lub zależności z procesu walidacji, co zapewnia płynne migracje, gdy zajdzie taka potrzeba.

Zarządzanie wersjami za pomocą zestawów zmian

Innym problemem, z którym możesz się spotkać przy wzroście repozytorium, jest zarządzanie wersjami i wydawanie. Zestawy zmian zapewniają eleganckie rozwiązanie do automatyzacji tego procesu, zapewniając, że każda zmiana jest śledzona, wersjonowana i wydawana.

Wprowadzenie do zestawów zmian

Changesets to narzędzie typu open source przeznaczone do zarządzania wersjonowaniem w repozytoriach monorepo. Upraszcza proces śledzenia zmian, przydzielając je do małych, czytelnych dla człowieka dokumentów, które odzwierciedlają intencję zmiany. Dokumenty te nazywane są changesetami. Kluczowe korzyści obejmują:

  • Przejrzysta dokumentacja

    Każdy zestaw zmian przedstawia wprowadzone zmiany, co pomaga zarówno deweloperom, jak i konsumentom zrozumieć, czego mogą się spodziewać po nowej wersji.

  • Granularna kontrola wersji

    Każdy pakiet jest wersjonowany niezależnie, co zapewnia, że tylko pakiety, których to dotyczy, są aktualizowane. Minimalizuje to ryzyko pustych podbić wersji i zerwania zależności.

  • Przyjazny dla współpracy

    Ponieważ każda zmiana jest rejestrowana w zestawie zmian, zespoły mogą przeglądać i zatwierdzać aktualizacje przed faktycznym wydaniem.

Automatyzacja wydań

Jedną z najpotężniejszych funkcji Changesets jest możliwość automatyzacji procesu. Możesz zintegrować Changesets z Twoim potokiem CI/CD i zapomnieć o ręcznych zmianach wersji i publikowaniu NPM.

Spójrz na przepływ pracy release.yaml w przykładowym repozytorium. Ma on krok create-release-pull-request-or-publish . Krok wspierany przez changesets/action GitHub action tworzy całą magię. Musisz tylko skonfigurować NPM_TOKEN do publikowania swoich pakietów. Następnie każde wypchnięcie do gałęzi main spowoduje:

  • Sprawdź, czy istnieją jakieś dokumenty Changeset .

    Jeśli dokumenty zestawu zmian są obecne, akcja tworzy żądanie ściągnięcia z niezbędnymi podbiciami wersji i aktualizacjami dziennika zmian. Jeśli nie zostaną wykryte żadne zmiany, nic się nie dzieje.

  • Sprawdź, czy są jakieś pakiety gotowe do opublikowania .

    Jeśli pakiety są gotowe do wydania, akcja publikuje nowe wersje w NPM przy użyciu dostarczonego NPM_TOKEN . Jeśli nie ma żadnych pakietów gotowych do opublikowania, akcja kończy działanie bez wprowadzania zmian.


Automatyzując te zadania, Changesets gwarantuje spójność i niezawodność wydań, zmniejszając ryzyko wystąpienia błędu ludzkiego i usprawniając proces tworzenia oprogramowania.

Integracja przepływu pracy z akcjami GitHub

Ta sekcja zagłębia się w to, jak uwolnić moc architektury, którą właśnie zbudowaliśmy. Używając GitHub Actions, zautomatyzujemy kontrole jakości PR, wydania wersji dla bibliotek i funkcji oraz wdrożenia aplikacji. Skupiamy się na maksymalizacji automatyzacji przy jednoczesnym zachowaniu jakości kodu i szczegółowości zadań.

Sprawdź jakość PR

Aby zapewnić spójność i stabilność kodu pull request, tworzymy dedykowany przepływ pracy quality.yaml . Ten przepływ pracy wykonuje kilka zadań, takich jak zapewnienie, że ręczne zmiany wersji nie zostaną wprowadzone (ponieważ wersjonowanie jest zarządzane przez Changesets):

 - 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 }}

Oprócz tego sprawdzenia zadanie check-quality instaluje zależności, weryfikuje ograniczenia, sprawdza zależności cykliczne i weryfikuje ogólną jakość kodu przy użyciu skryptu, który zdefiniowaliśmy wcześniej w 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

Kontrola jakości jest zaprojektowana tak, aby działała tylko na pakietach objętych bieżącym żądaniem ściągnięcia. Pomyślne ukończenie tych zadań sygnalizuje, że żądanie ściągnięcia jest gotowe do scalenia (oprócz otrzymania przeglądów kodu).

Jeśli w Twoim projekcie konieczne będą dodatkowe kontrole, możesz zaktualizować plik nx.json i skrypt jakości, zachowując niezmieniony przepływ pracy.

Publikuj biblioteki i funkcje

Po scaleniu PR uruchamiany jest przepływ pracy wydania (opisany w rozdziale Changesets). Ten przepływ pracy buduje pakiety, których to dotyczy, i tworzy PR z podbiciem wersji. Po zatwierdzeniu i scaleniu tego PR release.yaml uruchamia się ponownie — tym razem zamiast tworzyć PR, wykrywa zmiany wersji i wydaje zaktualizowane pakiety do 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 }}

Następnie wykonywane jest zadanie o nazwie release-apps , które odpowiada za wdrożenia aplikacji. Otrzymuje listę opublikowanych pakietów z poprzedniego kroku i przenosi nas do następnego rozdziału.

Publikuj aplikacje

Ostatnia część procesu wydania obejmuje wdrożenie aplikacji (aplikacje nie są publikowane w NPM, ponieważ są ustawione private w package.json ). Przepływ pracy release-apps.yaml jest automatycznie wyzwalany przez release.yaml lub może być wykonywany bezpośrednio z zakładki Actions w 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: "[]"

Ten przepływ pracy akceptuje dane wejściowe publishedPackages , aby określić, które pakiety zostały opublikowane. Używając strategii macierzy, sprawdza każdą aplikację macierzy pod kątem obecności opublikowanych zależności:

 - 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

To sprawdzenie jest jednym z warunków inicjowania wdrożenia aplikacji. Drugi warunek zapewnia, że wersja aplikacji została zmieniona (co oznacza, że ponowne wdrożenie jest konieczne, nawet jeśli nie zaktualizowano żadnych zależności):

 - 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

Na koniec, po potwierdzeniu, że aplikacja ma zaktualizowane zależności lub że jej wersja uległa zmianie, przepływ pracy pobiera nową wersję i przystępuje do kompilowania i wdrażania aplikacji:

 - 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 }}

W tym przykładzie budujemy obraz Dockera bez wypychania go do rejestru. W swoim przepływie pracy produkcyjnej zastąp ten krok rzeczywistym procesem wdrażania.

Wniosek

Podsumowanie najlepszych praktyk

W tym artykule przyjrzeliśmy się konfiguracji solidnego monorepo i narzędziom, które pomagają sprawnie nim zarządzać. Centralizując bazę kodu, nie tylko upraszczasz zarządzanie zależnościami, ale także usprawniasz współpracę między zespołami. Pokazaliśmy, jak Yarn może być wykorzystywany do współdzielenia zależności, przyspieszania instalacji z PnP i poprawy ogólnej spójności projektu. Ponadto integracja NX w celu ukierunkowanego wykonywania skryptów zapewnia, że CI jest szybkie i wydajne. Zestawy zmian pomogły zautomatyzować wersjonowanie, zmniejszając liczbę błędów ręcznych i usprawniając wydania. Na koniec stworzyliśmy gotowy do produkcji potok CI/CD z akcjami GitHub, który wykonuje tylko niezbędne zadania.

Następne kroki

  1. Eksperymentuj i dostosowuj : Zacznij od skonfigurowania monorepo na małą skalę, aby przetestować te najlepsze praktyki. Eksperymentuj z różnymi strukturami folderów i stopniowo rozszerzaj je, aby uwzględnić więcej pakietów, gdy wzrośnie Twoje zaufanie.
  2. Zintegruj dodatkowe narzędzia : Rozważ zintegrowanie uzupełniających narzędzi, takich jak PNPM lub Turborepo, w zależności od unikalnych wymagań Twojego projektu i preferencji zespołu.
  3. Ulepsz procesy CI/CD : dostosuj przepływy pracy GitHub Actions, aby uwzględnić dodatkowe kontrole jakości, pokrycie kodu i skanowanie zabezpieczeń dostosowane do Twojego projektu.
  4. Społeczność i aktualizacje : Bądź na bieżąco z najnowszymi wersjami Yarn, NX i Changesets. Współpracuj ze społecznością, aby dzielić się spostrzeżeniami i poznawać pojawiające się trendy w zarządzaniu monorepo.

Zasoby

  • Przykładowe repozytorium :

    Uzyskaj dostęp do kompletnego repozytorium przykładów utworzonego na potrzeby tego przewodnika. Poznaj strukturę projektu, przykłady kodu i skrypty, które pokazują konfigurację monorepo w działaniu.

  • Opublikowane pakiety NPM :

    Sprawdź rzeczywisty pakiet NPM opublikowany w ramach tego projektu. Te pakiety demonstrują rzeczywiste wykorzystanie i implementację koncepcji omówionych w artykule.


Trending Topics

blockchaincryptocurrencyhackernoon-top-storyprogrammingsoftware-developmenttechnologystartuphackernoon-booksBitcoinbooks