المشكلة: كل شيء يُحمَّل دفعة واحدة
في درس الوحدات تعلّمت import { x } from "./file.js" — استيراد ساكن (static): يُحلَّل ويُحمَّل مع بداية تشغيل الصفحة، حتى لو لم يستخدمه المستخدم أبدًا. لمكتبة كبيرة (محرّر نصوص، رسم بياني) تُستخدم فقط عند نقرة زر معيّن، هذا هدر واضح لوقت التحميل الأول.
الاستيراد الديناميكي import() 🌟
بعكس import الساكنة، import() دالة تقدر تستدعيها في أي مكان بالكود — داخل شرط، دالة، أو معالج حدث — وتُرجع Promise يتحلّل لكائن الوحدة (module namespace object):
// static (أعلى الملف دائمًا)
// import { formatDate } from "./date-utils.js";
// dynamic (أي مكان، وقت الحاجة فقط)
button.addEventListener("click", async () => {
const module = await import("./heavy-chart-library.js");
module.renderChart(data);
});
أو بالأسلوب الكلاسيكي .then() بدل await:
import("./date-utils.js").then((module) => {
console.log(module.formatDate(new Date()));
});
لماذا هذا مهم؟
- تقسيم الكود (Code Splitting): حزمة (bundle) المستخدم الأولية أصغر وتُحمَّل أسرع، لأن المكتبات الثقيلة تُجلَب لاحقًا فقط.
- تحميل شرطي: حمّل كودًا فقط إن تحقّق شرط (لغة معيّنة، صلاحية مستخدم، حجم شاشة).
- يعمل خارج الوحدات أيضًا: بعكس
importالساكنة التي تتطلّبtype="module"، الديناميكية تعمل بأي سياق — حتى بـ script عادي.
Top-Level Await 🌟
في درس البرمجة غير المتزامنة تعلّمت أن await يعمل فقط داخل دالة async. Top-Level Await استثناء واحد: يسمح باستخدام await مباشرة في أعلى ملف وحدة (module)، بدون تغليفه بدالة async:
// config.js — وحدة ES module (type="module")
const response = await fetch("/api/config.json");
const config = await response.json();
export default config;
// app.js
import config from "./config.js"; // ينتظر config.js حتى يكتمل تحميله فعليًا
console.log(config.apiUrl); // القيمة جاهزة، لا Promise
القيود المهمة
- يعمل فقط داخل وحدات ES modules (
<script type="module">، أو ملفات.mjs، أو حزم أُعِدّت كـ ESM). استخدامه بملف script عادي (typeغير module) خطأ صياغة. - الوحدة التي تستخدم top-level await تُعلَّق تنفيذها حتى ينتهي الانتظار — لكن هذا لا يُجمِّد تحميل وحدات أخرى شقيقة تعمل بالتوازي.
- أي وحدة تستورد وحدة تستخدم top-level await ستنتظر ضمنيًا حتى تكتمل، تمامًا كأنها تستورد وحدة عادية جاهزة.
متى تستخدم كل واحدة؟
| الأداة | الاستخدام النموذجي |
|---|---|
import() الديناميكي | تحميل مكتبة/مكوّن ثقيل عند حدث معيّن (نقرة، مسار صفحة) |
| Top-Level Await | تهيئة وحدة تعتمد على بيانات غير متزامنة (إعدادات، اتصال قاعدة بيانات) قبل تصديرها |
غالبًا يجتمعان: const module = await import("./x.js") داخل وحدة تستخدم top-level await نفسها.
أخطاء شائعة
- ❌ توقّع أن
import()تُرجع الوحدة مباشرة → تُرجع Promise دائمًا؛ تحتاجawaitأو.then(). - ❌ استخدام top-level await بسكربت عادي بلا
type="module"→ خطأ صياغة فورًا؛ الميزة حصرية للوحدات. - ❌ الإفراط باستخدام
import()لكل شيء حتى لو كان الملف صغيرًا يُستخدم فورًا — لا فائدة حقيقية من التقسيم هنا، فقط تعقيد إضافي بلا داعٍ. - ❌ نسيان أن top-level await بوحدة واحدة قد يؤخّر كل وحدة تعتمد عليها — استخدمه فقط عند الحاجة الفعلية لبيانات قبل التصدير.
خلاصة
import() الديناميكي يحوّل الاستيراد لدالة تُرجع Promise، فتقدر تحمّل كودًا وقت الحاجة فقط — أساس تقسيم الكود بأي تطبيق حديث. Top-Level Await يسمح بـ await مباشرة داخل وحدة بلا دالة async محيطة، مفيد لتهيئة وحدة تعتمد على بيانات غير متزامنة. كلاهما حصريان للوحدات (ES modules).
🎯 التالي: الخلاصة الشاملة لمسار JavaScript وخطواتك بعده.