1.3الوحدات (Modules): CommonJS (require) مقابل ES Modules
الأساسي في 30 ثانية
- الوحدة = ملف، مسؤولية واحدة: نصدّر ما نشاركه، ونستورد ما نحتاجه — أساس التنظيم.
- CommonJS (تاريخية، منتشرة): module.exports للتصدير، وrequire() للاستيراد — متزامنة، بسيطة، في كل مكان في npm.
- ES Modules (المعيار، المستقبل): export/import، تُفعَّل عبر .mjs أو "type": "module" — موصى بها لمشروع جديد، لكن يجب قراءة الاثنتين.
حالة مستمرة — ساليم يقسّم تطبيقه في الجزائر
تطبيق ساليم محشور في ملف واحد من 500 سطر: دوال الحساب مختلطة بقراءات الملفات، مستحيل الاختبار. يقسّم إلى ثلاث وحدات — حسابات، قراءة بيانات، عرض — كل واحدة تصدّر دوالها: أولًا بـ CommonJS (module.exports/require) للفهم، ثم يفعّل ES Modules ("type": "module") لمشروعه الجديد. لكل وحدة مسؤولية واضحة وتُختبَر منفصلة.
تكملة الحالة 1.1-1.2 — يهيكل ساليم كوده بعد اللغة وغير المتزامن.
1. CommonJS: module.exports وrequire()
لا يصمد برنامج جدّي في ملف واحد: نقسّم إلى وحدات (Modules) (ملفات ذات مسؤولية، تستورد بعضها). نظام Node التاريخي هو CommonJS: نصدّر مع module.exports (module.exports = { fn1, fn2 }) ونستورد مع require() (const truc = require('./monModule')). متزامن، بسيط، حاضر في ملايين حزم npm. الوحدة المحلية تبدأ بـ ./ أو ../؛ والوحدة الأصلية أو npm تُستورد باسمها (require('fs')).
ينشئ ساليم calculs.js مع module.exports = { additionner, soustraire } ويستورده بـ require('./calculs'): بدأ ملف الـ 500 سطر يتنفس.
2. ES Modules: export وimport
ES Modules هي صياغة معيار اللغة (نفسها كما في المتصفح الحديث): نصدّر مع export (export function fn() {}) ونستورد مع import (import { fn } from './autre.js'). نفعّلها بتسمية ملفاتنا .mjs أو — وهو الأشيع — بإضافة "type": "module" في package.json. ESM موصى بها لمشروع جديد (معيار، مستقبل، متسقة مع الواجهات).
يضيف ساليم "type": "module" إلى مشروعه الجديد وينتقل إلى import { additionner } from './calculs.js' — ملاحظًا الامتداد .js الإلزامي في المسار.
3. فهم الاثنتين، والتقسيم الجيد
تبقى CommonJS منتشرة (كود موجود، شروح، حزم): ستقرأون الكثير من require/module.exports حتى وأنتم تكتبون import/export. خلط الاثنتين يتطلب احتياطات (استيراد CommonJS من ESM يمر، والعكس أدقّ). وراء الصياغة، الجوهر هو المبدأ: التقسيم حسب المسؤولية، تصدير واجهة واضحة، تسمية الملفات صراحة، وتجنّب التبعيات الدائرية.
يسمّي ساليم وحداته calculs.js وdonnees.js وaffichage.js ولا يصدّر إلا الضروري: مطوّر جديد يعرف أين يبحث دون سؤاله.
🔎 مثال ملموس — 🔎 مثال عملي — التصدير والاستيراد
أولًا CommonJS، ثم ES Modules: بديلان، ونظام واحد لكل ملف.
// --- البديل A: CommonJS (ملف .js كلاسيكي) ---
// في calculs.js :
// module.exports = { additionner, soustraire };
// في app.js :
const calc = require("./calculs");
console.log(calc.additionner(2, 3));
القراءة: الوحدة المحلية تُستورد بمسارها بـ ./، والحزمة أو الأصلية باسمها.
الدرس: في ESM، يُكتب المكافئ export function additionner + import { additionner } from './calculs.js' — مع الامتداد الإلزامي.
مفردات القسم
- الوحدة (Module)
- ملف منفصل ذو مسؤولية، يصدّر ما يتيحه ويستورد ما يحتاجه؛ أساس تنظيم الكود.
مثال بسيط: وحدات ساليم calculs.js وdonnees.js وaffichage.js.
ما ليس كذلك: ملف وحيد من 500 سطر يفعل كل شيء.
رد فعل: ملف واحد = مسؤولية واضحة واحدة.
- CommonJS (CJS)
- نظام الوحدات التاريخي في Node: التصدير مع module.exports، والاستيراد مع require(). متزامن، بسيط، منتشر في منظومة npm.
مثال بسيط: الـ require('./calculs') الخاصة بالتقسيم الأول لساليم.
ما ليس كذلك: صياغة import/export معيار اللغة.
رد فعل: اقرأها بطلاقة، حتى وأنت تكتب ESM.
- ES Modules (ESM)
- نظام وحدات معيار اللغة: التصدير مع export، والاستيراد مع import. يُفعَّل عبر .mjs أو "type":"module" في package.json.
مثال بسيط: الـ import { additionner } from './calculs.js' الخاصة بمشروع ساليم الجديد.
ما ليس كذلك: require() في ملف مضبوط على ESM.
رد فعل: مشروع جديد = ESM، بامتداد صريح.
- require مقابل import
- صياغتا الاستيراد: require() (CommonJS) وimport (ESM). تتعايشان في الكود الحقيقي، ومن هنا ضرورة فهم الاثنتين.
مثال بسيط: يقرأ ساليم require في الحزم القديمة ويكتب import في مشروعه.
ما ليس كذلك: خلطهما في ملف واحد دون إعداد مناسب.
رد فعل: نظام واحد لكل ملف، يُختار عن دراية.
- مسار الوحدة
- محلي: يبدأ بـ ./ أو ../ (ملفات المشروع). الاسم وحده: وحدة أصلية (fs) أو حزمة npm من node_modules.
مثال بسيط: الـ require('./calculs') المحلية مقابل require('fs') الأصلية لدى ساليم.
ما ليس كذلك: require('calculs') دون ./ لملف من المشروع (لن يُعثَر عليه).
رد فعل: ./ للمشروع، والاسم وحده للنظام وnpm.
في التطبيق — عمليًا — تنظيم الكود في وحدات
- أنشئوا وحدة تصدّر دوالًا بـ CommonJS (module.exports = { fn1, fn2 }) واستوردوها مع require('./monModule').
- أعيدوا إنشاء الوحدة نفسها بـ ESM (export function fn1() {}) واستوردوها مع import (بامتداد .js صريح).
- فعّلوا ESM في مشروع بإضافة "type": "module" إلى package.json، ولاحظوا أن .js صارت وحدات ES.
- استوردوا وحدة أصلية (fs) ووحدة محلية (عبر ./my-module) — لاحظوا فرق المسار.
نص جاهز — اقتراح التقسيم إلى وحدات (30 ثانية)
«هذا الملف من 500 سطر وثلاث مهن: أقترح تقسيمه إلى ثلاث وحدات — حسابات، بيانات، عرض — كل واحدة بصادراتها. سنستطيع اختبار كل جزء منفصلًا وإعادة استعماله في مكان آخر.»
يُقال في مراجعة الكود؛ اعرضوا مخطط الوحدات الثلاث قبل التقسيم.
نموذج نسخ ولصق — وحدة CommonJS + استعمال
// calculs.js: نصدّر ما نشاركه
function additionner(a, b) {
return a + b;
}
function soustraire(a, b) {
return a - b;
}
module.exports = { additionner, soustraire };
// app.js: نستورد ما نحتاجه
const calc = require("./calculs");
console.log(calc.additionner(2, 3));
console.log(calc.soustraire(5, 2));
ملف وحيد للتقسيم
رصد المسؤوليات (حسابات، بيانات، عرض)، استخراج وحدة في كل مرة مع التحقق بـ node في كل خطوة — كما فعل ساليم مع أسطره الـ 500.
مشروع جديد في الجزائر
يبدأ ساليم بـ ESM ("type": "module"): نفس صياغة import/export كما في واجهة الفريق، نظام واحد يجب معرفته.
حزم قديمة بـ require
أمام توثيق بـ require: اعرفوا قراءته واستعماله كما هو، دون تحويل المشروع كله لذلك.
تسمية الفريق
اسم ملف يقول ما يفعله (donnees.js وvalidation.js): يصير الهيكل توثيقًا مجانيًا للمطور التالي.
✓ ممارسات جيدة
- وحدة واحدة = مسؤولية واحدة.
- لا تصدّروا إلا الضروري.
- نظام واحد لكل ملف (دون خلط).
✗ أخطاء شائعة
- خلط require وimport دون إعداد.
- نسيان ./ لوحدة محلية.
- نسيان الامتداد في ESM.
النقاط الأساسية
- تقسيم الكود إلى وحدات (ملفات ذات مسؤولية، تُصدَّر/تُستورَد) = أساسي للتنظيم والصيانة.
- COMMONJS (تاريخية، منتشرة): التصدير module.exports، والاستيراد require(). متزامنة، في ملايين حزم npm.
- وحدات ES (المعيار، المستقبل): التصدير export، والاستيراد import؛ تُفعَّل عبر .mjs أو "type":"module" في package.json.
- افهموا الاثنتين (require وimport تتعايشان)؛ وESM موصى بها لمشروع جديد. المبدأ يسبق الصياغة.
تحدي 48 ساعة — طبّق غدًا
استخرجوا وحدة واحدة من كودكم (النموذج أعلاه): module.exports + require('./...')، شغّلوا مع node، ثم حوّلوها إلى export/import. تكفي 20 دقيقة.
- أستطيع شرح الوحدة والتصدير والاستيراد.
- أعرف التمييز بين require (CJS) وimport (ESM).
- قسّمت وحدة واستوردتها بالطريقتين.
أسئلة شائعة
CommonJS أم ES Modules: أيها نختار لمشروعي؟
لمشروع جديد، يُوصَى بـ ESM: فهي المعيار الرسمي للغة (ليست اختراع Node)، متسقة مع الواجهة (نفس الصياغة في المتصفح)، واتجاه المستقبل. لكن CommonJS تبقى خيارًا صالحًا (بساطة، توافق مع الكود الموجود) — والأهم أنكم ستقرأون الكثير من CommonJS (حزم، شروح، كود موجود). لماذا نظامان؟ أنشأت Node CommonJS سنة 2009 لأن جافاسكريبت لم تكن تملك حينها أي نظام وحدات؛ ووحّدت اللغة وحدات ES سنة 2015 وتبنّتها Node دون كسر الموجود. ومن هنا التعايش الواجب معرفته.
كيف أنظم كودي في وحدات، وراء الصياغة؟
مهارة معمارية مستقلة عن require/import. لكل وحدة مسؤولية واضحة (مستخدمون، قاعدة بيانات، مسارات، أدوات) — لا خلط. قسّموا حسب الوظيفة، صدّروا واجهة واضحة ودنيا (العام، والباقي يبقى خاصًا)، سمّوا صراحة (userService.js وvalidation.js)، تجنّبوا التبعيات الدائرية (A يستورد B الذي يستورد A) بالتنظيم الهرمي. كما فعل ساليم: ثلاث وحدات مسماة خير من ملف من 500 سطر.
كيف تستورد وحدة محلية في CommonJS؟