مدونة د.خلدون عرفة للروبوتيك

Font size: +

plugin manager

photo_2026-01-02_21-56-38

عند بناء تطبيقات برمجية قابلة للنمو، تظهر تحديات حقيقية تتعلق بكيفية إضافة ميزات جديدة دون الإضرار باستقرار النظام أو تعقيد الكود الأساسي.
كلما كبر المشروع، أصبح تعديل النواة (Core) مباشرةً أمراً محفوفاً بالمخاطر، خصوصاً إذا كان التطبيق يستخدم من قِبل عدد كبير من المستخدمين أو يتم تطويره من قِبل أكثر من فريق.

من هنا تنشأ الحاجة إلى بنية الإضافات (Plugin Architecture)، ويأتي مدير الإضافات (Plugin Manager) ليكون القلب الذي ينظم هذه البنية.

في هذا المقال سنشرح:

  • المفهوم النظري لمدير الإضافات
  • سبب أهميته من الناحية المعمارية
  • تطبيقه عملياً في الخلفية باستخدام Node.js
  • تطبيق الفكرة نفسها في الواجهة الأمامية باستخدام React 

ما هو Plugin Manager؟ 

مدير الإضافات هو مكوّن مركزي داخل النظام مسؤول عن:

  • اكتشاف الإضافات المتوفرة
  • قراءة ملفات تعريفها (Manifest)
  • التحقق من حالتها (مفعّلة أو معطّلة)
  • تحميلها وتسجيلها داخل التطبيق

الفكرة الأساسية هي:

النواة لا تعرف تفاصيل الإضافة، بل تعرف فقط كيف تستدعيها.

هذا الفصل يحقق:

  • عزلاً بين المكونات
  • سهولة التوسعة
  • أماناً واستقراراً أعلى 

لماذا نحتاج إلى Plugin Architecture؟ 

بدون نظام إضافات:

  • كل ميزة جديدة = تعديل مباشر في النواة
  • تداخل في المسؤوليات
  • صعوبة الاختبار
  • زيادة احتمالية الأخطاء
مع نظام إضافات:
  • النواة تبقى بسيطة وثابتة
  • كل ميزة موجودة في وحدة مستقلة
  • إمكانية التفعيل والتعطيل بسهولة
  • قابلية التطوير على المدى الطويل 

الجزء الأول: التطبيق العملي في الخلفية (Backend)

سننشئ خادماً بسيطاً باستخدام Node.js وExpress:

  • كل إضافة تضيف مسار HTTP خاص بها
  • النواة لا تعرف هذه المسارات مسبقاً
  • مدير الإضافات هو من يقوم بتحميلها 

هيكل المشروع

simple-plugin-demo/

    core/

        server.js ← main Express server

        pluginManager.js ← plugin loader/registry

    plugins/

        hello/

            manifest.json

            plugin.js

        time/

            manifest.json

            plugin.js

    package.json 

دور ملف Manifest.json

كل إضافة تحتوي على ملف manifest.json يعرّفها:

{

    "name": "hello",
    
    "displayName": "Hello Plugin",
    
    "version": "1.0.0",
    
    "enabled": true

} 

هذا الملف هو عقد رسمي بين الإضافة والنظام، ومن خلاله:

  • يعرف النظام اسم الإضافة
  • إصدارها
  • حالتها

ويمكن مستقبلاً إضافة:

  • صلاحيات
  • إعدادات
  • توافق الإصدارات 

كود الإضافة (plugin.js)

كل إضافة يجب أن توفّر نقطة دخول موحدة: 

// plugins/hello/plugin.js

module.exports = {
  register(app) {
    app.get("/hello", (req, res) => {
      res.json({ message: "Hello from Hello Plugin!" });
    });
  }
};
 

هذه الدالة تسمح للإضافة:

  • بالوصول إلى التطبيق
  • بتسجيل مساراتها الخاصة
  • دون تعديل أي سطر في النواة 

مدير الإضافات: القلب الحقيقي للنظام 

// core/pluginManager.js
const fs = require("fs");
const path = require("path");

