استكشف قدرات المشاركة في وقت التشغيل لاتحاد وحدات جافا سكريبت، وفوائده لبناء تطبيقات عالمية قابلة للتطوير والصيانة والتعاون، واستراتيجيات التنفيذ العملية.
اتحاد وحدات جافا سكريبت (Module Federation): إطلاق العنان لقوة المشاركة في وقت التشغيل للتطبيقات العالمية
في المشهد الرقمي سريع التطور اليوم، يعد بناء تطبيقات ويب قابلة للتطوير والصيانة والتعاون أمرًا بالغ الأهمية. مع نمو فرق التطوير وزيادة تعقيد التطبيقات، أصبحت الحاجة إلى مشاركة الكود وفصل المكونات بكفاءة أكثر أهمية من أي وقت مضى. يقدم اتحاد وحدات جافا سكريبت (JavaScript Module Federation)، وهو ميزة رائدة تم تقديمها مع Webpack 5، حلاً قويًا من خلال تمكين مشاركة الكود في وقت التشغيل بين التطبيقات المنشورة بشكل مستقل. يتعمق هذا المقال في المفاهيم الأساسية لاتحاد الوحدات، مع التركيز على قدراته في المشاركة في وقت التشغيل، ويستكشف كيف يمكن أن يحدث ثورة في طريقة بناء الفرق العالمية لتطبيقات الويب الخاصة بها وإدارتها.
المشهد المتطور لتطوير الويب والحاجة إلى المشاركة
تاريخيًا، كان تطوير الويب غالبًا ما يتضمن تطبيقات متجانسة (monolithic) حيث يوجد كل الكود في قاعدة كود واحدة. في حين أن هذا النهج يمكن أن يكون مناسبًا للمشاريع الصغيرة، إلا أنه سرعان ما يصبح غير عملي مع توسع التطبيقات. تصبح التبعيات متشابكة، وتزداد أوقات البناء، ويمكن أن يكون نشر التحديثات مهمة معقدة ومحفوفة بالمخاطر. مهد صعود الخدمات المصغرة (microservices) في تطوير الواجهة الخلفية الطريق لأنماط معمارية مماثلة في الواجهة الأمامية، مما أدى إلى ظهور الواجهات الأمامية المصغرة (microfrontends).
تهدف الواجهات الأمامية المصغرة إلى تقسيم تطبيقات الواجهة الأمامية الكبيرة والمعقدة إلى وحدات أصغر ومستقلة وقابلة للنشر. هذا يسمح لفرق مختلفة بالعمل على أجزاء مختلفة من التطبيق بشكل مستقل، مما يؤدي إلى دورات تطوير أسرع وتحسين استقلالية الفريق. ومع ذلك، كان التحدي الكبير في معماريات الواجهات الأمامية المصغرة دائمًا هو المشاركة الفعالة للكود. يؤدي تكرار المكتبات المشتركة أو مكونات واجهة المستخدم أو الوظائف المساعدة عبر واجهات أمامية مصغرة متعددة إلى:
- زيادة أحجام الحزم (Bundle Sizes): يحمل كل تطبيق نسخته الخاصة من التبعيات المشتركة، مما يزيد من حجم التنزيل الإجمالي للمستخدمين.
- التبعيات غير المتسقة: قد ينتهي الأمر بالواجهات الأمامية المصغرة المختلفة باستخدام إصدارات مختلفة من نفس المكتبة، مما يؤدي إلى مشكلات التوافق وسلوك غير متوقع.
- عبء الصيانة: يتطلب تحديث الكود المشترك تغييرات عبر تطبيقات متعددة، مما يزيد من خطر الأخطاء ويبطئ عملية النشر.
- إهدار الموارد: يعد تنزيل نفس الكود عدة مرات أمرًا غير فعال لكل من العميل والخادم.
يعالج اتحاد الوحدات هذه التحديات مباشرة من خلال تقديم آلية لمشاركة الكود بشكل حقيقي في وقت التشغيل.
ما هو اتحاد وحدات جافا سكريبت؟
اتحاد وحدات جافا سكريبت، الذي يتم تنفيذه بشكل أساسي من خلال Webpack 5، هو ميزة لأداة البناء تسمح لتطبيقات جافا سكريبت بتحميل الكود ديناميكيًا من تطبيقات أخرى في وقت التشغيل. إنه يتيح إنشاء تطبيقات متعددة مستقلة يمكنها مشاركة الكود والتبعيات دون الحاجة إلى مستودع واحد (monorepo) أو عملية تكامل معقدة في وقت البناء.
الفكرة الأساسية هي تحديد "التطبيقات البعيدة" (remotes) (التطبيقات التي تعرض الوحدات) و"المضيفين" (hosts) (التطبيقات التي تستهلك الوحدات من التطبيقات البعيدة). يمكن نشر هذه التطبيقات البعيدة والمضيفة بشكل مستقل، مما يوفر مرونة كبيرة في إدارة التطبيقات المعقدة وتمكين أنماط معمارية متنوعة.
فهم المشاركة في وقت التشغيل: جوهر اتحاد الوحدات
المشاركة في وقت التشغيل هي حجر الزاوية في قوة اتحاد الوحدات. على عكس تقنيات تقسيم الكود التقليدية أو إدارة التبعيات المشتركة التي تحدث غالبًا في وقت البناء، يسمح اتحاد الوحدات للتطبيقات باكتشاف وتحميل الوحدات من تطبيقات أخرى مباشرة في متصفح المستخدم. هذا يعني أنه يمكن تحميل مكتبة مشتركة، أو مكتبة مكونات واجهة مستخدم مشتركة، أو حتى وحدة ميزة مرة واحدة بواسطة تطبيق واحد ثم إتاحتها للتطبيقات الأخرى التي تحتاج إليها.
دعنا نحلل كيفية عمل ذلك:
المفاهيم الأساسية:
- Exposes (يعرض): يمكن للتطبيق أن 'يعرض' وحدات معينة (على سبيل المثال، مكون React، وظيفة مساعدة) يمكن للتطبيقات الأخرى استهلاكها. يتم تجميع هذه الوحدات المعروضة في حاوية يمكن تحميلها عن بعد.
- Remotes (التطبيقات البعيدة): يُعتبر التطبيق الذي يعرض الوحدات 'تطبيقًا بعيدًا'. يعرض وحداته عبر تكوين مشترك.
- Consumes (يستهلك): التطبيق الذي يحتاج إلى استخدام وحدات من تطبيق بعيد هو 'مستهلك' أو 'مضيف'. يقوم بتكوين نفسه لمعرفة مكان العثور على التطبيقات البعيدة والوحدات التي يجب تحميلها.
- Shared Modules (الوحدات المشتركة): يسمح اتحاد الوحدات بتحديد الوحدات المشتركة. عندما تستهلك تطبيقات متعددة نفس الوحدة المشتركة، يتم تحميل مثيل واحد فقط من تلك الوحدة ومشاركته بينها. هذا جانب حاسم لتحسين أحجام الحزم ومنع تعارض التبعيات.
الآلية:
عندما يحتاج تطبيق مضيف إلى وحدة من تطبيق بعيد، فإنه يطلبها من حاوية التطبيق البعيد. ثم تقوم حاوية التطبيق البعيد بتحميل الوحدة المطلوبة ديناميكيًا. إذا كانت الوحدة محملة بالفعل بواسطة تطبيق مستهلك آخر، فسيتم مشاركتها. يحدث هذا التحميل والمشاركة الديناميكية بسلاسة في وقت التشغيل، مما يوفر طريقة فعالة للغاية لإدارة التبعيات.
فوائد اتحاد الوحدات للتطوير العالمي
إن مزايا اعتماد اتحاد الوحدات لبناء تطبيقات عالمية كبيرة وبعيدة المدى:
1. تعزيز قابلية التوسع والصيانة:
من خلال تقسيم تطبيق كبير إلى واجهات أمامية مصغرة أصغر ومستقلة، يعزز اتحاد الوحدات بطبيعته قابلية التوسع. يمكن للفرق العمل على واجهات أمامية مصغرة فردية دون التأثير على الآخرين، مما يسمح بالتطوير المتوازي والنشر المستقل. هذا يقلل من العبء المعرفي المرتبط بإدارة قاعدة كود ضخمة ويجعل التطبيق أكثر قابلية للصيانة بمرور الوقت.
2. تحسين أحجام الحزم والأداء:
الفائدة الأكبر للمشاركة في وقت التشغيل هي تقليل حجم التنزيل الإجمالي. بدلاً من أن يقوم كل تطبيق بتكرار المكتبات المشتركة (مثل React أو Lodash أو مكتبة مكونات واجهة مستخدم مخصصة)، يتم تحميل هذه التبعيات مرة واحدة ومشاركتها. وهذا يؤدي إلى:
- أوقات تحميل أولية أسرع: يقوم المستخدمون بتنزيل كود أقل، مما يؤدي إلى عرض أولي أسرع للتطبيق.
- تحسين التنقل اللاحق: عند التنقل بين الواجهات الأمامية المصغرة التي تشترك في التبعيات، يتم إعادة استخدام الوحدات المحملة بالفعل، مما يؤدي إلى تجربة مستخدم أكثر سلاسة.
- تقليل الحمل على الخادم: يتم نقل بيانات أقل من الخادم، مما قد يقلل من تكاليف الاستضافة.
لنأخذ مثالاً على منصة تجارة إلكترونية عالمية بها أقسام مميزة لقوائم المنتجات وحسابات المستخدمين والدفع. إذا كان كل قسم عبارة عن واجهة أمامية مصغرة منفصلة، ولكنها تعتمد جميعها على مكتبة مكونات واجهة مستخدم مشتركة للأزرار والنماذج والتنقل، يضمن اتحاد الوحدات تحميل هذه المكتبة مرة واحدة فقط، بغض النظر عن القسم الذي يزوره المستخدم أولاً.
3. زيادة استقلالية الفريق والتعاون:
يمكّن اتحاد الوحدات الفرق المختلفة، التي قد تكون موجودة في مناطق عالمية مختلفة، من العمل بشكل مستقل على واجهاتها الأمامية المصغرة الخاصة بها. يمكنهم اختيار حزم التكنولوجيا الخاصة بهم (في حدود المعقول، للحفاظ على مستوى معين من الاتساق) وجداول النشر. تعزز هذه الاستقلالية الابتكار الأسرع وتقلل من عبء الاتصال. ومع ذلك، فإن القدرة على مشاركة الكود المشترك تشجع أيضًا على التعاون، حيث يمكن للفرق المساهمة في المكتبات والمكونات المشتركة والاستفادة منها.
تخيل مؤسسة مالية عالمية لديها فرق منفصلة في أوروبا وآسيا وأمريكا الشمالية مسؤولة عن عروض منتجات مختلفة. يسمح اتحاد الوحدات لكل فريق بتطوير ميزاته المحددة مع الاستفادة من خدمة مصادقة مشتركة أو مكتبة رسوم بيانية مشتركة طورها فريق مركزي. هذا يعزز قابلية إعادة الاستخدام ويضمن الاتساق عبر خطوط الإنتاج المختلفة.
4. حيادية التكنولوجيا (مع بعض التحفظات):
بينما يعتمد اتحاد الوحدات على Webpack، فإنه يسمح بدرجة من حيادية التكنولوجيا بين الواجهات الأمامية المصغرة المختلفة. يمكن بناء واجهة أمامية مصغرة باستخدام React، وأخرى باستخدام Vue.js، وثالثة باستخدام Angular، طالما أنها تتفق على كيفية عرض واستهلاك الوحدات. ومع ذلك، للمشاركة الحقيقية في وقت التشغيل لأطر العمل المعقدة مثل React أو Vue، يجب إيلاء اهتمام خاص لكيفية تهيئة هذه الأطر ومشاركتها لتجنب تحميل مثيلات متعددة والتسبب في تعارضات.
قد يكون لدى شركة SaaS عالمية منصة أساسية مطورة بإطار عمل واحد ثم تقوم بفصل وحدات ميزات متخصصة طورتها فرق إقليمية مختلفة باستخدام أطر عمل أخرى. يمكن لاتحاد الوحدات تسهيل تكامل هذه الأجزاء المتباينة، شريطة إدارة التبعيات المشتركة بعناية.
5. إدارة أسهل للإصدارات:
عندما تحتاج تبعية مشتركة إلى التحديث، لا يلزم سوى تحديث وإعادة نشر التطبيق البعيد الذي يعرضها. ستلتقط جميع التطبيقات المستهلكة الإصدار الجديد تلقائيًا أثناء تحميلها التالي. هذا يبسط عملية إدارة وتحديث الكود المشترك عبر مشهد التطبيق بأكمله.
تنفيذ اتحاد الوحدات: أمثلة واستراتيجيات عملية
دعنا نلقي نظرة على كيفية إعداد واستخدام اتحاد الوحدات عمليًا. سنستخدم تكوينات Webpack مبسطة لتوضيح المفاهيم الأساسية.
السيناريو: مشاركة مكتبة مكونات واجهة المستخدم
لنفترض أن لدينا تطبيقين: 'كتالوج المنتجات' (تطبيق بعيد) و 'لوحة تحكم المستخدم' (مضيف). كلاهما يحتاج إلى استخدام مكون 'Button' مشترك من مكتبة مخصصة 'Shared UI'.
1. مكتبة 'Shared UI' (تطبيق بعيد):
سيعرض هذا التطبيق مكون 'Button' الخاص به.
webpack.config.js
لـ 'Shared UI' (تطبيق بعيد):
const { ModuleFederationPlugin } = require('webpack');
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
filename: 'remoteEntry.js',
path: path.resolve(__dirname, 'dist'),
publicPath: 'http://localhost:3001/dist/', // URL where the remote will be served
},
plugins: [
new ModuleFederationPlugin({
name: 'sharedUI',
filename: 'remoteEntry.js',
exposes: {
'./Button': './src/components/Button.js', // Expose Button component
},
shared: {
// Define shared dependencies
react: {
singleton: true, // Ensure only one instance of React is loaded
},
'react-dom': {
singleton: true,
},
},
}),
],
// ... other webpack configurations (babel, devServer, etc.)
};
src/components/Button.js
:
import React from 'react';
const Button = ({ onClick, children }) => (
);
export default Button;
في هذا الإعداد، يعرض 'Shared UI' مكون Button
الخاص به ويعلن عن react
و react-dom
كتابعيات مشتركة مع singleton: true
لضمان معاملتها كمثيلات فردية عبر التطبيقات المستهلكة.
2. تطبيق 'كتالوج المنتجات' (تطبيق بعيد):
سيحتاج هذا التطبيق أيضًا إلى استهلاك مكون Button
المشترك ومشاركة تبعياته الخاصة.
webpack.config.js
لـ 'كتالوج المنتجات' (تطبيق بعيد):
const { ModuleFederationPlugin } = require('webpack');
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
filename: 'remoteEntry.js',
path: path.resolve(__dirname, 'dist'),
publicPath: 'http://localhost:3002/dist/', // URL where this remote will be served
},
plugins: [
new ModuleFederationPlugin({
name: 'productCatalog',
filename: 'remoteEntry.js',
exposes: {
'./ProductList': './src/components/ProductList.js', // Expose ProductList
},
remotes: {
// Define a remote it needs to consume from
sharedUI: 'sharedUI@http://localhost:3001/dist/remoteEntry.js',
},
shared: {
// Shared dependencies with the same version and singleton: true
react: {
singleton: true,
},
'react-dom': {
singleton: true,
},
},
}),
],
// ... other webpack configurations
};
الآن، يعرض 'كتالوج المنتجات' مكون ProductList
الخاص به ويعلن عن تطبيقاته البعيدة، مشيرًا تحديدًا إلى تطبيق 'Shared UI'. كما يعلن عن نفس التبعيات المشتركة.
3. تطبيق 'لوحة تحكم المستخدم' (مضيف):
سيستهلك هذا التطبيق مكون Button
من 'Shared UI' ومكون ProductList
من 'كتالوج المنتجات'.
webpack.config.js
لـ 'لوحة تحكم المستخدم' (مضيف):
const { ModuleFederationPlugin } = require('webpack');
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist'),
publicPath: 'http://localhost:3000/dist/', // URL where this app's bundles are served
},
plugins: [
new ModuleFederationPlugin({
name: 'userDashboard',
remotes: {
// Define the remotes this host application needs
sharedUI: 'sharedUI@http://localhost:3001/dist/remoteEntry.js',
productCatalog: 'productCatalog@http://localhost:3002/dist/remoteEntry.js',
},
shared: {
// Shared dependencies that must match the remotes
react: {
singleton: true,
import: 'react', // Specify the module name for import
},
'react-dom': {
singleton: true,
import: 'react-dom',
},
},
}),
],
// ... other webpack configurations
};
src/index.js
لـ 'لوحة تحكم المستخدم':
import React, { Suspense } from 'react';
import ReactDOM from 'react-dom';
// Dynamically import the shared Button component
const RemoteButton = React.lazy(() => import('sharedUI/Button'));
// Dynamically import the ProductList component
const RemoteProductList = React.lazy(() => import('productCatalog/ProductList'));
const App = () => {
const handleClick = () => {
alert('Button clicked from shared UI!');
};
return (
User Dashboard
Loading Button... }>
Click Me
Products
Loading Products...