الأسبوع ٢ · فصل ٢

Turborepo من جوه

create-turbo · task pipeline · local & remote caching · --filter
🎯 الهدف: تفهم ليه Turbo بيخلّي build بياخد 3 دقايق ياخد 200ms، وتتحكم في مين يتعمله build ومين لأ.

pnpm لوحده ناقصه إيه؟

pnpm بيعرف يربط الـ packages ويشغّل scripts. بس لو عملت pnpm -r build كل مرة، هيبني كل حاجة من الأول حتى لو مافيش حاجة اتغيّرت. في ريبو فيه 10 packages ده عذاب، خصوصًا في الـ CI.

Turborepo هو task runner ذكي فوق الـ package manager. بيعمل ٣ حاجات:

يفهم ترتيب المهام (Task Graph)يشغّل بالتوازي على كل الـ coresيكيّش النتايج (Caching)يفلتر يشغّل إيه (--filter)Remote Cache للفريق والـ CI

فكّ الـ Starter

npx create-turbo@latest my-turbo# اختار pnpm لما يسألكcd my-turbo && pnpm dev

هتلاقي الهيكل ده تقريبًا (ممكن يختلف شوية حسب النسخة):

my-turbo/├── apps/│   ├── web/                  # Next.js app│   └── docs/                 # Next.js app تاني├── packages/│   ├── ui/                   # React components مشتركة│   ├── eslint-config/        # ESLint config مشترك│   └── typescript-config/    # tsconfig bases├── turbo.json                # ← قلب الموضوع├── pnpm-workspace.yaml└── package.json

الـ Task Pipeline: turbo.json

turbo.json
{  "$schema": "https://turbo.build/schema.json",  "tasks": {    "build": {      "dependsOn": ["^build"],      "inputs": ["$TURBO_DEFAULT$", ".env*"],      "outputs": ["dist/**", ".next/**", "!.next/cache/**"],      "env": ["VITE_API_URL", "NEXT_PUBLIC_API_URL"]    },    "lint": {      "dependsOn": ["^lint"]    },    "typecheck": {      "dependsOn": ["^build"]    },    "test": {      "dependsOn": ["build"],      "outputs": ["coverage/**"]    },    "dev": {      "cache": false,      "persistent": true    }  }}

الـ dependsOn — أهم سطر

الشكلالمعنىمثال
"^build"شغّل build في كل الـ dependencies بتاعتي الأولweb مش هيتبني قبل ui و types
"build"شغّل build في نفس الـ package الأولtest محتاج الـ dist بتاعي
"api#build"task معينة في package معينةالـ e2e في web محتاج الـ api مبني

علامة ^ = “اللي تحتي في الشجرة”. احفظها كده.

الـ inputs — $TURBO_DEFAULT$ و.env*

الـ inputs هي الملفات اللي Turbo بيحطها في الـ hash. لو الملف اتغيّر، الكاش يبقى miss والـ task يشتغل تاني.

من غير المفتاح ده، الافتراضي هو: كل الملفات اللي داخلة Git جوه الـ package. وTurbo بيحترم .gitignore، فملف .env المتجاهل مش داخل الحساب.

عشان كده السطر ده موجود:

turbo.json
"inputs": ["$TURBO_DEFAULT$", ".env*"]
  • $TURBO_DEFAULT$: رجّع السلوك الافتراضي. مجرد ما تكتب inputs، Turbo بيسيبه. من غيره، السطر [".env*"] لوحده معناه إن الكاش يتغيّر بس لما ملف env يتغيّر، وتعديل في src يفضل cache hit.
  • .env*: زود ملفات الـ env على الافتراضي. النجمة بتمسك .env و.env.local و.env.production وأي اسم بيبدأ كده. الملفات دي عادة في .gitignore، فمن غيرها تغيير الـ API URL في الملف مش بيكسّر الكاش، وتاخد build قديم.
  • Turbo مش بيقرأ الملف: مش بيحمّل القيم جوه الـ process. Next أو Vite أو dotenv هما اللي بيعملوا كده. الـ inputs بس بتدخل محتوى الملف في الـ hash.
  • الـ env حاجة تانية: env بيحط قيمة المتغير اللي موجودة في البيئة (زي secret في الـ CI). .env* بيحط الملف اللي على الديسك. الاتنين محتاجين بعض لو الـ build بيستخدم الاتنين.
  • الاستثناء: علامة ! بتطلّع ملف من الحساب، زي "!README.md"، بس بعد $TURBO_DEFAULT$.
  • مكان الملف: الـ glob نسبته للـ package. ملف .env في جذر الريبو مش هيتحسب من السطر ده. ساعتها تحطه في globalDependencies، أو تكتب المسار بـ $TURBO_ROOT$. راجع الـ docs لو المسار مش جوه الـ package.

