مستودع واحد (Monorepo) مع JavaScript: تنظيم الكود عملياً
في تطوير الويب الحديث باستخدام JavaScript (Node.js)، يبرز سؤال متزايد: كيف ننظم الكود بفعالية عندما يكون لدينا عدة حزم أو مكتبات أو خدمات مصغرة تعتمد على بعضها البعض؟ النهج التقليدي باستخدام مستودعات منفصلة (multirepo) قد يؤدي إلى تكرار الكود، وصعوبات في مزامنة الإصدارات، وطول مدة CI/CD. الحل هو المستودع الواحد (monorepo). في هذه المقالة، سنشرح ما هو المستودع الواحد، ولماذا هو ضروري، وكيفية تنظيمه عملياً باستخدام JavaScript.
ما هو المستودع الواحد (Monorepo) ولماذا هو ضروري؟
المستودع الواحد هو نهج يتم فيه تخزين كود عدة مشاريع (حزم، مكتبات، خدمات) في مستودع واحد. هذا ليس مجرد "وضع كل شيء في مجلد واحد"، بل هو بنية مدروسة تحل العديد من المشكلات:
- إصدار موحد للتبعيات: تتجنب الموقف الذي تستخدم فيه حزم مختلفة إصدارات مختلفة لنفس المكتبة.
- إعادة هيكلة سهلة: التغييرات في المكتبة المشتركة تظهر فوراً في جميع المشاريع التي تستخدمها.
- CI/CD مبسط: مستودع واحد يعني تكوين واحد للبناء والاختبار.
- مشاركة الكود: الأدوات والأنواع والتكوينات متاحة لجميع الحزم دون نشرها في npm.
ومع ذلك، فإن المستودع الواحد لا يناسب الجميع. فهو يتطلب انضباطاً في الفريق وأدوات جيدة. بالنسبة لـ JavaScript (Node.js)، الأدوات الرئيسية هي npm workspaces (مدمجة في npm 7+) و Lerna (أداة أكثر تقدماً).
إعداد مستودع واحد أساسي باستخدام npm workspaces
لنبدأ بمثال بسيط. لنفترض أن لدينا مشروعاً يتكون من حزمتين: @myapp/core (النواة بمنطق الأعمال) و @myapp/server (الخادم على Express).
لننشئ هيكل المجلدات:
my-monorepo/├── packages/│ ├── core/│ │ ├── src/│ │ │ └── index.js│ │ ├── package.json│ │ └── ...│ └── server/│ ├── src/│ │ └── index.js│ ├── package.json│ └── ...├── package.json└── ...يجب أن يحتوي package.json الجذري على حقل workspaces:
{ "name": "my-monorepo", "private": true, "workspaces": [ "packages/*" ], "scripts": { "start": "node packages/server/src/index.js", "test": "npm run test --workspaces" }}الآن في packages/core/package.json:
{ "name": "@myapp/core", "version": "1.0.0", "main": "src/index.js"}وفي packages/server/package.json نضيف تبعية إلى core:
{ "name": "@myapp/server", "version": "1.0.0", "dependencies": { "@myapp/core": "*", "express": "^4.18.0" }}بعد تنفيذ npm install في الجذر، سيقوم npm بإنشاء روابط رمزية بين الحزم. الآن في كود الخادم، يمكننا استيراد core مباشرة:
// packages/server/src/index.jsconst express = require('express');const { calculate } = require('@myapp/core');
const app = express();app.get('/', (req, res) => { res.send(calculate(2, 3));});app.listen(3000);هذا مثال أساسي، لكنه يظهر بالفعل الميزة الرئيسية: التغييرات في @myapp/core تظهر فوراً في @myapp/server دون نشر في السجل.
تنظيم متقدم باستخدام Lerna
عندما يصبح عدد الحزم أكثر من 5-10، قد لا تكون npm workspaces كافية. هنا تأتي Lerna للمساعدة - وهي أداة تضيف إمكانيات قوية:
- تشغيل الأوامر في جميع الحزم أو حزم محددة.
- إدارة الإصدارات تلقائياً (وضع ثابت أو مستقل).
- نشر الحزم إلى npm بشكل انتقائي.
لتثبيت Lerna، استخدم الأمر:
npm install -g lernaثم قم بتهيئته في مشروعك:
lerna initسينشئ هذا ملف lerna.json في الجذر. مثال على التكوين:
{ "version": "independent", "npmClient": "npm", "packages": ["packages/*"]}الآن يمكنك استخدام أوامر مثل lerna bootstrap (لربط الحزم) و lerna run test (لتشغيل الاختبارات في جميع الحزم).
الخلاصة
المستودع الواحد (Monorepo) هو أداة قوية لتنظيم مشاريع JavaScript الكبيرة. باستخدام npm workspaces أو Lerna، يمكنك تبسيط إدارة التبعيات، وتسريع التطوير، وتحسين التعاون في الفريق. ابدأ بمشاريع صغيرة، واختر الأداة التي تناسب احتياجاتك، ولا تنسَ الحفاظ على انضباط الكود.