إذا كنت قد تلاعبت بأدوات JavaScript مؤخرًا، فمن المحتمل أنك واجهت ESM (وحدات ECMAScript) - الطريقة الأصلية القياسية للمتصفح لتقسيم التعليمات البرمجية إلى ملفات قابلة لإعادة الاستخدام، بدلاً من التحسس باستدعاءات CommonJS require() القديمة. ولكن هذا هو الأمر: إذا كنت موردًا يركز على اللودر مثلنا، فأنت تعلم بالفعل أنه ليس كل بيئة تعمل بشكل جيد مع ESM خارج الصندوق. وهنا يأتي دور babel-esm-loader، واليوم أقوم بتفصيل كيفية عمله بالضبط، دون السماح بالمصطلحات المزعجة.
أولاً، دعنا نوضح شيئًا واحدًا: نحن لا نتحدث عن آلات البناء هنا، ولكن إذا كنت ترغب في رفع مستوى فهم فريقك للرافعات في صناعة مختلفة تمامًا، فاطلع على اللوادر انزلاقية التوجيه - تلك الآلات العاملة المدمجة التي تحفر وترفع وتدخل في مواقع العمل الضيقة. تعمل أدوات التحميل الخاصة بنا بشكل مختلف قليلاً، ولكن كلاهما يتلخص في نفس الفكرة الأساسية: فهي تأخذ المدخلات الأولية وتحولها إلى شيء يمكن لبيئتك تشغيله فعليًا.
الآن، نعود إلى babel-esm-loader. لنبدأ بالسياق. Babel هي الأداة التي يستخدمها مطورو JavaScript لإعادة كتابة بناء الجملة الأحدث (فكر في التسلسل الاختياري، وخصائص الفئة) في تعليمات برمجية قديمة يمكن للمتصفحات القديمة أو إصدارات Node فهمها. ولكن قبل وجود babel-esm-loader، إذا حاولت تحميل ملف ESM في Node، فستقوم Node بعملها الخاص، وعادةً ما يتم تشغيل babel فقط كخطوة بناء منفصلة - مما يعني أنك ستجمع كل التعليمات البرمجية الخاصة بك مرة واحدة، ثم تقوم بتشغيلها، بدون تعديلات لحظية. يعد ذلك بمثابة صداع إذا كنت تقوم بالاختبار، أو إذا كانت سلسلة الأدوات الخاصة بك بحاجة إلى ضبط التعليمات البرمجية بسرعة.