function loadPlugins(app) {
  const pluginsDir = path.join(__dirname, "..", "plugins");
  const pluginFolders = fs.readdirSync(pluginsDir);

  const registry = [];

  pluginFolders.forEach((folder) => {
    const pluginPath = path.join(pluginsDir, folder);
    const manifestPath = path.join(pluginPath, "manifest.json");
    const pluginFilePath = path.join(pluginPath, "plugin.js");

    if (!fs.existsSync(manifestPath) || !fs.existsSync(pluginFilePath)) {
      console.warn(`Skipping ${folder}: missing manifest or plugin.js`);
      return;
    }

    const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));

    if (manifest.enabled === false) {
      console.log(`Plugin ${manifest.name} is disabled, skipping.`);
      return;
    }

    const pluginModule = require(pluginFilePath);

    if (typeof pluginModule.register !== "function") {
      console.warn(`Plugin ${manifest.name} has no register() function`);
      return;
    }

    // Let the plugin attach its routes to the app
    pluginModule.register(app);

    // Add to registry
    registry.push({
      name: manifest.name,
      displayName: manifest.displayName,
      version: manifest.version
    });

    console.log(`Loaded plugin: ${manifest.name}`);
  });

  return registry;
}

module.exports = { loadPlugins };
 

حيث ان دور مدير الإضافات هو:

  • الاكتشاف
  • التحقق
  • التحميل
  • التسجيل
  • بناء سجل داخلي (Registry) 

السجل (Registry) ولماذا هو مهم؟ 

السجل هو قائمة تحتوي على الإضافات التي تم تحميلها بنجاح، ويُستخدم لـ:

  • عرض الإضافات المتاحة
  • التشخيص
  • بناء واجهة إدارة لاحقاً
  • دعم التفعيل والتعطيل الديناميكي 

الخادم الرئيسي 

النواة تقوم فقط بـ:

  • إنشاء الخادم
  • استدعاء مدير الإضافات
  • تشغيل التطبيق 
// core/server.js
const express = require("express");
const { loadPlugins } = require("./pluginManager");

const app = express();

// Load all plugins and keep registry
const pluginRegistry = loadPlugins(app);

// Simple route to see which plugins are loaded
app.get("/plugins", (req, res) => {
  res.json(pluginRegistry);
});

const PORT = 3000;
app.listen(PORT, () => {
  console.log(`Server running on http://localhost:${PORT}`);
});
 

وهكذا عندما تقوم بتشغيل السيرفر:
node core/server.js
ستستطيع الوصول الى:

  • http://localhost:3000/hello → from Hello Plugin
  • http://localhost:3000/time → from Time Plugin
  • http://localhost:3000/plugins → see plugin registry
كلها أضيفت دون تعديل النواة.

الجزء الثاني: نفس الفكرة في الواجهة الأمامية (Frontend)

رغم اختلاف البيئة، إلا أن الفكرة المعمارية واحدة.  

هيكل مشروع الواجهة

simple-plugin-frontend/

    src/

        core/

            App.jsx ← main UI

            PluginManager.js ← UI plugin loader/registry

        extensions/

            HelloUi/

                manifest.ui.json

                HelloPlugin.jsx

            TimeUi/

                manifest.ui.json

                TimePlugin.jsx

    package.json 

دور ملف manifest.json

كل إضافة تحتوي على ملف manifest.json يعرّفها: 

// extensions/HelloUi/manifest.ui.json
{
  "name": "hello-ui",
  "displayName": "Hello UI Plugin",
  "enabled": true
}
 

إضافات الواجهة

كل إضافة:

  • تحتوي على مكوّن React
  • لها ملف تعريف
  • لا تعرف أين ستُعرض 
مثال لاضافة hello:
// extensions/HelloUi/HelloPlugin.jsx

export default function HelloPlugin() {
  return (
    <div>
      <h2>Hello Plugin</h2>
      <p>This content comes from the <strong>Hello UI Plugin</strong>.</p>
    </div>
  );
}
 

مدير الإضافات في الواجهة 

دوره:

  • الاحتفاظ بسجل الإضافات
  • تحديد المفعّل منها
  • تزويد النواة بالمكوّن المناسب 
