Menu
العربية

ملف package.json في Node.js: شرح شامل

تعرّف على محتويات ملف package.json في مشاريع Node.js: الحقول المهمة، طريقة عمل السكربتات، ودلالات نطاقات semver التي تحدد الإصدار الذي يثبّته npm.

ملف التعريف الخاص بمشروع Node

أي مشروع Node.js لازم يحتوي في جذره على ملف package.json. هو ببساطة ملف JSON عادي يصف المشروع: اسمه، إصداره، الحزم اللي يعتمد عليها، والأوامر اللي يوفّرها. وهذا الملف هو المرجع الأساسي اللي يقرأه npm في أي عملية يقوم بها. احذفه، ولن يعرف npm أي شيء عن مشروعك.

أسرع طريقة لإنشائه هي عبر الأمر npm init:

npm init -y

خيار -y يتخطّى كل الأسئلة ويعتمد القيم الافتراضية مباشرة. النتيجة تكون ملفًا شبيهًا بهذا:

{
  "name": "my-app",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

هذا هو الهيكل الأساسي فقط. معظم هذه الحقول لا تُحدث فرقاً كبيراً بحد ذاتها، لكن أهميتها تظهر عندما تبدأ بإضافة الاعتمادات والـ scripts.

الفرق بين dependencies و devDependencies

هناك حقلان يقومان بمعظم العمل الفعلي: dependencies و devDependencies. كلاهما عبارة عن خريطة تربط أسماء الحزم بنطاقات إصداراتها.

index.js
Output
Click Run to see the output here.

هذا الفصل مهم لسبب واحد: الحزم الموجودة في dependencies هي التي يحتاجها كودك وقت التشغيل، أمّا devDependencies فهي حزم لا تحتاجها إلا أثناء التطوير — مثل أدوات الاختبار، والـ linters، وأدوات البناء، وفاحصات الأنواع. وعندما يثبّت أحدهم حزمتك كاعتمادية في مشروعه، فإن npm يُنزّل ما في dependencies فقط ويتجاهل devDependencies.

يتولى npm تحديث هذه الحقول تلقائيًا. فأمر npm install express يضيف سطرًا إلى dependencies، بينما npm install --save-dev vitest يضيف سطرًا إلى devDependencies. ونادرًا ما تعدّلها يدويًا.

نطاقات الإصدارات: ^ و ~ والإصدار الثابت

سلاسل الإصدارات من نوع ^4.19.0 ليست إصدارات محددة بدقة، بل هي نطاقات. ويعتمد npm على semver الذي يقسّم الإصدار إلى ثلاثة أجزاء: MAJOR.MINOR.PATCH:

  • MAJOR: قفزة تكسر التوافق مع الإصدارات السابقة.
  • MINOR: إضافة ميزات جديدة دون كسر أي شيء.
  • PATCH: إصلاح أخطاء فقط.

أكثر رمزين ستصادفهما باستمرار:

"express": "^4.19.0"   // >= 4.19.0 و < 5.0.0  (أي 4.x.x عند أو فوق 4.19.0)
"express": "~4.19.0"   // >= 4.19.0 و < 4.20.0 (أي 4.19.x عند أو فوق 4.19.0)
"express": "4.19.0"    // 4.19.0 بالضبط

^ هو الافتراضي اللي بيستخدمه npm لما تثبّت أي حزمة، وهو بيفترض إن تحديثات minor و patch هتفضل متوافقة. أما ~ فأكثر تحفظًا، وبيسمح بتحديثات patch فقط. ولو كتبت رقم النسخة مجردًا من غير أي رمز، فده معناه تثبيت النسخة بالظبط.

المشكلة إن "النسخة اللي ثبّتها دلوقتي" و"النسخ اللي يسمح بيها النطاق" مش نفس الحاجة. لو ثبّتّ express@4.19.0 النهارده، وجه زميلك بعد شهر يشغّل نفس المشروع، ممكن ^4.19.0 يتحوّل عنده إلى 4.19.5. هنا بييجي دور package-lock.json؛ الملف ده بيسجّل النسخ الفعلية اللي اتحلّت، عشان الكل يحصل على نفس الشجرة بالظبط. خليك حريص إنك ترفعه على Git.

سكربتات package.json: واجهة الأوامر الخاصة بمشروعك

حقل scripts هو المكان اللي بتعرّف فيه اختصارات للأوامر المتكررة. أي أمر تحطه هنا تقدر تشغّله بـ npm run <name>:

index.js
Output
Click Run to see the output here.

بعض الأشياء المهمة اللي تحتاج تعرفها عن السكربتات:

  • npm start و npm test وحفنة من الأسماء الأخرى تشتغل بدون كلمة run. أي سكربت غير هؤلاء لازم تناديه بـ npm run <name>.
  • السكربتات تنفذ في shell يكون فيه node_modules/.bin مضاف إلى PATH، ولهذا تقدر تستدعي الـ binaries الخاصة بالحزم المثبتة مباشرة. يعني "test": "vitest" راح يشتغل حتى لو vitest مو مثبّت globally.
  • تقدر تربط السكربتات ببعض: "build": "npm run lint && npm run compile". استخدم && لما تبي "نفّذ بالترتيب، ووقّف عند أول فشل".
  • سكربتات pre<name> و post<name> تشتغل تلقائياً. لو عندك prebuild، راح ينفذ قبل build بدون ما تحتاج تضبط أي شيء إضافي.

السكربتات هي الواجهة اللي يتعامل معها أي شخص يشتغل على المشروع. الـ package.json الممتاز يخلي أي مساهم جديد يقدر يعمل clone للمستودع، ثم npm install، وبعدها npm run dev أو npm test دون ما يضطر يرجع لأي wiki أو توثيق خارجي.

نقاط الدخول: main و exports و type

هذه الحقول هي اللي تخبر Node (والـ bundlers) بالطريقة الصحيحة لتحميل الحزمة الخاصة بك.

index.js
Output
Click Run to see the output here.
  • type: يحدد الطريقة التي يتم بها تفسير ملفات .js. القيمة "module" تعني أنك تستخدم نظام ESM (أي import و export)، أما إذا حذفتها أو ضبطتها على "commonjs" فستعمل بنظام CommonJS (require). للتفاصيل الكاملة، راجع صفحة المقارنة بين CommonJS و ESM.
  • main: هي نقطة الدخول القديمة — أي الملف الذي يُرجعه require("my-lib") عند استيراد المكتبة. لا تزال الأدوات القديمة تعتمد عليها.
  • exports: هي البديل الحديث والأكثر صرامة. تُعرّف بدقة أي الملفات يستطيع المستخدم استيرادها ومن أي مسار فرعي. إذا لم يكن الملف مذكورًا هنا، فسيفشل استيراده — وهذه ميزة مقصودة وليست خطأً. فأنت من يتحكم بواجهة المكتبة العامة.

إذا كنت تبني تطبيقًا فقط (ولست تنشر حزمة)، فغالبًا type هي الخاصية الوحيدة التي تهمك من بين هذه الخصائص.

مثال واقعي على package.json

بجمع كل ما سبق، هذا تقريبًا شكل ملف package.json لتطبيق Node صغير في الواقع العملي:

index.js
Output
Click Run to see the output here.

لاحظ حقل engines.node. هذا الحقل إرشادي فقط؛ إذ يُطلق npm تحذيرًا (أو خطأً عند تفعيل engine-strict) لو كانت نسخة Node عند المستخدم لا تطابق المطلوب. وهي عادة جيدة لأي حزمة تنوي نشرها.

حقول يستحسن معرفتها

هناك حقول أخرى ستصادفها كثيرًا:

  • private: true — يمنعك من نشر الحزمة على npm بالخطأ. ضعه في أي مشروع غير مخصص للنشر.
  • license — معرّف SPDX مثل "MIT" أو "ISC". مهم لأي مشروع عام.
  • repository و**bugs** و**homepage** — تظهر في صفحة الحزمة على سجل npm.
  • bin — إذا كانت حزمتك توفّر أداة سطر أوامر (CLI)، اربط هنا اسم الأمر بملف السكربت. بعد التثبيت تصبح هذه الأوامر قابلة للتشغيل مباشرة.
  • workspaces — مخصّص للـ monorepos؛ يُخبر npm أن يتعامل مع المجلدات الفرعية كحزم مرتبطة.

لست بحاجة إلى كل هذه الحقول، بل إلى ما يناسب مشروعك فقط.

أخطاء شائعة يقع فيها الكثيرون

إليك بعض المزالق التي يتعثّر فيها المطوّرون عادةً:

  • رفع مجلد node_modules إلى Git. لا تفعل ذلك. أضفه إلى .gitignore. يكفي وجود package.json مع package-lock.json ليستطيع أي شخص إعادة بناء المجلد بأمر npm install.
  • عدم رفع package-lock.json. بالعكس، ارفعه دائمًا. بدون ملف القفل ستتحوّل عبارة «يعمل على جهازي» إلى مشكلة حقيقية، لأن نطاقات semver قد تُترجَم إلى إصدارات مختلفة مع مرور الوقت.
  • وضع اعتماديات التشغيل داخل devDependencies. قد يعمل التطبيق محليًا لأن اعتماديات التطوير مثبّتة، ثم ينهار في بيئة الإنتاج حيث يتم تجاوزها. إن كان الكود الذي تنشره يستخدمها، فمكانها الطبيعي في dependencies.
  • تعديل الإصدارات يدويًا دون إعادة التثبيت. إذا غيّرت رقم إصدار في package.json، شغّل npm install بعدها، وإلا ستفقد التزامن بين node_modules وملف القفل.

الخطوة التالية: بيئة تشغيل Node

package.json يُخبر Node بماهية مشروعك، أما بيئة تشغيل Node فهي التي تقرّر كيف يعمل: حلّ المسارات (module resolution)، والوحدات المدمجة، والمتغيرات العامة، وحلقة الأحداث (event loop) في الخلفية. هذا ما سنتناوله في الصفحة التالية.

الأسئلة الشائعة

ما فائدة ملف package.json؟

هو ملف التعريف الرئيسي لأي مشروع Node.js. يحفظ اسم المشروع وإصداره، والحزم التي يعتمد عليها، والسكربتات التي يمكنك تشغيلها عبر npm run، إضافة إلى بيانات وصفية مثل نقطة الدخول ونوع الموديول. وعند تنفيذ npm install يقرأ npm هذا الملف ليحدد ما يجب تنزيله.

ما الفرق بين dependencies و devDependencies؟

dependencies هي الحزم التي يحتاجها الكود أثناء التشغيل الفعلي، مثل express أو react. أما devDependencies فهي خاصة بمرحلة التطوير والبناء فقط، كأدوات الاختبار و bundlers و linters. فإذا قام شخص آخر بتثبيت حزمتك كاعتمادية ضمن مشروعه، فإن npm يتجاهل devDependencies الخاصة بك.

ماذا يعني ^ و ~ في إصدارات package.json؟

هما معاملان لتحديد نطاق الإصدار وفق semver. ^1.2.3 يسمح بأي إصدار 1.x.x يساوي 1.2.3 أو أعلى منه (نفس الإصدار الرئيسي). أما ~1.2.3 فهو أكثر صرامة؛ يسمح بإصدارات 1.2.x بدءاً من 1.2.3 فقط (نفس الإصدار الفرعي). وكتابة 1.2.3 بدون أي رمز تثبّت الإصدار تماماً كما هو. ويقوم ملف package-lock.json بحفظ الإصدارات الفعلية المحلولة حتى تبقى عمليات التثبيت قابلة للتكرار.

كيف أنشئ ملف package.json؟

نفّذ أمر npm init داخل مجلد فارغ وأجب على الأسئلة، أو استخدم npm init -y لقبول القيم الافتراضية وإنشاء الملف فوراً. كما يمكنك كتابته يدوياً فهو مجرد JSON عادي. الحقلان الإلزاميان فعلياً هما name و version فقط.

تعلّم البرمجة مع Coddy

ابدأ الآن