{"id":"l-js-5-1","courseId":"javascript-fundamentals","lang":"ar","kind":"lesson","summary":"تقسّم برنامجًا متناميًا إلى وحدات (modules) باستخدام export وimport، وتحمّلها في المتصفّح بـ type=“module” من خادم تطوير، وتنظّم Pocket في ملفات لكل منها مهمة واضحة.","html":"<p>صار لدى Pocket دوال مساعدة للمال، وتحقّق من المدخلات، وتخزين، وعرض، ومعالجات أحداث، وكود شبكة. وفي ملف واحد يعني هذا مئات الأسطر، تستطيع فيها كل دالة رؤية كل متغيّر، وتغيير جزء واحد يعني التمرير متجاوزًا كل الأجزاء الأخرى. البرامج الحقيقية تُقسَّم إلى ملفات، لكل منها مهمة واحدة، وطريقة JavaScript في ذلك هي <strong>الوحدات</strong> (modules).</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">الوحدة ملف له نطاقه الخاص</h2>\n<p>في الوحدة، تكون المتغيّرات والدوال في المستوى الأعلى خاصة بذلك الملف. ولتشارك شيئًا، تُصدّره بـ <code dir=\"ltr\">export</code>. ولتستخدم شيئًا من وحدة أخرى، تستورده بـ <code dir=\"ltr\">import</code>:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"js\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">src/money.js</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">export</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> toCents</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">dollars</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> Math.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">round</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(dollars </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">*</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 100</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">export</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> formatMoney</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">cents</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">symbol</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> \"$\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  return</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> `${</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">symbol</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">}${</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">(</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">cents</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> /</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 100</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">).</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">toFixed</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">2</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">)</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">}`</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> SECRET_ROUNDING_NOTE</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> \"not exported, so only this file can see it\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span></code></pre></div>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"js\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">src/main.js</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { toCents, formatMoney } </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> \"./money.js\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">console.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">log</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">formatMoney</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">toCents</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">12.2</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)));</span></span></code></pre></div>\n<p>الأقواس المعقوفة تسرد <strong>التصديرات المسمّاة</strong> (named exports) التي تريدها. والمسار يبدأ بـ <code dir=\"ltr\">./</code>، ومعناه “نسبةً إلى هذا الملف”، وفي المتصفّح يتضمّن الامتداد <code dir=\"ltr\">.js</code>. أما <code dir=\"ltr\">SECRET_ROUNDING_NOTE</code> فلا يمكن استيرادها أصلًا؛ وهذا هو المقصود. كل وحدة تقرّر ما تقدّمه، وكل ما عدا ذلك مخفي، مثل الـ closures في القسم 2 تمامًا، لكن على مستوى الملف.</p>\n<p>صيغ أخرى ستراها:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"js\" data-index=\"2\"><div class=\"code-head\"><span class=\"code-title\">src/main.js</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { formatMoney </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">as</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> fmt } </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> \"./money.js\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;   </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// rename on import</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> *</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> as</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> money </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> \"./money.js\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;                </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// everything, as money.toCents etc.</span></span></code></pre></div>\n<p>وهناك أيضًا <strong>التصدير الافتراضي</strong> (default export)، واحد لكل ملف، يُستورد دون أقواس معقوفة وبأي اسم تحبّه: <code dir=\"ltr\">export default function renderApp() {}</code> ثم <code dir=\"ltr\">import renderApp from &quot;./ui.js&quot;</code>. كثير من الفرق تفضّل التصديرات المسمّاة، وكذلك هذه الدورة: فالتصدير المسمّى يحمل الاسم نفسه في كل ملف يستخدمه، لذلك يعمل البحث وإعادة التسمية التلقائية في محرّرك بشكل موثوق.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">تحميل الوحدات في المتصفّح</h2>\n<p>يُحمَّل ملف الدخول بـ <code dir=\"ltr\">type=&quot;module&quot;</code>، وهو يستورد كل ما عداه:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"html\" data-index=\"3\"><div class=\"code-head\"><span class=\"code-title\">index.html</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">script</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> type</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"module\"</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> src</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"./src/main.js\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">script</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span></code></pre></div>\n<p>تتصرّف سكربتات الوحدات بشكل مختلف قليلًا عن السكربتات الكلاسيكية التي استخدمتها حتى الآن. فهي مؤجَّلة تلقائيًا، فيكون الـ DOM جاهزًا حين تُنفَّذ. وتعمل في الوضع الصارم (strict mode)، الذي يحوّل بعض الأخطاء الصامتة إلى أخطاء ظاهرة. وكل وحدة تُنفَّذ <strong>مرة واحدة</strong>، مهما كان عدد الملفات التي تستوردها، لذلك يكون المتغيّر على مستوى الوحدة مشتركًا بين كل من يستوردها.</p>\n<aside class=\"callout callout-mistake\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\"/><path d=\"M12 9v4\"/><path d=\"M12 17h.01\"/></svg><span><span class=\"callout-kind\">خطأ شائع</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>فتح الصفحة من نظام الملفات</span></p>\n<p>النقر المزدوج على <code dir=\"ltr\">index.html</code> يفتحه من عنوان <code dir=\"ltr\">file://</code>، والمتصفّحات ترفض تحميل سكربتات الوحدات من هناك: فتحصل على خطأ CORS في الـ console. قدّم المجلد عبر http بدلًا من ذلك. والطريقة المعتادة خادم تطوير: أنشئ مشروعًا بـ <code dir=\"ltr\">npm create vite@latest</code> وشغّل <code dir=\"ltr\">npm run dev</code>، أو شغّل <code dir=\"ltr\">npx serve</code> داخل المجلد.</p>\n</aside>\n<p>عمليًا، تستخدم معظم المشاريع أداة بناء مثل Vite. إنها تقدّم وحداتك أثناء التطوير، وتتيح لك استيراد الحزم من npm بأسمائها (<code dir=\"ltr\">import { something } from &quot;some-package&quot;</code>)، وعند البناء تحزم كل شيء في ملفات قليلة محسّنة. وصياغة <code dir=\"ltr\">import</code> و<code dir=\"ltr\">export</code> التي تكتبها هي نفسها تمامًا.</p>\n<h2 id=\"sec-3\" tabindex=\"-1\">الوحدات في Node.js</h2>\n<p>يدعم Node.js الصياغة نفسها. يُعامَل الملف على أنه وحدة ES حين يحتوي أقرب <code dir=\"ltr\">package.json</code> على <code dir=\"ltr\">&quot;type&quot;: &quot;module&quot;</code>، أو حين ينتهي اسم الملف بـ <code dir=\"ltr\">.mjs</code>. (والإصدارات الحديثة من Node.js تكتشف أيضًا صياغة <code dir=\"ltr\">import</code> في ملف <code dir=\"ltr\">.js</code> عادي، لكن ضبط <code dir=\"ltr\">&quot;type&quot;</code> صراحةً أوضح.) وستصادف أيضًا نظام Node.js الأقدم، CommonJS، الذي يستخدم <code dir=\"ltr\">require()</code> و<code dir=\"ltr\">module.exports</code>؛ وما زال شائعًا في المشاريع القائمة، لكن الكود الجديد يستخدم <code dir=\"ltr\">import</code> و<code dir=\"ltr\">export</code>.</p>\n<h2 id=\"sec-4\" tabindex=\"-1\">تنظيم Pocket</h2>\n<p>التقسيم الجيد يتبع المهام، ويُبقي الأجزاء التي تتعامل مع العالم الخارجي منفصلة عن المنطق النقي:</p>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 680 250\" role=\"img\" aria-labelledby=\"l-js-5-1-t1\"><defs><marker id=\"arrow-l-js-5-1-1\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\"><path d=\"M0 0 L10 5 L0 10 z\" class=\"d-arrowhead\"/></marker></defs>\n  <title id=\"l-js-5-1-t1\">يستورد main.js من ui.js وstorage.js وexpenses.js. ويستورد ui.js من money.js. أما expenses.js وmoney.js فمنطق نقي لا يمسّ الصفحة ولا التخزين أبدًا.</title>\n  <rect class=\"d-box-primary\" x=\"270\" y=\"16\" width=\"140\" height=\"48\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"340\" y=\"46\" text-anchor=\"middle\">main.js</text>\n  <rect class=\"d-box-accent\" x=\"40\" y=\"110\" width=\"150\" height=\"48\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"115\" y=\"140\" text-anchor=\"middle\">ui.js</text>\n  <rect class=\"d-box-accent\" x=\"265\" y=\"110\" width=\"150\" height=\"48\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"340\" y=\"140\" text-anchor=\"middle\">storage.js</text>\n  <rect class=\"d-box-success\" x=\"490\" y=\"110\" width=\"150\" height=\"48\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"565\" y=\"140\" text-anchor=\"middle\">expenses.js</text>\n  <rect class=\"d-box-success\" x=\"40\" y=\"196\" width=\"150\" height=\"48\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"115\" y=\"226\" text-anchor=\"middle\">money.js</text>\n  <path class=\"d-arrow\" d=\"M300 64 L150 106\" marker-end=\"url(#arrow-l-js-5-1-1)\"/>\n  <path class=\"d-arrow\" d=\"M340 64 L340 106\" marker-end=\"url(#arrow-l-js-5-1-1)\"/>\n  <path class=\"d-arrow\" d=\"M380 64 L530 106\" marker-end=\"url(#arrow-l-js-5-1-1)\"/>\n  <path class=\"d-arrow\" d=\"M115 158 L115 192\" marker-end=\"url(#arrow-l-js-5-1-1)\"/>\n  <text class=\"d-label-muted\" x=\"420\" y=\"214\" text-anchor=\"middle\">الأخضر: منطق نقي، بلا DOM ولا تخزين</text>\n</svg><figcaption>وحدات Pocket ومن يستورد ماذا</figcaption></figure>\n<ul>\n<li><code dir=\"ltr\">money.js</code>: <code dir=\"ltr\">toCents</code> و<code dir=\"ltr\">formatMoney</code>. نقية.</li>\n<li><code dir=\"ltr\">expenses.js</code>: <code dir=\"ltr\">addExpense</code> و<code dir=\"ltr\">removeExpense</code> و<code dir=\"ltr\">totalCents</code> و<code dir=\"ltr\">totalsByCategory</code> و<code dir=\"ltr\">validateExpense</code>. نقية: بيانات تدخل، وبيانات تخرج.</li>\n<li><code dir=\"ltr\">storage.js</code>: <code dir=\"ltr\">loadExpenses</code> و<code dir=\"ltr\">saveExpenses</code>. الملف الوحيد الذي يعرف بوجود <code dir=\"ltr\">localStorage</code>.</li>\n<li><code dir=\"ltr\">ui.js</code>: <code dir=\"ltr\">renderExpenses</code> وغيرها من كود الـ DOM.</li>\n<li><code dir=\"ltr\">main.js</code>: نقطة الدخول. تحمّل البيانات، وتربط مستمعي الأحداث، وتستدعي البقية.</li>\n</ul>\n<p>والقاعدة وراء هذا التقسيم: الاعتماديات تتّجه من الخارج إلى الداخل. <code dir=\"ltr\">main.js</code> يعرف كل شيء؛ و<code dir=\"ltr\">expenses.js</code> لا يعرف شيئًا سوى البيانات. وهذا يعني أنك تستطيع اختبار المنطق الأساسي، أو إعادة استخدامه في Node.js، دون متصفّح، وتستطيع لاحقًا استبدال <code dir=\"ltr\">localStorage</code> بخادم بتغيير ملف واحد.</p>\n<aside class=\"callout callout-tip\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5\"/><path d=\"M9 18h6\"/><path d=\"M10 22h4\"/></svg><span><span class=\"callout-kind\">نصيحة</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>انتبه للاستيراد الدائري</span></p>\n<p>إن استورد <code dir=\"ltr\">a.js</code> من <code dir=\"ltr\">b.js</code> واستورد <code dir=\"ltr\">b.js</code> من <code dir=\"ltr\">a.js</code>، فقد يُنفَّذ أحدهما قبل أن ينتهي الآخر من تعريف تصديراته، فتحصل على <code dir=\"ltr\">undefined</code> محيّرة أو على أخطاء “cannot access before initialization”. وحين يحدث ذلك، انقل الكود المشترك إلى وحدة ثالثة يستورد منها الاثنان.</p>\n</aside>\n<p>بيئة التجربة في هذه الدورة تشغّل ملفًا واحدًا في كل مرة، لذلك يدور التمرين أدناه حول قراءة كود الوحدات. وفي الدرس التالي ستتعرّف إلى طريقة أخرى لتنظيم الكود، هي الأصناف (classes)، التي تجمع البيانات مع الدوال التي تعمل عليها.</p>\n","toc":[{"id":"sec-1","text":"الوحدة ملف له نطاقه الخاص","level":2},{"id":"sec-2","text":"تحميل الوحدات في المتصفّح","level":2},{"id":"sec-3","text":"الوحدات في Node.js","level":2},{"id":"sec-4","text":"تنظيم Pocket","level":2}],"takeaways":["الوحدة (module) ملف له نطاقه الخاص؛ ولا شيء فيه مرئي للملفات الأخرى ما لم يُصدَّر.","فضّل التصدير المسمّى (<code dir=\"ltr\">export function formatMoney</code>)، فهو يحتفظ بالاسم نفسه في كل مكان ويسهل البحث عنه.","في المتصفّح، حمّل ملف الدخول بـ <code dir=\"ltr\">&lt;script type=&quot;module&quot;&gt;</code>، واستخدم مسارات نسبية مع الامتداد <code dir=\"ltr\">.js</code>، وقدّم الملفات عبر http.","في Node.js، تكون الملفات وحدات ES حين يحتوي أقرب <code dir=\"ltr\">package.json</code> على <code dir=\"ltr\">&quot;type&quot;: &quot;module&quot;</code> أو حين ينتهي اسم الملف بـ <code dir=\"ltr\">.mjs</code>.","نظّم حسب المهمة: المنطق النقي في وحدات لا تمسّ الصفحة أبدًا، وملف دخول رفيع يربط المنطق والتخزين والـ DOM معًا."],"further":[{"title":"JavaScript modules (MDN)","url":"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules"},{"title":"export (MDN)","url":"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export"},{"title":"Modules: ECMAScript modules (Node.js)","url":"https://nodejs.org/api/esm.html"}],"quiz":[{"q":"<p>يقول console المتصفّح: <code dir=\"ltr\">Uncaught SyntaxError: Cannot use import statement outside a module</code>. ما الحل؟</p>","options":[{"text":"غيّر اسم الملف من <code dir=\"ltr\">.js</code> إلى <code dir=\"ltr\">.mjs</code>.","why":"المتصفّح لا يقرّر بحسب امتداد الملف. بل بحسب الطريقة التي يحمّل بها وسم السكربت الملف."},{"text":"انقل أسطر <code dir=\"ltr\">import</code> إلى أسفل الملف.","why":"الاستيراد مسموح فقط داخل الوحدات، أينما ظهر. ونقله لا يجعل الملف وحدة."},{"text":"غلّف تعليمات الاستيراد بدالة.","why":"تعليمات <code dir=\"ltr\">import</code> الثابتة يجب أن تكون في المستوى الأعلى من الوحدة؛ وداخل دالة تكون خطأً في الصياغة."},{"text":"حمّل ملف الدخول بـ <code dir=\"ltr\">&lt;script type=&quot;module&quot; src=&quot;./main.js&quot;&gt;</code>.","why":"صحيح. دون <code dir=\"ltr\">type=&quot;module&quot;</code>، يشغّل المتصفّح الملف على أنه سكربت كلاسيكي، حيث <code dir=\"ltr\">import</code> غير مسموحة."}],"answer":3},{"q":"<p>يصرّح <code dir=\"ltr\">money.js</code> بـ <code dir=\"ltr\">function toCents(d) { … }</code> دون <code dir=\"ltr\">export</code>. ماذا يحدث حين ينفّذ <code dir=\"ltr\">main.js</code> السطر <code dir=\"ltr\">import { toCents } from &quot;./money.js&quot;;</code>؟</p>","options":[{"text":"يعمل، لأن كل الدوال في المستوى الأعلى مشتركة بين الوحدات.","why":"لكل وحدة نطاقها الخاص. ولا يمكن استيراد إلا الأسماء المُصدَّرة."},{"text":"يفشل التحميل بخطأ SyntaxError يقول إن <code dir=\"ltr\">money.js</code> لا يوفّر تصديرًا اسمه <code dir=\"ltr\">toCents</code>.","why":"صحيح. تُفحص تعليمات الاستيراد قبل تنفيذ أي كود، لذلك يُبلَّغ عن الخطأ فورًا."},{"text":"يُستورد <code dir=\"ltr\">toCents</code> على أنه <code dir=\"ltr\">undefined</code> ويظهر الخطأ حين تستدعيه.","why":"الاستيرادات المسمّاة تُربط قبل تنفيذ الكود؛ والتصدير المفقود يمنع شبكة الوحدات من التحميل أصلًا."}],"answer":1},{"q":"<p>تفتح <code dir=\"ltr\">index.html</code> بالنقر المزدوج عليه، فيفشل سكربت الوحدة في التحميل بخطأ CORS. لماذا؟</p>","options":[{"text":"سكربتات الوحدات ممنوعة في صفحات <code dir=\"ltr\">file://</code>، لذلك يجب تقديم الملفات عبر http، مثلًا عبر خادم تطوير.","why":"صحيح. تطبّق المتصفّحات قواعد تحميل أكثر صرامة على الوحدات. وأي خادم محلي، مثل الذي يشغّله Vite، يحلّ المشكلة."},{"text":"لا تعمل الوحدات إلا بعد نشر الموقع على الإنترنت.","why":"خادم محلي على حاسوبك يكفي. لا حاجة إلى النشر."},{"text":"المتصفّح أقدم من أن يدعم الوحدات.","why":"كل المتصفّحات الحالية تدعم الوحدات منذ سنوات. المشكلة في المصدر <code dir=\"ltr\">file://</code>، لا في المتصفّح."}],"answer":0}],"exercise":{"kind":"spot-bug","language":"js","code":"// src/money.js\nexport function formatMoney(cents) {\n  return `$${(cents / 100).toFixed(2)}`;\n}\nfunction toCents(dollars) {\n  return Math.round(dollars * 100);\n}\n\n// src/main.js  (loaded with <script type=\"module\" src=\"./src/main.js\">)\nimport { formatMoney, toCents } from \"./money\";\nconst amount = toCents(12.2);\nconsole.log(formatMoney(amount));","bugLines":[5,10],"title":"لماذا لا تُحمَّل وحدات Pocket؟","prompt":"<p>قُسّم Pocket إلى وحدتين، والآن لا تعرض الصفحة شيئًا ويُبلغ الـ console عن أخطاء. ملف HTML يحمّل <code dir=\"ltr\">src/main.js</code> بشكل صحيح بـ <code dir=\"ltr\">type=&quot;module&quot;</code> من خادم ملفات ثابتة بسيط (دون أداة بناء مثل Vite). اختر <strong>السطرين</strong> اللذين يمنعان هاتين الوحدتين من العمل في المتصفّح.</p>\n","hints":["أحد الملفين يستخدم دالة من الآخر. هل هي معروضة فعلًا؟","انظر جيدًا إلى مسار الاستيراد. في المتصفّح، ودون أداة بناء، يجب أن يشير المسار إلى الملف بالضبط."],"explanation":"<p><strong>السطر 5:</strong> <code dir=\"ltr\">toCents</code> غير مُصدَّرة، لذلك هي خاصة بـ <code dir=\"ltr\">money.js</code>. يفشل الاستيراد قبل تنفيذ أي كود، بخطأ مثل <code dir=\"ltr\">The requested module './money.js' does not provide an export named 'toCents'</code>. الإصلاح: <code dir=\"ltr\">export function toCents(dollars) {</code>.</p>\n<p><strong>السطر 10:</strong> يطلب المتصفّح الرابط الذي تكتبه بالضبط، و<code dir=\"ltr\">./money</code> ليس ملفًا (الملف هو <code dir=\"ltr\">money.js</code>)، فيُرجع الطلب 404. الإصلاح: <code dir=\"ltr\">import { formatMoney, toCents } from &quot;./money.js&quot;;</code>. أدوات البناء مثل Vite تسمح لك بحذف الامتداد، لكن وحدات المتصفّح العادية لا تسمح بذلك.</p>\n","checkLabels":{}},"words":635,"readMinutes":4}