// src/core/PluginManager.js

import HelloPlugin from "../extensions/HelloUi/HelloPlugin";
import TimePlugin from "../extensions/TimeUi/TimePlugin";

// A simple "plugin registry" for the UI
// In a real app, this can be generated from manifests or loaded dynamically.
const plugins = [
  {
    name: "hello-ui",
    displayName: "Hello UI Plugin",
    enabled: true,
    component: HelloPlugin,
  },
  {
    name: "time-ui",
    displayName: "Time UI Plugin",
    enabled: true,
    component: TimePlugin,
  },
];

export function getEnabledPlugins() {
  return plugins.filter((p) => p.enabled);
}
 

التطبيق الرئيسي 

النواة:

  • تعرض قائمة الإضافات
  • تسمح للمستخدم باختيار إحداها
  • تقوم بعرض المكوّن دون معرفة تفاصيله

وهذا هو جوهر الفصل بين النواة والإضافات. 

// src/core/App.jsx

import React, { useState } from "react";
import { getEnabledPlugins } from "./PluginManager";

export default function App() {
  const availablePlugins = getEnabledPlugins();

  // Select the first plugin by default (if any)
  const [selectedPluginName, setSelectedPluginName] = useState(
    availablePlugins[0]?.name || null
  );

  const selectedPlugin = availablePlugins.find(
    (p) => p.name === selectedPluginName
  );

  const SelectedComponent = selectedPlugin ? selectedPlugin.component : null;

  return (
    <div style={{ display: "flex", height: "100vh", fontFamily: "sans-serif" }}>
      {/* Sidebar */}
      <div
        style={{
          width: "200px",
          borderRight: "1px solid #ccc",
          padding: "10px",
          boxSizing: "border-box",
        }}
      >
        <h3>Plugins</h3>
        <ul style={{ listStyle: "none", padding: 0 }}>
          {availablePlugins.map((plugin) => (
            <li key={plugin.name} style={{ marginBottom: "8px" }}>
              <button
                style={{
                  width: "100%",
                  padding: "6px 8px",
                  cursor: "pointer",
                  backgroundColor:
                    plugin.name === selectedPluginName ? "#007bff" : "#f0f0f0",
                  color:
                    plugin.name === selectedPluginName ? "#fff" : "#000",
                  border: "none",
                  borderRadius: "4px",
                }}
                onClick={() => setSelectedPluginName(plugin.name)}
              >
                {plugin.displayName}
              </button>
            </li>
          ))}
        </ul>
      </div>

      {/* Content area */}
      <div style={{ flex: 1, padding: "20px" }}>
        {SelectedComponent ? (
          <SelectedComponent />
        ) : (
          <p>No plugin selected or no plugins available.</p>
        )}
      </div>
    </div>
  );
}
 

مدير الإضافات ليس مجرد كود، بل هو قرار معماري يحدد مستقبل المشروع.

إذا كنت تطور نظاماً:

  • قابلاً للنمو
  • متعدد الميزات
  • طويل الأمد

فإن اعتماد Plugin Architecture يمنحك مرونة واستقراراً يصعب تحقيقهما بغير ذلك. 

تحميل ملفات المشروع

يمكنك تحميل ملفات المشروع كاملة لتجربته والاتطلاع عليه من هنا:

https://drive.google.com/file/d/1VhIrBxXeqHWEAD7fHOaH5bAlBZm6QIwu/view?usp=sharing

مشاهدة شرح تطبيق المشروع

يمكنك مشاهدة شرح كامل لتنفيذ المشروع على قناتنا على يوتيوب من الرابط التالي:

https://youtu.be/_Gq4Cpaa57E

حول المقالة

  • متوسط
×
Stay Informed

When you subscribe to the blog, we will send you an e-mail when there are new updates on the site so you wouldn't miss them.

Workshop in BENELUX Championship – FIRST Tech Chal...
System Architecture

Related Posts

 

Comments

No comments made yet. Be the first to submit a comment
Already Registered? Login Here
Thursday, 25 June 2026