pnpm لوحده ناقصه إيه؟
pnpm بيعرف يربط الـ packages ويشغّل scripts. بس لو عملت pnpm -r build كل مرة، هيبني كل حاجة من الأول حتى لو مافيش حاجة اتغيّرت. في ريبو فيه 10 packages ده عذاب، خصوصًا في الـ CI.
Turborepo هو task runner ذكي فوق الـ package manager. بيعمل ٣ حاجات:
فكّ الـ 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
{ "$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 المتجاهل مش داخل الحساب.
عشان كده السطر ده موجود:
"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
- متخزن في
.turbo/cacheعلى جهازك. - بيفيدك انت بس.
- ضيف
.turboللـ.gitignore.
- الكاش على سيرفر مشترك (Vercel أو self-hosted).
- زميلك بنى؟ انت تاخد النتيجة جاهزة.
- الـ CI بيبني مرة، وكل الـ PRs تستفيد.
npx turbo login # مع Vercelnpx turbo link # اربط الريبو بالـ team# في الـ CI بتحط:# TURBO_TOKEN=xxx و TURBO_TEAM=your-team
الـ --filter: شغّل اللي يخصك بس
| الـ filter | هيشغّل على مين |
|---|---|
--filter=web | web بس |
--filter=web... | web + كل اللي هو بيعتمد عليه (النقط بعد = تحت) |
--filter=...@repo/ui | ui + كل اللي بيعتمد عليها (النقط قبل = فوق) |
--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 الأسبوع التاني
- اعمل
create-turboوشغّلpnpm turbo buildمرتين، وقيس الوقت. - غيّر ملف في
apps/docsوابني تاني: لاحظ إنwebوuiفضلوا hit. - غيّر ملف في
packages/ui: لاحظ إن الاتنين apps اتعملهم rebuild. - امسح
.nextمنoutputsبتاعة الـ build وجرّب تبني: شوف المشكلة بعينك، ورجّعها. - ضيف env var لـ
webمن غير ما تعرّفه فيturbo.json، وشوف strict mode بيعمل إيه. - جرّب
--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 فالكاش اتشارك بين الاتنين.٤) ليه الـ dev task مالهاش لازمة تتكيّش؟
٥) كتبت inputs فيها .env* بس من غير $TURBO_DEFAULT$. غيّرت ملف في src، والكاش فضل hit. ليه؟
inputs بتلغي الافتراضي. من غير $TURBO_DEFAULT$ Turbo بيتابع ملفات الـ env بس، ومش ملفات السورس.