أطلق العنان لقوة الواجهات الأمامية المصغرة مع اتحاد وحدات جافاسكريبت في Webpack 5. تعلم كيفية بناء تطبيقات ويب قابلة للتطوير والصيانة ومستقلة.
اتحاد وحدات جافاسكريبت (Module Federation) مع Webpack 5: دليل شامل للواجهات الأمامية المصغرة
في المشهد المتطور باستمرار لتطوير الويب، يمكن أن يكون بناء التطبيقات الكبيرة والمعقدة مهمة شاقة. غالبًا ما تؤدي البنى المترابطة التقليدية إلى زيادة وقت التطوير، واختناقات النشر، وتحديات في الحفاظ على جودة الكود. ظهرت الواجهات الأمامية المصغرة (Micro-frontends) كنمط معماري قوي لمواجهة هذه التحديات، مما يسمح للفرق ببناء ونشر أجزاء مستقلة من تطبيق ويب أكبر. واحدة من أكثر التقنيات الواعدة لتنفيذ الواجهات الأمامية المصغرة هي اتحاد وحدات جافاسكريبت (JavaScript Module Federation)، التي تم تقديمها في Webpack 5.
ما هي الواجهات الأمامية المصغرة؟
الواجهات الأمامية المصغرة هي نمط معماري حيث يتم تقسيم تطبيق الواجهة الأمامية إلى وحدات أصغر ومستقلة، يمكن تطويرها واختبارها ونشرها بشكل مستقل من قبل فرق مختلفة. كل واجهة أمامية مصغرة مسؤولة عن مجال عمل أو ميزة معينة، ويتم تجميعها معًا في وقت التشغيل لتشكيل واجهة المستخدم الكاملة.
فكر في الأمر كشركة: بدلاً من وجود فريق تطوير عملاق واحد، لديك عدة فرق أصغر تركز على مجالات محددة. يمكن لكل فريق العمل بشكل مستقل، مما يسمح بدورات تطوير أسرع وصيانة أسهل. لنأخذ منصة تجارة إلكترونية كبيرة مثل أمازون؛ قد تدير فرق مختلفة كتالوج المنتجات، وعربة التسوق، وعملية الدفع، وإدارة حساب المستخدم. كل هذه يمكن أن تكون واجهات أمامية مصغرة مستقلة.
فوائد الواجهات الأمامية المصغرة:
- عمليات نشر مستقلة: يمكن للفرق نشر واجهاتها الأمامية المصغرة بشكل مستقل، دون التأثير على أجزاء أخرى من التطبيق. هذا يقلل من مخاطر النشر ويسمح بدورات إصدار أسرع.
- غير مقيدة بتقنية معينة: يمكن بناء واجهات أمامية مصغرة مختلفة باستخدام تقنيات أو أطر عمل مختلفة (مثل React، Angular، Vue.js). يتيح هذا للفرق اختيار أفضل تقنية لاحتياجاتهم الخاصة واعتماد التقنيات الجديدة تدريجيًا دون الحاجة إلى إعادة كتابة التطبيق بأكمله. تخيل فريقًا يستخدم React لكتالوج المنتجات، وآخر يستخدم Vue.js لصفحات الهبوط التسويقية، وثالث يستخدم Angular لعملية الدفع.
- تحسين استقلالية الفريق: تتمتع الفرق بملكية كاملة لواجهاتها الأمامية المصغرة، مما يؤدي إلى زيادة الاستقلالية، واتخاذ قرارات أسرع، وتحسين إنتاجية المطورين.
- زيادة قابلية التوسع: تتيح لك الواجهات الأمامية المصغرة توسيع تطبيقك أفقيًا عن طريق نشر واجهات أمامية مصغرة فردية على خوادم مختلفة.
- إعادة استخدام الكود: يمكن مشاركة المكونات والمكتبات المشتركة بسهولة بين الواجهات الأمامية المصغرة.
- صيانة أسهل: قواعد الكود الأصغر تكون عمومًا أسهل في الفهم والصيانة والتصحيح.
تحديات الواجهات الأمامية المصغرة:
- زيادة التعقيد: يمكن أن تضيف إدارة العديد من الواجهات الأمامية المصغرة تعقيدًا إلى البنية العامة، خاصة فيما يتعلق بالاتصال وإدارة الحالة والنشر.
- عبء على الأداء: يمكن أن يؤدي تحميل العديد من الواجهات الأمامية المصغرة إلى عبء على الأداء، خاصة إذا لم يتم تحسينها بشكل صحيح.
- الاهتمامات المشتركة: يمكن أن يكون التعامل مع الاهتمامات المشتركة مثل المصادقة والترخيص والتصميم (theming) تحديًا في بنية الواجهات الأمامية المصغرة.
- عبء تشغيلي: يتطلب ممارسات DevOps وبنية تحتية ناضجة لإدارة نشر ومراقبة العديد من الواجهات الأمامية المصغرة.
ما هو اتحاد وحدات جافاسكريبت (JavaScript Module Federation)؟
اتحاد وحدات جافاسكريبت هو ميزة في Webpack 5 تسمح لك بمشاركة الكود بين تطبيقات جافاسكريبت المترجمة بشكل منفصل في وقت التشغيل. يمكّنك من عرض أجزاء من تطبيقك كـ "وحدات" يمكن استهلاكها من قبل تطبيقات أخرى، دون الحاجة إلى نشرها في مستودع مركزي مثل npm.
فكر في اتحاد الوحدات كطريقة لإنشاء نظام بيئي متحد من التطبيقات، حيث يمكن لكل تطبيق أن يساهم بوظائفه الخاصة ويستهلك وظائف من تطبيقات أخرى. هذا يلغي الحاجة إلى الاعتماديات في وقت البناء ويسمح بعمليات نشر مستقلة حقًا.
على سبيل المثال، يمكن لفريق نظام التصميم عرض مكونات واجهة المستخدم كوحدات، ويمكن لفرق التطبيقات المختلفة استهلاك هذه المكونات مباشرة من تطبيق نظام التصميم، دون الحاجة إلى تثبيتها كحزم npm. عندما يقوم فريق نظام التصميم بتحديث المكونات، تنعكس التغييرات تلقائيًا في جميع التطبيقات المستهلكة.
المفاهيم الأساسية في اتحاد الوحدات:
- المستضيف (Host): التطبيق الرئيسي الذي يستهلك الوحدات البعيدة.
- البعيد (Remote): تطبيق يعرض وحدات ليتم استهلاكها من قبل تطبيقات أخرى.
- الوحدات المشتركة (Shared Modules): الوحدات التي تتم مشاركتها بين التطبيقات المستضيفة والبعيدة (مثل React، Lodash). يمكن لاتحاد الوحدات التعامل تلقائيًا مع إدارة الإصدارات وإزالة التكرار للوحدات المشتركة لضمان تحميل إصدار واحد فقط من كل وحدة.
- الوحدات المعروضة (Exposed Modules): وحدات محددة من تطبيق بعيد يتم إتاحتها للاستهلاك من قبل تطبيقات أخرى.
- RemoteEntry.js: ملف يتم إنشاؤه بواسطة Webpack يحتوي على البيانات الوصفية حول الوحدات المعروضة لتطبيق بعيد. يستخدم التطبيق المستضيف هذا الملف لاكتشاف وتحميل الوحدات البعيدة.
إعداد اتحاد الوحدات مع Webpack 5: دليل عملي
دعنا نمر بمثال عملي لإعداد اتحاد الوحدات مع Webpack 5. سننشئ تطبيقين بسيطين: تطبيق مستضيف (Host) وتطبيق بعيد (Remote). سيعرض التطبيق البعيد مكونًا، وسيستهلكه التطبيق المستضيف.
1. إعداد المشروع
أنشئ مجلدين منفصلين لتطبيقاتك: `host` و `remote`.
```bash mkdir host remote cd host npm init -y npm install webpack webpack-cli webpack-dev-server html-webpack-plugin --save-dev npm install react react-dom cd ../remote npm init -y npm install webpack webpack-cli webpack-dev-server html-webpack-plugin --save-dev npm install react react-dom ```2. إعداد التطبيق البعيد (Remote)
في مجلد `remote`، أنشئ الملفات التالية:
- `src/index.js`: نقطة الدخول للتطبيق.
- `src/RemoteComponent.jsx`: المكون الذي سيتم عرضه.
- `webpack.config.js`: ملف إعدادات Webpack.
src/index.js:
```javascript import React from 'react'; import ReactDOM from 'react-dom/client'; import RemoteComponent from './RemoteComponent'; const App = () => (Remote Application
src/RemoteComponent.jsx:
```javascript import React from 'react'; const RemoteComponent = () => (This is a Remote Component!
Rendered from the Remote Application.
webpack.config.js:
```javascript const HtmlWebpackPlugin = require('html-webpack-plugin'); const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin'); const path = require('path'); module.exports = { entry: './src/index', mode: 'development', devServer: { port: 3001, static: { directory: path.join(__dirname, 'dist'), }, }, output: { publicPath: 'auto', }, module: { rules: [ { test: /\.(js|jsx)$/, exclude: /node_modules/, use: { loader: 'babel-loader', options: { presets: ['@babel/preset-react', '@babel/preset-env'], }, }, }, ], }, plugins: [ new ModuleFederationPlugin({ name: 'remote', filename: 'remoteEntry.js', exposes: { './RemoteComponent': './src/RemoteComponent', }, shared: { react: { singleton: true, eager: true }, 'react-dom': { singleton: true, eager: true }, }, }), new HtmlWebpackPlugin({ template: './public/index.html', }), ], resolve: { extensions: ['.js', '.jsx'], }, }; ```أنشئ `public/index.html` ببنية HTML أساسية. المهم هو وجود `
`3. إعداد التطبيق المستضيف (Host)
في مجلد `host`، أنشئ الملفات التالية:
- `src/index.js`: نقطة الدخول للتطبيق.
- `webpack.config.js`: ملف إعدادات Webpack.
src/index.js:
```javascript import React, { Suspense } from 'react'; import ReactDOM from 'react-dom/client'; const RemoteComponent = React.lazy(() => import('remote/RemoteComponent')); const App = () => (Host Application
webpack.config.js:
```javascript const HtmlWebpackPlugin = require('html-webpack-plugin'); const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin'); const path = require('path'); module.exports = { entry: './src/index', mode: 'development', devServer: { port: 3000, static: { directory: path.join(__dirname, 'dist'), }, }, output: { publicPath: 'auto', }, module: { rules: [ { test: /\.(js|jsx)$/, exclude: /node_modules/, use: { loader: 'babel-loader', options: { presets: ['@babel/preset-react', '@babel/preset-env'], }, }, }, ], }, plugins: [ new ModuleFederationPlugin({ name: 'host', remotes: { remote: 'remote@http://localhost:3001/remoteEntry.js', }, shared: { react: { singleton: true, eager: true }, 'react-dom': { singleton: true, eager: true }, }, }), new HtmlWebpackPlugin({ template: './public/index.html', }), ], resolve: { extensions: ['.js', '.jsx'], }, }; ```أنشئ `public/index.html` ببنية HTML أساسية (مشابهة للتطبيق البعيد). المهم هو وجود `
`4. تثبيت Babel
في كلا المجلدين `host` و `remote`، قم بتثبيت اعتماديات Babel:
```bash npm install --save-dev @babel/core @babel/preset-env @babel/preset-react babel-loader ```5. تشغيل التطبيقات
في كلا المجلدين `host` و `remote`، أضف السكربت التالي إلى `package.json`:
```json "scripts": { "start": "webpack serve" } ```الآن، ابدأ كلا التطبيقين:
```bash cd remote npm start cd ../host npm start ```افتح متصفحك وانتقل إلى `http://localhost:3000`. يجب أن ترى التطبيق المستضيف مع المكون البعيد معروضًا بداخله.
شرح خيارات الإعداد الرئيسية:
- `name`: اسم فريد للتطبيق.
- `filename`: اسم الملف الذي سيحتوي على البيانات الوصفية حول الوحدات المعروضة (مثل `remoteEntry.js`).
- `exposes`: خريطة من أسماء الوحدات إلى مسارات الملفات، تحدد الوحدات التي يجب عرضها.
- `remotes`: خريطة من أسماء التطبيقات البعيدة إلى عناوين URL، تحدد مكان العثور على ملف remoteEntry.js لكل تطبيق بعيد.
- `shared`: قائمة بالوحدات التي يجب مشاركتها بين التطبيق المستضيف والتطبيقات البعيدة. يضمن الخيار `singleton: true` تحميل نسخة واحدة فقط من كل وحدة مشتركة. يضمن الخيار `eager: true` تحميل الوحدة المشتركة بشكل فوري (أي قبل أي وحدات أخرى).
تقنيات متقدمة في اتحاد الوحدات
يقدم اتحاد الوحدات العديد من الميزات المتقدمة التي يمكن أن تساعدك في بناء معماريات واجهات أمامية مصغرة أكثر تطورًا.
الجهات البعيدة الديناميكية (Dynamic Remotes)
بدلاً من كتابة عناوين URL للتطبيقات البعيدة بشكل ثابت في إعدادات Webpack، يمكنك تحميلها ديناميكيًا في وقت التشغيل. يتيح لك هذا تحديث موقع التطبيقات البعيدة بسهولة دون الحاجة إلى إعادة بناء التطبيق المستضيف.
على سبيل المثال، يمكنك تخزين عناوين URL للتطبيقات البعيدة في ملف إعدادات أو قاعدة بيانات وتحميلها ديناميكيًا باستخدام جافاسكريبت.
```javascript // In webpack.config.js remotes: { remote: `promise new Promise(resolve => { const urlParams = new URLSearchParams(window.location.search); const remoteUrl = urlParams.get('remote'); // Assume remoteUrl is something like 'http://localhost:3001/remoteEntry.js' const script = document.createElement('script'); script.src = remoteUrl; script.onload = () => { // the key of module federation is that the remote app is // available using the name in the remote resolve(window.remote); }; document.head.appendChild(script); })`, }, ```الآن يمكنك تحميل التطبيق المستضيف مع معامل استعلام `?remote=http://localhost:3001/remoteEntry.js`
إدارة إصدارات الوحدات المشتركة
يمكن لاتحاد الوحدات التعامل تلقائيًا مع إدارة الإصدارات وإزالة التكرار للوحدات المشتركة لضمان تحميل إصدار متوافق واحد فقط من كل وحدة. هذا مهم بشكل خاص عند التعامل مع التطبيقات الكبيرة والمعقدة التي لديها العديد من الاعتماديات.
يمكنك تحديد نطاق الإصدار لكل وحدة مشتركة في إعدادات Webpack.
```javascript // In webpack.config.js shared: { react: { singleton: true, eager: true, requiredVersion: '^18.0.0' }, 'react-dom': { singleton: true, eager: true, requiredVersion: '^18.0.0' }, }, ```محملات الوحدات المخصصة
يسمح لك اتحاد الوحدات بتعريف محملات وحدات مخصصة يمكن استخدامها لتحميل الوحدات من مصادر مختلفة أو بتنسيقات مختلفة. يمكن أن يكون هذا مفيدًا لتحميل الوحدات من شبكة توصيل المحتوى (CDN) أو من سجل وحدات مخصص.
مشاركة الحالة بين الواجهات الأمامية المصغرة
أحد تحديات معماريات الواجهات الأمامية المصغرة هو مشاركة الحالة بين الواجهات المختلفة. هناك عدة طرق يمكنك اتباعها لمعالجة هذا التحدي:
- إدارة الحالة المستندة إلى URL: قم بتخزين الحالة في عنوان URL واستخدمه للتواصل بين الواجهات الأمامية المصغرة. هذه طريقة بسيطة ومباشرة، ولكنها يمكن أن تصبح مرهقة للحالات المعقدة.
- الأحداث المخصصة: استخدم الأحداث المخصصة لبث تغييرات الحالة بين الواجهات الأمامية المصغرة. هذا يسمح بالاقتران الضعيف بين الواجهات، ولكن قد يكون من الصعب إدارة الاشتراكات في الأحداث.
- مكتبة إدارة حالة مشتركة: استخدم مكتبة إدارة حالة مشتركة مثل Redux أو MobX لإدارة حالة التطبيق بأكمله. يوفر هذا طريقة مركزية ومتسقة لإدارة الحالة، ولكنه قد يفرض اعتمادًا على مكتبة إدارة حالة معينة.
- وسيط الرسائل (Message Broker): استخدم وسيط رسائل مثل RabbitMQ أو Kafka لتسهيل الاتصال ومشاركة الحالة بين الواجهات الأمامية المصغرة. هذا حل أكثر تعقيدًا، ولكنه يوفر درجة عالية من المرونة وقابلية التوسع.
أفضل الممارسات لتنفيذ الواجهات الأمامية المصغرة مع اتحاد الوحدات
فيما يلي بعض أفضل الممارسات التي يجب مراعاتها عند تنفيذ الواجهات الأمامية المصغرة مع اتحاد الوحدات:
- تحديد حدود واضحة لكل واجهة أمامية مصغرة: يجب أن تكون كل واجهة مسؤولة عن مجال عمل أو ميزة محددة ويجب أن يكون لها واجهات محددة جيدًا.
- استخدام حزمة تقنية متسقة: بينما يسمح لك اتحاد الوحدات باستخدام تقنيات مختلفة لواجهات مختلفة، فمن الجيد عمومًا استخدام حزمة تقنية متسقة لتقليل التعقيد وتحسين قابلية الصيانة.
- إنشاء بروتوكولات اتصال واضحة: حدد بروتوكولات اتصال واضحة لكيفية تفاعل الواجهات الأمامية المصغرة مع بعضها البعض.
- أتمتة عملية النشر: أتمتة عملية النشر لضمان إمكانية نشر الواجهات الأمامية المصغرة بشكل مستقل وموثوق. فكر في استخدام خطوط أنابيب CI/CD وأدوات البنية التحتية ككود.
- مراقبة أداء واجهاتك الأمامية المصغرة: راقب أداء واجهاتك لتحديد ومعالجة أي اختناقات في الأداء. استخدم أدوات مثل Google Analytics أو New Relic أو Datadog.
- تنفيذ معالجة قوية للأخطاء: نفذ معالجة قوية للأخطاء لضمان مرونة تطبيقك في مواجهة الفشل.
- تبني نموذج حوكمة لامركزي: قم بتمكين الفرق من اتخاذ القرارات بشأن واجهاتها الأمامية المصغرة، مع الحفاظ على الاتساق والجودة بشكل عام.
أمثلة واقعية لاتحاد الوحدات قيد التنفيذ
في حين أن دراسات الحالة المحددة غالبًا ما تكون سرية، فإليك بعض السيناريوهات العامة حيث يمكن أن يكون اتحاد الوحدات مفيدًا بشكل لا يصدق:
- منصات التجارة الإلكترونية: كما ذكرنا سابقًا، يمكن لمنصات التجارة الإلكترونية الكبيرة استخدام اتحاد الوحدات لبناء واجهات أمامية مصغرة مستقلة لكتالوج المنتجات وعربة التسوق وعملية الدفع وإدارة حساب المستخدم. يتيح ذلك لفرق مختلفة العمل على هذه الميزات بشكل مستقل ونشرها دون التأثير على أجزاء أخرى من التطبيق. يمكن لمنصة عالمية تخصيص الميزات لمناطق مختلفة عبر الوحدات البعيدة.
- تطبيقات الخدمات المالية: غالبًا ما تحتوي تطبيقات الخدمات المالية على واجهات مستخدم معقدة مع العديد من الميزات المختلفة. يمكن استخدام اتحاد الوحدات لبناء واجهات أمامية مصغرة مستقلة لأنواع الحسابات المختلفة ومنصات التداول ولوحات معلومات التقارير. يمكن تسليم ميزات الامتثال الفريدة لبلدان معينة عبر اتحاد الوحدات.
- بوابات الرعاية الصحية: يمكن لبوابات الرعاية الصحية استخدام اتحاد الوحدات لبناء واجهات أمامية مصغرة مستقلة لإدارة المرضى وجدولة المواعيد والوصول إلى السجلات الطبية. يمكن تحميل وحدات مختلفة لمقدمي التأمين أو المناطق المختلفة ديناميكيًا.
- أنظمة إدارة المحتوى (CMS): يمكن لنظام إدارة المحتوى استخدام اتحاد الوحدات للسماح للمستخدمين بإضافة وظائف مخصصة إلى مواقعهم عن طريق تحميل وحدات بعيدة من مطورين خارجيين. يمكن توزيع السمات والإضافات والأدوات المختلفة كواجهات أمامية مصغرة مستقلة.
- أنظمة إدارة التعلم (LMS): يمكن لنظام إدارة التعلم تقديم دورات تم تطويرها بشكل مستقل ودمجها في منصة موحدة عبر اتحاد الوحدات. لا تتطلب التحديثات على الدورات الفردية إعادة نشر على مستوى المنصة.
الخلاصة
يوفر اتحاد وحدات جافاسكريبت في Webpack 5 طريقة قوية ومرنة لبناء معماريات الواجهات الأمامية المصغرة. يسمح لك بمشاركة الكود بين تطبيقات جافاسكريبت المترجمة بشكل منفصل في وقت التشغيل، مما يتيح عمليات نشر مستقلة وتنوعًا تقنيًا وتحسين استقلالية الفريق. باتباع أفضل الممارسات الموضحة في هذا الدليل، يمكنك الاستفادة من اتحاد الوحدات لبناء تطبيقات ويب قابلة للتطوير والصيانة ومبتكرة.
مستقبل تطوير الواجهات الأمامية يتجه بلا شك نحو معماريات معيارية وموزعة. يوفر اتحاد الوحدات أداة حاسمة لبناء هذه الأنظمة الحديثة، مما يمكّن الفرق من إنشاء تطبيقات معقدة بسرعة ومرونة وقدرة على الصمود أكبر. مع نضوج التكنولوجيا، يمكننا أن نتوقع ظهور المزيد من حالات الاستخدام المبتكرة وأفضل الممارسات.