في المشهد المتطور لتطوير الواجهات الأمامية، غالباً ما تصبح التطبيقات الضخمة عنق زجامة للفرق الهندسية الكبيرة. ومع نمو التطبيقات، تزداد تعقيدات إدارة التبعيات ودورات النشر وملكية الكود. هنا تبرز هندسة الواجهات الأمامية الصغيرة (Micro-Frontend)، مما يتيح للفرق بناء واختبار ونشر تطبيقات واجهة أمامية مستقلة. بينما اعتمدت التطبيقات المبكرة على حلول معقدة مثل إطارات العمل iframe أو تجميع وقت التشغيل، قدم Webpack 5 حلاً أصلياً وقوياً: توحيد الوحدات (Module Federation).
يستكشف هذا المنشور كيفية الاستفادة من إضافة توحيد الوحدات في Webpack 5 لإنشاء واجهات أمامية صغيرة قابلة للتوسع ومستقلة حقاً، يمكن مشاركتها واستهلاكها عبر تطبيقات مختلفة دون الأعباء التقليدية.
ما هو توحيد الوحدات؟
توحيد الوحدات هو مفهوم يسمح لتطبيقات JavaScript باستيراد الكود ديناميكياً من وقت تشغيل آخر (متصفحات، خوادم، أو عمال الويب) أثناء وقت التنفيذ. على عكس مجمعات الكود التقليدية التي تحل التبعيات أثناء وقت البناء، يقوم توحيد الوحدات بحلها أثناء وقت التنفيذ. هذا يعني أن التطبيق أ يمكنه تشغيل الكود المكتوب في التطبيق ب، وكأنه محلي، شريطة أن يكون كلا التطبيقين قيد التشغيل.
تشمل الفوائد الرئيسية ما يلي:
- النشر المستقل: يمكن للفرق تحديث ونشر واجهتها الأمامية الصغيرة المحددة دون لمس التطبيق المضيف.
- التبعيات المشتركة: يمكن مشاركة المكتبات الحرجة مثل React أو Redux بين التطبيقات لتقليل حجم الحزمة وضمان الاتساق.
- استقلالية التقنية: بينما يُعد Webpack الأداة الأساسية، يسمح البروتوكول بالتكامل مع أنظمة البناء الأخرى، على الرغم من أن Webpack يقدم أكثر التنفيذات نضجاً.
إعداد التطبيقات المضيف والبعيد
لتوضيح توحيد الوحدات، لنفترض سيناريو يتضمن تطبيقين:
- التطبيق المضيف (Host App): التطبيق القشري الرئيسي الذي ينسق واجهة المستخدم.
- التطبيق البعيد (Remote App): وحدة محددة بالميزات (مثل لوحة تحكم المستخدم أو مكون الدردشة) التي سيتم استهلاكها بواسطة التطبيق المضيف.
الخطوة 1: تعريض الوحدات في التطبيق البعيد
أولاً، نحتاج إلى تكوين التطبيق البعيد لتعريض مكوناته. في ملف webpack.config.js للتطبيق البعيد، نستورد ModuleFederationPlugin ونحدد حقل exposes.
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { ModuleFederationPlugin } = require('webpack').container;
module.exports = {
// ... other config
plugins: [
new ModuleFederationPlugin({
name: 'remoteApp',
filename: 'remoteEntry.js',
exposes: {
'./UserProfile': './src/components/UserProfile',
},
shared: {
react: { singleton: true, requiredVersion: '^18.0.0' },
'react-dom': { singleton: true, requiredVersion: '^18.0.0' }
}
}),
new HtmlWebpackPlugin({
template: './public/index.html'
})
]
};
هنا، نقوم بتعريض ./UserProfile. نقوم أيضاً بتكوين التبعيات shared. من خلال تعيين singleton: true، نضمن تحميل نسخة واحدة فقط من React عبر كلا التطبيقين، مما يمنع عدم تطابق الحالة ويقلل من حجم الحمولة.
الخطوة 2: استهلاك الوحدات في التطبيق المضيف
بعد ذلك، نقوم بتكوين التطبيق المضيف لاستهلاك الوحدة المعروضة. نضيف تكوين remote في ModuleFederationPlugin الخاص بالتطبيق المضيف.
new ModuleFederationPlugin({
name: 'hostApp',
remotes: {
remoteApp: 'remoteApp@http://localhost:3001/remoteEntry.js',
},
shared: {
react: { singleton: true, requiredVersion: '^18.0.0' },
'react-dom': { singleton: true, requiredVersion: '^18.0.0' }
}
})
يخبر حقل remotes Webpack بالعثور على ملف الإدخال البعيد. يمكنك استخدام عنوان URL ثابت أو نقطة نهاية ديناميكية تعيد التكوين بناءً على البيئة. لاحظ أن تكوين shared يجب أن يتطابق مع التطبيق البعيد لضمان التوافق.
الاستيراد الديناميكي في React
مع وجود التكوين، يمكنك استيراد المكون البعيد ديناميكياً باستخدام واجهة برمجة التطبيقات System.import (التي يتم توفيرها بواسطة Webpack) أو الاستيرادات الديناميكية القياسية. إليك مثال في مكون React:
import React, { lazy, Suspense } from 'react';
// Lazily load the remote component
const UserProfile = lazy(() => import('remoteApp/UserProfile'));
const HostComponent = () => {
return (
Loading... }>