يعمل برنامج Babel-esm-loader على إصلاح ذلك من خلال التكامل مباشرة مع نظام ESM الخاص بالعقدة، مباشرة عند النقطة التي تكون فيها العقدة على وشك جلب وحدة نمطية ومعالجتها. دعونا نتعرف خطوة بخطوة على كيفية تشغيل الأمر خلف الكواليس:
-
تسجيل اللودر أولا . قبل أن تقوم حتى بتشغيل برنامج Node النصي الخاص بك، عليك أن تخبر Node، "مرحبًا، استخدم هذا المحمل المخصص بدلاً من معالج الوحدة الافتراضي الخاص بك." يمكنك القيام بذلك باستخدام علامة: العقدة --loader babel-esm-loader your-script.js. يتم ربط هذه العلامة في خط أنابيب تحليل الوحدة النمطية للعقدة - تحتوي العقدة على مجموعة محددة من الخطافات التي تستخدمها للتعامل مع استيراد الوحدات، مثل تحليل مسار الملف، وجلب محتوى الملف، وتحويله قبل التنفيذ. يقوم المحمل بالنقر على تلك الخطافات.
-
خطاف الحل: عندما تبحث Node عن وحدة نمطية، فإنها لا تبحث فقط عن ملف .js. فهو يتحقق من عمليات استيراد package.json وأسماء الوحدات العارية (مثل "lodash") وامتدادات الملفات. لا يغير خطاف حل babel-esm-loader المكان الذي تبحث فيه Node عن الملفات - فهو يتأكد فقط من وضع علامة على أي ملف يتم العثور عليه كوحدة ESM إذا لزم الأمر. انتظر، لا، تصحيح: إذا كانت الوحدة لا تحتوي على ملحق .mjs، أو أن package.json الخاص بها لا يحتوي على "type": "module"، فقد تعاملها Node على أنها CommonJS. يضبط هذا الخطاف ما يلي: إذا كان المُحمل نشطًا، فيمكنه وضع علامة على الوحدة كـ ESM حتى لو كان ملف .js، لذلك تعرف Node أنه يجب عليها استخدام الاستيراد/التصدير بدلاً من require().
-
خطاف التحميل: هذا هو السحر الحقيقي. بمجرد حصول Node على مسار الملف، فإنها تقوم بجلب محتوى الملف. سيعيد خطاف تحميل العقدة الافتراضي النص الخام فقط، لكن خطاف تحميل babel-esm-loader يأخذ هذا الكود الأولي، ويشغله عبر Babel، ويعيد الكود المحول كـ ESM. لنجعل ذلك واضحًا: لنفترض أن لديك ملفًا به رمز مثل const add = (a, b) => a + b; إضافة التصدير الافتراضية؛ - ولكن إذا كان هذا الكود يحتوي على ميزات بابل، مثل class MyComponent { #privateField = 5; }، سيقوم Babel بتحويل ذلك إلى رمز يمكن لإصدارات Node الأقدم تشغيله، ثم يرسل المُحمل هذا الرمز المحول مرة أخرى إلى Node.
-
رابط التحويل: انتظر، بعض الأشخاص يخلطون بين التحميل والتحويل، ولكن دعونا نوضح ذلك. رابط التحميل هو المكان الذي يحدث فيه التحويل، ولكنه مبني على واجهة برمجة التطبيقات الخاصة بـ Babel، والتي تتيح لك تكوين الإعدادات المسبقة والمكونات الإضافية التي تريد استخدامها. على سبيل المثال، إذا كان مشروعك يستخدم @babel/preset-env، فسيقوم المُحمل بسحب هذا التكوين من ملف babel.config.json الخاص بك، وتطبيقه على كل وحدة يتم تحميلها، وإخراج التعليمات البرمجية المتوافقة. لا حاجة إلى ترجمة كل ملف مسبقًا أولاً - يمكنك تعديل التعليمات البرمجية الخاصة بك ورؤية التغييرات على الفور، وهو ما يغير قواعد اللعبة بالنسبة لسير عمل التطوير.
ولكن هذا هو الأمر: نحن مورد أدوات التحميل، ولسنا مجرد مدونة أدوات عشوائية. فلماذا يهم هذا الأمر بالنسبة للأشخاص الذين ينشئون الأدوات أو التطبيقات أو حتى بيئات JS الخاصة بالمؤسسات؟ دعونا نتحدث عن حالات الاستخدام. إذا كنت تقوم بإنشاء إطار عمل مخصص، أو أداة إنشاء تحتاج إلى دعم ESM عبر إصدارات Node المختلفة، فإن babel-esm-loader يوفر عليك كتابة منطق تحويل الوحدة الخاصة بك من البداية. بدلاً من بناء مترجم كامل، يمكنك الاستفادة من النظام البيئي الذي تم اختباره في معركة Babel للتعامل مع دعم بناء الجملة، بينما يتعامل المُحمل مع التكامل مع نظام ESM الخاص بـ Node.
انتظر، ولكن دعونا نتناول مشكلة شائعة. يعتقد الكثير من المطورين الجدد في هذا الأمر أن الأمر مجرد "تشغيل Babel على ESM"، لكن لا - لا تتوفر خطافات ESM الخاصة بالعقدة إلا في Node 12 وما فوق، ويجب على المُحمل اتباع قواعد API محددة. على سبيل المثال، في Node، يجب أن يقوم خطاف التحميل بإرجاع كائن بتنسيق (مثل "الوحدة النمطية" لـ ESM)، وقصير (محتوى الملف كسلسلة)، وsourceMap (إذا كنت في حاجة إليه). يتأكد برنامج babel-esm-loader من صحة جميع قيم الإرجاع، لذلك لا تقوم Node بإلقاء أخطاء حول تنسيقات الوحدات غير الصالحة.
سؤال شائع آخر: كيف يختلف هذا عن @babel/register؟ @babel/register مخصص لـ CommonJS، أليس كذلك؟ يتم ربطه بـ require()، لذا فهو يعمل مع مكالمات النمط القديم، لكنه لا يلمس ESM. تم تصميم Babel-esm-loader خصيصًا لـ ESM، مما يعني أنه يعمل مع عبارات الاستيراد والاستيراد الديناميكي () وجميع ميزات ESM الحديثة التي لا يتعامل معها @babel/register. ولهذا السبب نوصي به لأي مشروع يتبنى الإدارة السليمة بيئيًا بشكل كامل ويحتاج إلى تكامل Babel بدون خطوة بناء كاملة.
الآن، دعونا نتعرف على كيفية عمل ذلك من الناحية العملية لعملائنا. عندما يستخدم أحد المطورين أدواتنا المدمجة في أداة التحميل، فإننا غالبًا ما نوجههم عبر إعداد babel-esm-loader بتكوين Babel الحالي الخاص بهم. على سبيل المثال، إذا كان babel.config.json لديك يحتوي على:
{
"الإعدادات المسبقة": ["@babel/preset-env"],
"الإضافات": ["@babel/plugin-proposal-class-properties"]
}
كل ما عليك فعله هو تشغيل العقدة --loader babel-esm-loader your-script.js، وكل وحدة يتم استيرادها في البرنامج النصي الخاص بك سوف تمر عبر Babel تلقائيًا. لا توجد خطوة بناء إضافية، ولا يوجد أمر ترجمة منفصل - يتم التعامل مع كل شيء بسرعة. هذا هو نوع التكامل الذي يجعل الأدوات تبدو سلسة، تمامًا مثلما يجعل اللودر المناسب للمعدات الثقيلة المهمة تسير بشكل أسرع.
انتظر، دعنا نعود إلى رابط Skid-steer Loader الذي ذكرته سابقًا. فكر في أداة التحميل على هذا النحو: إنها ليست المهمة نفسها، إنها الأداة التي تتيح لك نقل المواد (أو التعليمات البرمجية في حالتنا) حيث تحتاج إليها بالضبط، دون العمل اليدوي. إذا حاولت نقل كمية من الأوساخ باستخدام مجرفة، فهذا يشبه محاولة تشغيل JS الحديث بدون أداة مثل babel-esm-loader - فهو بطيء وغير فعال وسينتهي بك الأمر إلى حالة من الفوضى.
الآن، دعونا نتحدث عن بعض التحديثات الأخيرة لبرنامج babel-esm-loader. في عام 2023، أضاف المشرفون دعمًا لاستيراد ملفات JSON، والتي كانت نقطة ألم شائعة. قبل ذلك، إذا حاولت استيراد ملف JSON في وحدة ESM باستخدام أداة التحميل، فسيؤدي ذلك إلى حدوث خطأ لأن Node لم تتعرف على التنسيق. الآن، يمكنك استيراد pkg من './package.json' تأكيد { type: 'json' }; وسيتعامل المُحمل مع تحليل JSON جنبًا إلى جنب مع تحويل JS الخاص بك. جعل هذا التحديث الصغير طريقة التحميل أكثر فائدة لتطبيقات العالم الحقيقي.
شيء آخر يجب ملاحظته: إذا كنت تستخدم TypeScript، فإن babel-esm-loader يعمل مع TypeScript أيضًا. كل ما عليك فعله هو إضافة @babel/preset-typescript إلى تكوين Babel الخاص بك، وسيقوم المُحمل بإزالة أنواع TypeScript ونقل التعليمات البرمجية إلى JS عادي. وهذا يعني أنه يمكنك كتابة كود TypeScript ESM وتشغيله مباشرة في Node، دون الحاجة إلى خطوة بناء tsc - وهو مثالي للاختبار السريع أو المشروعات الصغيرة.
ولكن لنكن صادقين: إنها ليست حلاً سحريًا. هناك حالات لن يعمل فيها برنامج babel-esm-loader. على سبيل المثال، إذا كنت تكتب تعليمات برمجية تستخدم وحدات CommonJS حصريًا ولا تحتاج إلى ESM، فإن @babel/register لا يزال مناسبًا بشكل أفضل. وأيضًا، إذا كنت تقوم بإنشاء تطبيق إنتاج، فقد لا تزال ترغب في استخدام خطوة إنشاء تقليدية (مثل Webpack أو Rollup) للتحسين وتقسيم التعليمات البرمجية والتصغير. تم تصميم المُحمل لسير عمل التطوير، وليس لنشر الإنتاج - على الرغم من أنه يمكن أن يقترن بأدوات الإنتاج لتبسيط الاختبار المحلي.
الآن، باعتباري أحد موردي أدوات التحميل، سأخبرك بما يحبه عملاؤنا أكثر في babel-esm-loader: فهو يقلل من وقت الإعداد. يقضي الكثير من المطورين الجدد في ESM ساعات طويلة في اكتشاف كيفية جعل Babel وNode يعملان معًا، وهذا المحمل يلغي ذلك. بدلاً من كتابة خطافات وحدة مخصصة أو العبث بتكوينات Webpack، ما عليك سوى تشغيل علامة واحدة والبدء. هذه هي قيمة المُحمل الجيد: فهو يزيل التعقيد من العملية.
انتظر، دعنا نتعمق قليلاً في منطق مستوى التعليمات البرمجية. دعونا نلقي نظرة على نسخة مبسطة لما قد يفعله خطاف التحميل (لا تقلق، لا يوجد كود تقني فائق، فقط الجوهر):
تحميل وظيفة غير متزامنة (url، context، defaultLoad) {
// أولاً، استخدم التحميل الافتراضي للعقدة للحصول على محتوى الملف الخام
const { source, format } = انتظار defaultLoad(url, context, defaultLoad);
// قم بتطبيق Babel فقط إذا كان ملف JS ونتعامل مع ESM
if (format === 'module' && /.(js|mjs|cjs)$/.test(url)) {
// اسحب تكوين Babel من ملف التكوين الخاص بالمشروع
const babelConfig = require('./babel.config.json');
// قم بتحويل الكود المصدري باستخدام Babel
تحويل ثابت = يتطلب ('@babel/core').transformSync(source, {
...babelConfig,
اسم الملف: url، // أخبر بابل بالملف الذي يعالجه
});
// قم بإرجاع الكود المحول كـ ESM return { source: Transformed.code, format: 'module', };
}
// إذا لم يكن ملف JS ESM، فقم بإرجاع نتيجة التحميل الافتراضية
return defaultLoad(url, context, defaultLoad);
}
هذا هو جوهر كيفية عمل اللودر. إنها قطعة صغيرة من التعليمات البرمجية، ولكنها بمثابة الغراء بين نظام ESM الخاص بـ Node ومحرك التحويل الخاص بـ Babel. بالنسبة للمورد، هذه هي النقطة المثالية: أداة مركزة تحل مشكلة معينة، دون أن تكون متضخمة بميزات إضافية.
الآن، دعونا نتحدث عن المشكلات الشائعة التي يواجهها عملاؤنا عند استخدام هذا المُحمل. واحدة كبيرة منها هي إعدادات بابل المسبقة التي تم تكوينها بشكل خاطئ. إذا كانت أهداف البيئة المحددة مسبقًا ضيقة جدًا، فقد لا تعمل التعليمات البرمجية المحولة في إصدارات Node الأقدم. إذا كانت واسعة جدًا، فأنت تقوم بعمل غير ضروري وتبطئ النص البرمجي الخاص بك. نحن نساعد العملاء دائمًا على مراجعة تكوين Babel الخاص بهم عند قيامهم بإعداد المُحمل، بحيث يكون مُحسّنًا لبيئتهم.
هناك مشكلة أخرى تتعلق بالتبعيات التي تستخدم CommonJS حصريًا. إذا قمت باستيراد وحدة CommonJS (مثل 'lodash') من خلال أداة التحميل، فسوف تقوم Node بإلقاء خطأ لأن وحدات CommonJS ليست ESM أصلية. انتظر، لا - في الواقع، تدعم إصدارات Node الحديثة التشغيل المتداخل بين ESM وCommonJS، لكن المُحمل لا يزال يتعامل معها عن طريق ترجمة استدعاءات CommonJS إلى تعليمات برمجية متوافقة مع ESM. يعمل هذا في معظم الأحيان دون أي إعداد إضافي، لكن في بعض الأحيان ستواجه وحدة تعتمد على وحدات عمومية خاصة بـ CJS مثل الوحدة النمطية أو الصادرات، والتي يجب على أداة التحميل تعبئتها. لدينا حلول سريعة لهذه الحالات أيضًا.
في نهاية المطاف، يعد babel-esm-loader مثالًا رائعًا على أهمية أدوات التحميل في أدوات JavaScript. لا يقتصر الأمر على معالجة التعليمات البرمجية فحسب، بل يتعلق أيضًا بسد الفجوات بين المعايير والبيئات المختلفة، حتى يتمكن المطورون من التركيز على كتابة التعليمات البرمجية بدلاً من القتال باستخدام أدوات البناء. تمامًا مثلما تقوم اللودر الانزلاقي بتحويل موقع البناء الموحل وغير المستوي إلى مساحة يمكن أن يتم فيها العمل بكفاءة، فإن هذا اللودر يحول سير عمل ESM+Babel المفكك إلى شيء سلس وسهل.
إذا سئمت من خطوات البناء الفوضوية، أو إذا كان فريقك يواجه صعوبة في جعل ESM وBabel يعملان معًا، فيمكننا مساعدتك في دمج babel-esm-loader في سير عملك - بدون إعداد معقد أو مصطلحات، مجرد أداة تفعل ما تحتاجه بالضبط. تواصل معنا لمناقشة احتياجات مشروعك والحصول على حل مخصص يناسب مجموعتك.
مراجع:
- وثائق خطافات محمل ESM لـ Node.js
- وثائق بابل الرسمية لتكامل واجهة برمجة التطبيقات (API).
- دليل موارد صناعة اللوادر الانزلاقية (للحصول على سياق حول وظيفة اللودر)
- مناقشات المجتمع حول سير عمل ESM + Babel