الـ outputs و persistent

  • outputs: الملفات اللي Turbo هيخزنها في الكاش ويرجّعها لما يحصل hit. لو نسيتها، هتلاقي “cache hit” بس فولدر dist فاضي!
  • persistent: true: للمهام اللي مابتخلصش (dev server). Turbo مش هيسمح لحد يعمل dependsOn عليها.
  • cache: false: الـ dev ملوش معنى يتكيّش.

الـ Caching: إزاي Turbo عارف إن مفيش حاجة اتغيّرت؟

قبل أي task، Turbo بيحسب hash من كل حاجة ممكن تأثر على النتيجة:

لو الـ hash ده اتشاف قبل كده → cache hit: Turbo بيرجّع الـ outputs ويعيد طباعة الـ logs كأنه شغّل فعلًا. لو كل المهام hit هتشوف رسالة 🔥 FULL TURBO.

pnpm turbo build              # أول مرة: بيبني كل حاجةpnpm turbo build              # ⁧تاني مرة: ⁦FULL TURBO⁩ في أقل من ثانية⁩pnpm turbo build --force      # تجاهل الكاشpnpm turbo build --dry=json   # ⁧وريني هيعمل إيه والـ ⁦hashes⁩ من غير ما يشغّل⁩pnpm turbo build --summarize  # ⁧تقرير بعد التشغيل: ليه ⁦hit⁩ وليه ⁦miss⁩⁩

Local Cache vs Remote Cache

💻 Local Cache
  • متخزن في .turbo/cache على جهازك.
  • بيفيدك انت بس.
  • ضيف .turbo للـ .gitignore.
☁️ Remote Cache
  • الكاش على سيرفر مشترك (Vercel أو self-hosted).
  • زميلك بنى؟ انت تاخد النتيجة جاهزة.
  • الـ CI بيبني مرة، وكل الـ PRs تستفيد.
npx turbo login     # مع Vercelnpx turbo link      # اربط الريبو بالـ team# في الـ CI بتحط:#   TURBO_TOKEN=xxx  و  TURBO_TEAM=your-team

الـ --filter: شغّل اللي يخصك بس

الـ filterهيشغّل على مين
--filter=webweb بس
--filter=web...web + كل اللي هو بيعتمد عليه (النقط بعد = تحت)
--filter=...@repo/uiui + كل اللي بيعتمد عليها (النقط قبل = فوق)
--filter=./apps/*كل الـ apps بالـ path
--filter=!docsكله ماعدا docs
--filter=[main]الـ packages اللي اتغيّرت من بعد main
--filter=...[main]اللي اتغيّر + كل اللي بيعتمد عليه ← ده اللي بيتعمل في CI
pnpm turbo dev --filter=web...          # شغّل web وكل الـ packages اللي محتاجهاpnpm turbo test --filter=...@repo/ui    # غيّرت في ui؟ اختبر كل اللي بيستخدمهاpnpm turbo build --graph                # اطلع صورة للـ task graph

🧪 Lab الأسبوع التاني

  1. اعمل create-turbo وشغّل pnpm turbo build مرتين، وقيس الوقت.
  2. غيّر ملف في apps/docs وابني تاني: لاحظ إن web و ui فضلوا hit.
  3. غيّر ملف في packages/ui: لاحظ إن الاتنين apps اتعملهم rebuild.
  4. امسح .next من outputs بتاعة الـ build وجرّب تبني: شوف المشكلة بعينك، ورجّعها.
  5. ضيف env var لـ web من غير ما تعرّفه في turbo.json، وشوف strict mode بيعمل إيه.
  6. جرّب --dry=json و --summarize واقرا الـ hash inputs.

Checklist الأسبوع التاني

🧠 اختبر نفسك
١) الـ build عمل cache hit بس dist فاضي. السبب الأغلب؟
الـ outputs مش متعرّفة صح، فـ Turbo ماخزّنش الملفات.
٢) عاوز تشغّل الـ tests لكل حاجة ممكن تتأثر بتعديلك في @repo/types. الأمر؟
turbo test --filter=...@repo/types
٣) الـ staging build بيكلّم production API. إيه اللي غالبًا نسيته؟
ماحطّيتش الـ env var في env فالكاش اتشارك بين الاتنين.
٤) ليه الـ dev task مالهاش لازمة تتكيّش؟
لأنها persistent ومش بتطلّع output نهائي؛ وظيفتها تفضل شغالة.
٥) كتبت inputs فيها .env* بس من غير $TURBO_DEFAULT$. غيّرت ملف في src، والكاش فضل hit. ليه؟
لأن كتابة inputs بتلغي الافتراضي. من غير $TURBO_DEFAULT$ Turbo بيتابع ملفات الـ env بس، ومش ملفات السورس.