بعدها · فصل ٥

Nx: الأخ الكبير

Project Graph · Affected · Generators · Module Boundaries
🎯 الهدف: تفهم Nx بيقدّم إيه زيادة عن Turbo، وإمتى يستاهل التعقيد الإضافي.

الفرق في الفلسفة

⚡ Turborepo

“أنا task runner سريع جدًا. اعمل الـ monorepo بتاعك بالطريقة اللي تعجبك، وأنا أشغّله وأكيّشه.”

أداة صغيرة بتعمل حاجة واحدة كويس.

🏗️ Nx

“أنا build system + منصة. هفهم الكود بتاعك، أولّدلك projects، أحمي الحدود بينهم، وأوزّع المهام على machines.”

أداة كبيرة بتمسك الريبو كله.

Project Graph

Nx بيحلل الـ imports في كودك نفسه (مش بس package.json) ويبني خريطة لمين بيعتمد على مين. وبيعرضهالك تفاعلي في المتصفح:

npx nx@latest init               # ضيف Nx لريبو موجود (حتى لو فيه Turbo/pnpm)npx nx graph                     # افتح الخريطةnpx nx affected -t lint test build   # زي --affected في turbonpx nx show project web          # كل الـ targets المستنتجة للـ project

من ميزاته المهمة: Inferred tasks — لو لقى vite.config.ts بيستنتج إن فيه build و serve و test لوحده من غير ما تكتبهم.

Generators

بدل ما تنسخ فولدر وتعدّل 7 ملفات كل ما تعمل package جديدة، Nx عنده generators بتعمل الـ scaffold كامل ومتوصّل:

npx nx add @nx/reactnpx nx g @nx/react:library packages/jobs-feature --bundler=nonenpx nx g @nx/react:component packages/ui/src/Badgenpx nx g @nx/nest:application apps/api

Module Boundaries: أقوى ميزة

في ريبو كبير، أكيد هييجي يوم حد يعمل import من apps/api جوه packages/ui، أو feature تـ import من جوه feature تانية. Nx بيمنع ده بـ tags + ESLint rule:

packages/ui/project.json
{  "name": "ui",  "tags": ["scope:shared", "type:ui"]}
eslint.config.js (root)
import nx from "@nx/eslint-plugin";export default [  ...nx.configs["flat/base"],  {    files: ["**/*.ts", "**/*.tsx"],    rules: {      "@nx/enforce-module-boundaries": ["error", {        depConstraints: [          { sourceTag: "type:ui",      onlyDependOnLibsWithTags: ["type:ui", "type:util"] },          { sourceTag: "type:feature", onlyDependOnLibsWithTags: ["type:ui", "type:util", "type:data"] },          { sourceTag: "scope:web",    onlyDependOnLibsWithTags: ["scope:web", "scope:shared"] },          { sourceTag: "scope:api",    onlyDependOnLibsWithTags: ["scope:api", "scope:shared"] },        ],      }],    },  },];

دلوقتي لو ui حاول يـ import من feature، الـ lint هيضرب في الـ PR. ده بيترجم الـ clean architecture من “اتفاق شفهي” لـ “قاعدة بتتنفّذ”.

المقارنة الصريحة

الجانبTurborepoNx
منحنى التعلمساعاتأيام لأسابيع
Configturbo.json واحدnx.json + project.json أو inferred
Caching محلي/remote✅ / Vercel أو self-host✅ / Nx Cloud أو self-host
Affected✅ --affected✅ nx affected (أنضج)
Project graphمن package.jsonمن الـ imports + UI تفاعلي
Generatorsبسيطة (turbo gen)قوية جدًا + plugins
Module boundariesمحدود/لسه بيتطور✅ ناضج
توزيع المهام على machines❌✅ (Nx Agents)
الأنسب لـفريق صغير/متوسط، 2–15 packageفرق كبيرة، عشرات الـ projects، قواعد معمارية صارمة

🧪 Lab

  1. خد الريبو بتاع الأسبوع التالت واعمل branch جديد.
  2. npx nx@latest init وشغّل nx graph — قارنها باللي في دماغك.
  3. ضيف tags لكل project واعمل rule تمنع ui من types… ثم اكسرها بقصد.
  4. اعمل library جديدة بـ generator وشوف الملفات اللي اتعملت.
  5. اكتب لنفسك صفحة: هتختار إيه لمشروعك الجاي وليه.
🧠 اختبر نفسك
١) إيه الفرق الجوهري في إزاي الاتنين بيبنوا الـ graph؟
Turbo من الـ dependencies في package.json؛ Nx كمان بيحلل الـ imports في الكود.
٢) عاوز تمنع أي feature تـ import من feature تانية. الأداة؟
Nx tags + @nx/enforce-module-boundaries.
٣) فريق 4 أفراد، 3 apps و 4 packages. ترشّح إيه؟
Turbo غالبًا؛ التعقيد الإضافي لـ Nx مش هيرجع بفايدة لسه.