מָבוֹא
למה מונורפו?
כיום, אי אפשר להכחיש את ההתפתחות המהירה של פיתוח תוכנה. הצוותים גדלים, פרויקטים נוטים להיות מורכבים יותר. חברות מוציאות משאבים משמעותיים כדי לשמור על בסיס קוד מבוזר המורכב מפרגמנטים רבים. היכנסו למונורפו - מאגר יחיד ומאוחד שמאגד את כל הקוד שלכם. רחוק מלהיות טרנד, monorepos הפכו לאחרונה לגישה אדריכלית לשכן את כל בסיס הקוד במקום אחד. צוותים משיגים שיתוף הקשר משופר, שיתוף פעולה חלק וכלי שמעודד באופן טבעי שימוש חוזר בקוד.
הקמת סביבות עבודה של חוט
הערה: בכל מאמר זה, בכל פעם ש"Yarn" מוזכר, הוא מתייחס ספציפית ל-Yarn v4 - הגרסה העדכנית ביותר המציעה יכולות משופרות וביצועים משופרים.
מהם חללי עבודה של חוט?
חללי עבודה הם החבילות של המונורפו, הנקראות לעתים קרובות חבילות. הם עוזרים לך לנהל מספר חבילות במאגר אחד ללא מאמץ. עם חללי עבודה. אתה יכול:
שתף תלות בקלות:
שתף תלות משותפת בפרויקט שלך בצורה חלקה.
פשט את ניהול התלות:
Yarn מקשר אוטומטית חבילות מקומיות, מפחית כפילות ומקל על פיתוח.
האץ התקנות:
תיהנו ממיטוב הביצועים ומנגנוני האחסון של Yarn (כלומר, plug'n'play מובנה ).
שפר את השליטה על Monorepo:
הגדירו אילוצים (כללים) והשתמשו בעשרות תוספים זמינים כדי לשמור על עקביות.
בעוד Yarn הוא המנהל הנבחר למאמר זה בזכות הפשטות, המהירות ואפשרויות התצורה הנרחבות שלו - חשוב לציין שהבחירה הנכונה תלויה בצרכים הספציפיים של הפרויקט שלך, בהעדפות הצוות ובזרימת העבודה הכוללת. לדוגמה, PNPM ו- Turborepo הם כלים מודרניים אחרים המציעים מגוון רחב של תכונות.
תצורה ראשונית
התקנת חוט היא תהליך פשוט. עקוב אחר המדריך הרשמי להתקנה והגדרה של Yarn בפרויקט שלך: מדריך התקנת חוטים .
לאחר שתסיים את ההתקנה, בוא נעבור לתצורה. מכיוון שאנו משתמשים ב-plug'n'play, עליך לוודא שה-IDE שלך מזהה כראוי תלות. אם אתה משתמש ב- VSCode, הרץ:
# Typescript is required for VSCode SDK to set up correctly yarn add -D typescript@^5 yarn dlx @yarnpkg/sdks vscode
אם אתה משתמש בעורך קוד אחר, בדוק את ערכות ה-SDK הזמינות כאן: ערכות SDK של Yarn Editor .
בשלב זה, אתה מוכן להתחיל להשתמש ב-Yarn.
ארגון מבנה מונורפו
כעת לאחר שמנהל החבילות מוגדר, הגיע הזמן לעצב ארגון פרויקט שניתן להרחבה. מבנה ברור ומוגדר היטב לא רק מקל על הניווט במאגר אלא גם מקדם שימוש חוזר טוב יותר בקוד. בדוגמה זו, נחלק את בסיס הקוד לשלוש קטגוריות עיקריות:
אפליקציות :
- לקוח: מכיל את מוצרי הלקוח הסופיים הניתנים לפריסה.
- שרת: מכיל את מוצרי השרת הסופיים הניתנים לפריסה.
תכונות :
- לקוח: עבור ווידג'טים של ממשק משתמש עצמאיים.
- שרת: עבור חלקי לוגיקה עסקיים עצמאיים.
ליבס :
בתים קוד משותף כגון עיצוב רכיבי מערכת, קבועים, נכסים וכלי עזר. זהו האזור נטול הקשר לאחסון לוגיקה לשימוש חוזר.
כדי להדגים את העוצמה של מבנה התיקיות הזה, נתחיל בהוספת התיקיות העיקריות הללו לרשימת סביבות העבודה של Yarn. ב-root package.json שלך, הוסף את הדברים הבאים:
"workspaces": [ "apps/**", "features/**", "libs/**" ]
תצורה זו אומרת ל-Yarn להתייחס לחבילות בתיקיות אלו כאל חבילות מקומיות. התקנות עוקבות יבטיחו שהתלות של כל חבילה מוגדרת ומקושרת כהלכה.
Bootstrapping Codebase
בסעיף זה, נעבור על דוגמה מינימלית של בסיס קוד שממחישה כיצד לאתחל את המונורפו. במקום לכלול קטעי קוד מלאים, אספק דוגמאות קצרות עם קישורים לקבצים המלאים במאגר שנוצר במיוחד עבור מאמר זה .
יישום שרת אתחול
אנו מתחילים עם Express API פשוט לאימות משתמשים. יישום שרת זה חושף נקודת קצה אחת ( /auth/signIn
) המשתמשת במטפל מחבילה אחרת.
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}`); });
כפי שאתה יכול לראות, נקודת הקצה /auth/signIn
משתמשת במטפל מיובא מחבילה אחרת. זה מביא אותנו לרכיב הבא שלנו: תכונת השרת.
תכונת שרת אתחול
תכונת השרת מקפלת את היגיון האימות. בחבילה זו, אנו מגדירים את מטפל הכניסה, הממנף כלי אימות משותף מה-libs.
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;
גישה זו מסכמת את היגיון האימות בתוך החבילה שלה, ומאפשרת לפתח ולתחזק אותו באופן עצמאי. שים לב כיצד כלי האימות מיובאים מה- lib המשותף .
יישום לקוח אתחול
לאחר מכן, בואו נסתכל על צד הלקוח. באפליקציית הלקוח שלנו, אנו בונים אתר אינטרנט פשוט המאפשר אימות משתמשים על ידי הפעלת ה-API של השרת.
"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> ); }
בדוגמה זו, רכיב SignInForm
מיובא מחבילת תכונות לקוח, מה שמוביל אותנו לרכיב הסופי שלנו.
תכונת לקוח אתחול
חבילת תכונות הלקוח מספקת את טופס האימות יחד עם היגיון האימות המשותף. זה מונע שכפול קוד ומבטיח עקביות.
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;
כאן, אנו שוב רואים את השימוש במאמת מהספרות המשותפות שלנו, מה שמבטיח שהלוגיקת האימות מרוכזת ומתוחזקת בקלות.
זהו הדוגמה של בסיס הקוד המינימלי שלנו. זכור כי קוד זה הוא איור מפושט שנועד להדגים את המבנה הבסיסי ואת החיבור בין אפליקציות, תכונות ו-Libs ב-monrepo. אתה יכול להרחיב על דוגמאות אלה לפי הצורך כדי להתאים לדרישות הספציפיות של הפרויקט שלך.
הפעלת סקריפטים עם NX
ניהול סקריפטים במונורפו יכול להיות מאתגר. בעוד Yarn מאפשר לך להריץ סקריפטים על פני מספר חבילות תוך שימוש בתנאים שונים, זה עשוי לדרוש סקריפטים מותאם אישית לשליטה מפורטת יותר. כאן נכנס NX לתמונה: הוא מספק פתרון מחוץ לקופסה לביצוע סקריפט יעיל וממוקד.
מבוא ל-NX
NX היא מערכת בנייה מותאמת עבור monorepos עם יכולות CI מתקדמות. עם NX, אתה יכול:
- הפעל משימות ביעילות במקביל : נצל במקביל כדי להאיץ את הבנייה שלך.
- זיהוי יחסי תלות : הבן קשרים בין חבילות וסקריפטים.
- תוצאות ביצוע סקריפט מטמון : הימנע מעבודה מיותרת על ידי שמירת פלטים במטמון.
- התאם אישית את ההתנהגות באמצעות תוספים: הרחב את הפונקציונליות באמצעות מערכת אקולוגית עשירה של תוספים .
ביצוע סקריפט ממוקד
כדי לרתום את היכולות של NX, תחילה עלינו ליצור קובץ nx.json
כדי להגדיר מערכת כללים עבור הסקריפטים שלנו. להלן דוגמה לתצורה:
{ "targetDefaults": { "build": { "dependsOn": [ "^build" ], "outputs": [ "{projectRoot}/dist" ], "cache": true }, "typecheck": { "dependsOn": [ "^build", "^typecheck" ] }, "lint": { "dependsOn": [ "^build", "^lint" ] } }, "defaultBase": "main" }
באנגלית פשוטה, משמעות התצורה הזו היא:
לִבנוֹת
סקריפט
build
עבור חבילה תלוי בבנייה המוצלחת של התלות שלה, והפלט שלה שמור במטמון.בדיקת סוג
סקריפט
typecheck
עבור חבילה תלוי הן בסקריפט הבנייה והן בסקריפט בדיקת הסוג של התלות שלה.מוֹך
סקריפט
lint
עבור חבילה תלוי הן בסקריפט ה-build והן בסקריפט ה-lint של התלות שלה.
כעת, בואו נוסיף סקריפטים ל- 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}" }
כאן, אנו מגדירים ארבעה סוגים של סקריפטים לביצוע:
build: בונה חבילה.
typecheck: בודק את סוגי החבילה.
מוך: מוך חבילה.
איכות: מפעיל גם בדיקת סוג וגם מוך.'
לכל סקריפט שתי גרסאות:
- all: מריץ את הסקריפט בכל החבילות.
- מושפע: מפעיל את הסקריפט רק על חבילות שהושפעו מהשינויים האחרונים. משתני הסביבה
BASE
ו-HEAD
מאפשרים לך לציין טווח (ברירת המחדל למקורorigin/main
וה-HEAD
הנוכחי), המאפשרים ביצוע מפורט בבקשות משיכה. זה יכול לחסוך באופן משמעותי זמן ומשאבים.
ניהול תלות מעגלית
NX מספקת גם פקודה מובנית ליצירת גרף תלות, שיכול לעזור בזיהוי מחזורי תלות. הסקריפט הבא משתמש בפלט גרף NX כדי לבדוק תלות מעגלית ונכשל אם נמצאו כאלה.
צור קובץ בכתובת scripts/check-circulardeps.mjs
עם התוכן הבא:
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();
התסריט הזה:
- מבצע את פקודת NX ליצירת גרף תלות.
- קורא את הגרף מקובץ JSON זמני.
- בודק באופן רקורסיבי עבור מחזורים.
- רושם שגיאה ויוצא אם מזוהה תלות מעגלית.
אימות תלות עם אילוצי חוט
ככל שפרויקטים גדלים, שמירה על עקביות על פני תלות הופכת למאתגרת. אכיפת כללים נוקשים סביב תלות, גרסאות צומת ותצורות אחרות חיונית כדי למנוע חובות טכניים מיותרים. אילוצי חוט מציעים דרך להפוך את האימותים הללו לאוטומטיים.
הבנת אילוצי חוט
אילוצי חוט הם מערכת הכללים עבור חבילות ב-monorepo שלך. יתרון משמעותי בשימוש בהם הוא שאתה המנהל של הכללים הללו. לדוגמה, אתה יכול ליצור כלל כדי לאלץ את כל החבילות להשתמש באותה גרסת React. לאחר הגדרתו, לעולם לא תתקל בבעיה כאשר יישום מארח אינו יכול להשתמש בתכונה/lib עם גרסת React גבוהה יותר.
בעוד שהעברה של מונורופו גדול לגרסה עיקרית חדשה של תלות עשויה להיות מורכבת, השימוש באילוצים מביא בסופו של דבר לעקביות ויציבות לכל הפרויקט.
אכיפת עקביות
במאגר לדוגמה שלנו, אנו משתמשים בקובץyarn.config.cjs כדי לאכוף עקביות עבור:
גרסת הצומת
גרסת חוט
גרסאות התלות
כדי לאפשר גמישות במהלך מעברים, אתה יכול להגדיר אי הכללות כדי לעקוף זמנית בדיקות מסוימות. לְמָשָׁל:
const workspaceCheckExclusions = []; const dependencyCheckExclusions = [];
הקבועים האלה מאפשרים לך לא לכלול סביבות עבודה או תלות ספציפיים מתהליך האימות, ומבטיחים העברות חלקות בעת הצורך.
ניהול גירסאות עם ערכות שינויים
בעיה נוספת שאתה עלול להתמודד עם צמיחת המאגר היא ניהול ושחרור הגרסאות. ערכות שינויים מספקות פתרון אלגנטי לאוטומציה של תהליך זה, ומבטיחות שכל שינוי יהיה במעקב, גרסה ושחרור.
מבוא ל-Changesets
Changesets הוא כלי קוד פתוח שנועד לנהל ניהול גרסאות במאגרי monorepo. זה מפשט את תהליך המעקב אחר השינויים על ידי הקצאתם למסמכים קטנים הניתנים לקריאה על ידי אדם, המתעדים את כוונת השינוי. מסמכים אלו נקראים שינויים. היתרונות העיקריים כוללים:
תיעוד ברור
כל ערכת שינויים מתארת את השינויים שבוצעו, מה שעוזר הן למפתחים והן לצרכנים להבין למה לצפות במהדורה חדשה.
בקרת גרסאות מפורטת
כל חבילה מנוסחת באופן עצמאי, מה שמבטיח שרק החבילות המושפעות מתעדכנות. זה ממזער את הסיכון למכות גרסה ריקות והפסקות תלות.
ידידותי לשיתוף פעולה
מכיוון שכל שינוי מתועד באמצעות ערכת שינויים, צוותים יכולים לבדוק ולאשר עדכונים לפני ההפצה בפועל.
אוטומציה של מהדורות
אחת התכונות החזקות ביותר של Changesets היא היכולת להפוך את התהליך לאוטומטי. אתה יכול לשלב Changesets בצנרת ה-CI/CD שלך ולשכוח משינויי גרסאות ידניים ופרסום NPM.
תסתכל על זרימת העבודה release.yaml במאגר לדוגמה. יש לו שלב create-release-pull-request-or-publish
. הצעד המגובה בפעולת שינויים/פעולת GitHub יוצר את כל הקסם. אתה רק צריך להגדיר NPM_TOKEN
לפרסום החבילות שלך. לאחר מכן, כל דחיפה לסניף main
תעשה:
בדוק אם יש מסמכי Changeset .
אם קיימים מסמכי ערכת שינויים, הפעולה יוצרת בקשת משיכה עם בליטות הגרסה הדרושות ועדכוני יומן השינויים. אם לא מזוהים שינויים, שום דבר לא קורה.
בדוק אם יש חבילות מוכנות לפרסום .
אם החבילות מוכנות לשחרור, הפעולה מפרסמת את הגרסאות החדשות ל-NPM באמצעות
NPM_TOKEN
שסופק. אם אין חבילות מוכנות לפרסום, הפעולה יוצאת מבלי לבצע שינויים.
על ידי אוטומציה של משימות אלו, Changesets מבטיחים שהגרסאות שלך יהיו עקביות ואמינות, מצמצמות את הפוטנציאל לטעויות אנוש ומייעלות את זרימת העבודה בפיתוח שלך.
שילוב זרימת עבודה עם פעולות GitHub
חלק זה מתעמק כיצד לשחרר את כוחה של הארכיטקטורה שזה עתה בנינו. באמצעות GitHub Actions, נהפוך בדיקות איכות יחסי ציבור לאוטומטיות, שחרור גרסאות לספריות ותכונות ופריסות יישומים. ההתמקדות היא במקסום אוטומציה תוך שמירה על איכות הקוד ופירוט העבודה.
בדוק את איכות יחסי הציבור
כדי להבטיח שקוד בקשת המשיכה יישאר עקבי ויציב, אנו יוצרים זרימת עבודה ייעודית של quality.yaml . זרימת עבודה זו מבצעת מספר משימות, כגון להבטיח שלא יוצגו שינויים ידניים בגרסה (מכיוון שניהול הגרסה מנוהל על ידי 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 }}
לצד בדיקה זו, עבודת check-quality
מתקינה תלות, מאמתת אילוצים, בודקת תלות מעגלית ומאמתת את איכות הקוד הכוללת באמצעות הסקריפט שהגדרנו קודם לכן עם 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
בדיקת האיכות נועדה לפעול רק על החבילות המושפעות מבקשת המשיכה הנוכחית. סיום מוצלח של עבודות אלו מעיד על כך שבקשת המשיכה מוכנה להתמזג (בנוסף לקבלת ביקורות קוד).
אם נדרשות בדיקות נוספות עבור הפרויקט שלך, תוכל לעדכן nx.json
ואת הסקריפט האיכותי שלך כדי לשמור על זרימת העבודה ללא שינוי.
פרסם ספריות ותכונות
לאחר מיזוג PR, זרימת העבודה של השחרור (כמתואר בפרק Changesets) מופעלת. זרימת עבודה זו בונה את החבילות המושפעות ויוצרת יחסי ציבור עם בליטות הגרסה. לאחר אישור ואיחוד יחסי ציבור זה, release.yaml פועל שוב - הפעם, במקום ליצור PR, הוא מזהה שינויים בגרסה ומשחרר את החבילות המעודכנות ל-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 }}
לאחר מכן, מבוצעת עבודה בשם release-apps
, אשר אחראית על פריסות יישומים. הוא מקבל רשימה של חבילות שפורסמו מהשלב הקודם ומביא אותנו לפרק הבא.
פרסם אפליקציות
החלק האחרון של תהליך השחרור כולל פריסת היישומים שלך (אפליקציות אינן מתפרסמות ב-NPM, מכיוון שהן מוגדרות private
ב- package.json
). זרימת העבודה release-apps.yaml מופעלת אוטומטית על ידי release.yaml , או שניתן לבצעה ישירות מהכרטיסייה פעולות ב-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: "[]"
זרימת עבודה זו מקבלת קלט publishedPackages
כדי לקבוע אילו חבילות פורסמו. באמצעות אסטרטגיית מטריצה, הוא בודק כל יישום של המטריצה עבור נוכחות של תלות שפורסמה:
- 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
בדיקה זו היא תנאי אחד לתחילת פריסת אפליקציה. התנאי הנוסף מבטיח שגרסת האפליקציה שונתה (מה שמציין שיש צורך בפריסה מחדש גם אם לא עודכנו תלות):
- 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
לבסוף, לאחר אישור שלאפליקציה יש תלות מעודכנת או שהגרסה שלה השתנתה, זרימת העבודה מאחזרת את הגרסה החדשה וממשיכה לבנות ולפרוס את האפליקציה:
- 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 }}
בדוגמה זו, אנו בונים את תמונת Docker מבלי לדחוף אותה לרישום. בזרימת העבודה של הייצור, החלף שלב זה בתהליך הפריסה בפועל.
מַסְקָנָה
תקציר של שיטות עבודה מומלצות
לאורך המאמר הזה, בדקנו את ההגדרה של מונורפואי חזק ואת הכלים שעוזרים לנהל אותו ביעילות. על ידי ריכוז בסיס הקוד שלך, אתה לא רק מפשט את ניהול התלות אלא גם מייעל את שיתוף הפעולה בין צוותים. הדגמנו כיצד ניתן למנף את Yarn לשיתוף תלות, להאיץ את ההתקנה עם PnP ולשפר את עקביות הפרויקט הכוללת. בנוסף, שילוב NX לביצוע סקריפט ממוקד מבטיח שה-CI מהיר ויעיל. ערכות שינויים עזרו לאוטומציה של ניהול גרסאות, צמצום שגיאות ידניות וייעול מהדורות. לבסוף, יצרנו צינור CI/CD מוכן לייצור עם פעולות GitHub שמבצעת רק את המשימות הדרושות.
השלבים הבאים
- ניסוי והתאם : התחל בהקמת מונורפואו בקנה מידה קטן כדי לבדוק את השיטות המומלצות הללו. נסה עם מבני תיקיות שונים, והרחיב בהדרגה כדי לכלול חבילות נוספות ככל שהביטחון שלך גדל.
- שלב כלים נוספים : שקול לשלב כלים משלימים כמו PNPM או Turborepo בהתבסס על הדרישות הייחודיות של הפרויקט שלך והעדפות הצוות.
- שפר את צינורות ה-CI/CD : כוונן את זרימות העבודה של GitHub Actions שלך כדי לכלול בדיקות איכות נוספות, כיסוי קוד וסריקות אבטחה המותאמות לפרויקט שלך.
- קהילה ועדכונים : הישאר מעודכן עם המהדורות האחרונות של Yarn, NX ו-Changesets. צור קשר עם הקהילה כדי לחלוק תובנות וללמוד על מגמות מתפתחות בניהול מונורפו.
אֶמְצָעִי
גש למאגר הדוגמה המלא שנוצר עבור מדריך זה. חקור את מבנה הפרויקט, דוגמאות קוד ותסריטים המציגים את התקנת monorepo בפעולה.
בדוק את חבילת NPM בפועל שפורסמה כחלק מהפרויקט הזה. חבילות אלו מדגימות שימוש ויישום בעולם האמיתי של המושגים הנדונים במאמר.