توثيق شامل لبيئة عمل Electron.js

هذا الدليل يقدم توثيقًا تفصيليًا ومتعمقًا لبيئة عمل Electron بالكامل، بدءًا من المفاهيم الأساسية وحتى أنماط التطوير المتقدمة. نهدف إلى شرح كل جانب من جوانب تطوير تطبيقات سطح المكتب باستخدام تقنيات الويب، مع توضيح البدائل المتاحة والممارسات الموصى بها في كل خطوة.

النظام البيئي لـ Electron

قبل الغوص في تفاصيل التطوير، من المهم فهم النظام البيئي الكامل لـ Electron وكيف يتكامل مع تقنيات الويب والأدوات الأخرى.

المكونات الأساسية

Electron مبني على مكونات قوية تشكل أساس النظام البيئي بأكمله.

  • Chromium: لعرض واجهات المستخدم
  • Node.js: للوصول إلى نظام الملفات والشبكة
  • V8: محرك JavaScript من Google

أدوات النظام البيئي

مجموعة متكاملة من الأدوات التي تسهل تطوير وبناء وصيانة تطبيقات Electron.

  • electron-builder: لتعبئة التطبيقات
  • electron-forge: منصة تطوير شاملة
  • electron-updater: للتحديثات التلقائية

أطر العمل المتكاملة

يمكن دمج أطر عمل واجهة المستخدم الشائعة بسهولة مع Electron.

  • React: مكتبة واجهات مستخدم تفاعلية
  • Vue.js: إطار عمل تدريجي
  • Angular: منصة شاملة لبناء التطبيقات

لماذا Electron مقارنة بالبدائل الأخرى؟

تتميز Electron بالجمع بين سهولة تطوير واجهات الويب وقوة تطبيقات سطح المكتب الأصلية. عند المقارنة بالبدائل، نجد:

الخاصية Electron NW.js Tauri Flutter Desktop
لغات البرمجة JavaScript/TypeScript JavaScript Rust + JavaScript Dart
حجم التطبيق كبير (> 100MB) كبير (> 100MB) صغير (< 10MB) متوسط (~ 30MB)
سهولة التطوير عالية جدًا عالية متوسطة متوسطة
الأداء جيد جيد ممتاز ممتاز
الدعم المجتمعي ممتاز جيد متنامي جيد

بنية نظام Electron

من الضروري فهم البنية الأساسية لتطبيقات Electron قبل الغوص في تفاصيل التطوير. هذه البنية تحدد كيفية تنظيم الكود وتفاعل المكونات المختلفة.

النموذج متعدد العمليات

Electron Architecture

تطبيق Electron يعتمد على نموذج متعدد العمليات مستوحى من Chromium، وينقسم إلى عمليتين رئيسيتين:

  1. 1
    العملية الرئيسية (Main Process):

    العملية الأساسية التي تتحكم في دورة حياة التطبيق. تبدأ عند تشغيل تطبيقك وتنتهي عند إغلاقه. مسؤولة عن إنشاء النوافذ والتفاعل مع نظام التشغيل.

  2. 2
    عملية العرض (Renderer Process):

    كل نافذة في تطبيقك تشغل عملية عرض منفصلة. تستخدم Chromium لعرض واجهة المستخدم وتنفيذ كود JavaScript في بيئة شبيهة بمتصفح الويب.

ملاحظة مهمة: لكل من هذه العمليات قدرات ومسؤوليات مختلفة:

  • العملية الرئيسية تستطيع الوصول إلى وحدات Node.js الكاملة ونظام التشغيل
  • عمليات العرض تركز على تقديم واجهة المستخدم والتفاعل مع المستخدم
  • التواصل بين العمليتين يتم عبر آلية IPC (الاتصال بين العمليات)

بدائل أنماط البنية:

هناك عدة أنماط معمارية يمكن اتباعها عند تطوير تطبيقات Electron:

  • النمط التقليدي: العملية الرئيسية + عملية عرض واحدة أو أكثر، كما وصفنا أعلاه.
  • نمط Sandboxed Renderer: عمليات عرض معزولة تمامًا بدون وصول مباشر إلى Node.js، مما يحسن الأمان.
  • نمط العملية الخدمية: إضافة عملية خلفية ثالثة للمهام الثقيلة للحفاظ على استجابة واجهة المستخدم.

مكونات النظام الرئيسية

العملية الرئيسية

تعمل في خلفية التطبيق وتتحكم بدورة حياته. تنفذ هذه العملية ملف main.js الذي يحدد كيفية تشغيل التطبيق بأكمله.

  • • إنشاء وإدارة نوافذ التطبيق
  • • التعامل مع أحداث دورة حياة التطبيق
  • • التفاعل مع نظام التشغيل
  • • إدارة قوائم النظام والإشعارات
  • • الوصول للنظام الملفات والشبكة

عملية العرض

كل نافذة هي عملية منفصلة تعرض محتوى الويب. تنفذ هذه العملية ملفات HTML وCSS وJavaScript التي تشكل واجهة المستخدم.

  • • عرض واجهة المستخدم (HTML/CSS)
  • • التفاعل مع إدخال المستخدم
  • • تنفيذ تأثيرات الرسوم المتحركة والتحولات
  • • التواصل مع العملية الرئيسية عبر IPC
  • • تنفيذ منطق واجهة المستخدم

الاتصال بين العمليات (IPC)

آلية لتبادل الرسائل والبيانات بين العملية الرئيسية وعمليات العرض. تعتمد على نمط المرسل/المستمع.

  • • إرسال الرسائل بين العمليات
  • • نقل البيانات
  • • استدعاء الوظائف عن بُعد
  • • الاتصال المتزامن وغير المتزامن
  • • نمط الطلب/الاستجابة

سكريبت التحميل المسبق

سكريبت JavaScript يتم تنفيذه قبل تحميل محتوى الويب في عملية العرض. يعمل كجسر بين العمليتين.

  • • إنشاء واجهة API آمنة
  • • توفير ميزات Node.js بطريقة محكومة
  • • عزل السياق لأمان أفضل
  • • إعداد البيئة الأولية للصفحة
  • • التحكم في ما يمكن الوصول إليه من عملية العرض

تطوير تطبيقات Electron

سنغطي الآن عملية تطوير تطبيق Electron من البداية، مع شرح كل خطوة وتقديم أمثلة مفصلة للكود المستخدم.

إعداد بيئة التطوير

قبل البدء في التطوير، تحتاج إلى إعداد بيئة العمل المناسبة. هذه البيئة تتكون من مجموعة من الأدوات والبرامج التي ستساعدك على كتابة وتشغيل واختبار تطبيق Electron.

المتطلبات الأساسية

Node.js

منصة JavaScript التي تعمل خارج المتصفح. Electron يعتمد على Node.js، لذا ستحتاج إلى تثبيته أولًا.

# التحقق من وجود Node.js
node --version
# يجب أن يكون الإصدار 14.x أو أعلى
npm أو yarn

مدير حزم JavaScript الذي يساعدك على تثبيت وإدارة تبعيات المشروع.

# التحقق من وجود npm
npm --version
# أو التحقق من وجود yarn
yarn --version
Git

نظام التحكم بالإصدارات، مفيد جدًا لإدارة مشروعك والتعاون مع الآخرين.

# التحقق من وجود Git
git --version

تثبيت Electron وإنشاء مشروع جديد

هناك عدة طرق لبدء مشروع Electron جديد. سنشرح الطريقتين الأكثر شيوعًا:

  1. 1. البدء من الصفر: إنشاء مشروع Node.js وإضافة Electron إليه.
  2. 2. استخدام قالب: استخدام قالب جاهز مثل Electron Forge أو Electron Boilerplate.
الطريقة 1: البدء من الصفر
# إنشاء مجلد للمشروع
mkdir my-electron-app
cd my-electron-app

# تهيئة مشروع Node.js
npm init -y

# تثبيت Electron كتبعية تطوير
npm install electron --save-dev

شرح الأوامر:

  • mkdir my-electron-app: إنشاء مجلد جديد للمشروع
  • cd my-electron-app: الانتقال إلى المجلد الجديد
  • npm init -y: تهيئة مشروع Node.js مع الإعدادات الافتراضية (سينشئ ملف package.json)
  • npm install electron --save-dev: تثبيت Electron كتبعية تطوير

بعد ذلك، تحتاج إلى تعديل ملف package.json لإضافة سكريبت تشغيل لتطبيقك:

{
  "name": "my-electron-app",
  "version": "1.0.0",
  "description": "تطبيق Electron تجريبي",
  "main": "main.js",
  "scripts": {
    "start": "electron .",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [
    "electron"
  ],
  "author": "اسمك",
  "license": "MIT",
  "devDependencies": {
    "electron": "^29.0.0"
  }
}

التغييرات الرئيسية:

  • • تحديد "main": "main.js" كنقطة دخول التطبيق
  • • إضافة سكريبت "start": "electron ." لتشغيل التطبيق
  • • التبعيات تتضمن الآن "electron": "^29.0.0" (أو الإصدار الذي قمت بتثبيته)
الطريقة 2: استخدام قالب جاهز (Electron Forge)
# تثبيت Electron Forge CLI عالميًا
npm install -g @electron-forge/cli

# إنشاء مشروع جديد
electron-forge init my-forge-app

# الانتقال للمشروع الجديد
cd my-forge-app

# تشغيل التطبيق
npm start

لماذا استخدام Electron Forge؟ يوفر Electron Forge مجموعة من الأدوات التي تبسط عملية تطوير وبناء وتوزيع تطبيقات Electron. يتضمن:

  • • بنية مشروع منظمة
  • • سكريبتات جاهزة للتطوير والبناء والنشر
  • • دعم للوحدات النمطية والتحميل الكسول
  • • أدوات تعبئة وتوزيع متكاملة

بدائل أخرى للقوالب:

  • electron-react-boilerplate: قالب جاهز يدمج React مع Electron
  • electron-vue: لمن يفضل استخدام Vue.js
  • angular-electron: قالب يدمج Angular مع Electron

بنية المشروع

بنية المشروع الأساسية لتطبيق Electron بسيط تتكون من الملفات التالية:

my-electron-app/
├── package.json          # معلومات المشروع والتبعيات
├── main.js               # ملف العملية الرئيسية
├── preload.js            # سكريبت التحميل المسبق
├── renderer/
│   ├── index.html        # صفحة الويب الرئيسية
│   ├── styles.css        # أنماط CSS
│   └── renderer.js       # سكريبت جانب العميل
├── assets/               # الصور والأيقونات والموارد الأخرى
└── node_modules/         # التبعيات (ينشأ تلقائيًا)
الملفات الرئيسية
  • main.js نقطة الدخول الرئيسية للتطبيق. ينفذ في العملية الرئيسية ويتحكم بدورة حياة التطبيق.
  • preload.js سكريبت ينفذ قبل تحميل صفحات الويب. يعمل كجسر بين عملية العرض والعملية الرئيسية.
  • renderer/index.html صفحة HTML الرئيسية التي تعرض واجهة المستخدم.
  • renderer/renderer.js سكريبت JavaScript ينفذ في عملية العرض ويتحكم بمنطق واجهة المستخدم.
الملفات الإضافية للمشاريع المتقدمة
  • forge.config.js إعدادات Electron Forge (إذا كنت تستخدمه).
  • webpack.config.js إعدادات Webpack لتجميع الكود (في حالة استخدام Webpack).
  • tsconfig.json إعدادات TypeScript (إذا كنت تستخدم TypeScript بدلاً من JavaScript).
  • .eslintrc.js إعدادات ESLint لفحص جودة الكود.

إنشاء تطبيق Electron بسيط

سنقوم الآن بإنشاء تطبيق Electron بسيط خطوة بخطوة، مع شرح كل جزء من الكود ووظيفته.

الخطوة 1: إنشاء ملف main.js

// استيراد الوحدات المطلوبة من Electron
const { app, BrowserWindow } = require('electron')
const path = require('path')

// حفظ مرجع للنافذة لمنع إغلاقها بواسطة جامع النفايات
let mainWindow

// دالة إنشاء النافذة الرئيسية
function createWindow() {
  // إنشاء نافذة متصفح جديدة
  mainWindow = new BrowserWindow({
    width: 800,                  // عرض النافذة بالبكسل
    height: 600,                 // ارتفاع النافذة بالبكسل
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'),  // سكريبت التحميل المسبق
      contextIsolation: true,    // تفعيل عزل السياق
      nodeIntegration: false     // تعطيل تكامل Node في عملية العرض
    }
  })

  // تحميل ملف HTML في النافذة
  mainWindow.loadFile(path.join(__dirname, 'renderer', 'index.html'))
  
  // فتح أدوات المطور (تعليق هذا السطر في الإنتاج)
  // mainWindow.webContents.openDevTools()

  // معالجة حدث إغلاق النافذة
  mainWindow.on('closed', () => {
    mainWindow = null
  })
}

// عند استعداد التطبيق، قم بإنشاء النافذة
app.whenReady().then(() => {
  createWindow()
  
  // في macOS، من الشائع إعادة إنشاء نافذة في التطبيق
  // عندما ينقر المستخدم على أيقونة الـ dock والتطبيق ليس له نوافذ مفتوحة
  app.on('activate', () => {
    if (BrowserWindow.getAllWindows().length === 0) {
      createWindow()
    }
  })
})

// إغلاق التطبيق عندما تُغلق جميع النوافذ (باستثناء macOS)
app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit()
  }
})
شرح الكود:
  1. استيراد الوحدات:

    نستورد app و BrowserWindow من Electron. وحدة app تتحكم في دورة حياة التطبيق، بينما BrowserWindow تستخدم لإنشاء وإدارة نوافذ التطبيق. نستورد أيضًا وحدة path من Node.js للتعامل مع مسارات الملفات.

  2. تعريف المتغيرات العامة:

    let mainWindow يحفظ مرجعًا للنافذة الرئيسية. نحتفظ بهذا المرجع خارج الدوال لمنع إزالته بواسطة جامع النفايات في JavaScript.

  3. دالة createWindow:

    تقوم بإنشاء نافذة جديدة وتحديد خصائصها مثل:

    • width و height: أبعاد النافذة
    • webPreferences: إعدادات عملية العرض، وتشمل:
      • - preload: مسار سكريبت التحميل المسبق
      • - contextIsolation: تفعيل عزل السياق لزيادة الأمان
      • - nodeIntegration: تعطيل الوصول المباشر لوحدات Node.js من عملية العرض
  4. تحميل ملف HTML:

    mainWindow.loadFile(...) يقوم بتحميل ملف HTML في النافذة. يمكن أيضًا استخدام loadURL لتحميل URL خارجي.

  5. دورة حياة التطبيق:

    نستخدم أحداث app.whenReady، app.on('window-all-closed') و app.on('activate') للتحكم في دورة حياة التطبيق عبر أنظمة التشغيل المختلفة.

ملاحظة عن سلوك أنظمة التشغيل المختلفة:

  • • في Windows و Linux، عادة ما يتم إغلاق التطبيق عند إغلاق جميع النوافذ.
  • • في macOS، تبقى التطبيقات نشطة عادة حتى عند إغلاق جميع النوافذ، إلى أن يخرج المستخدم صراحة من التطبيق (Cmd+Q).
خيارات وإعدادات أخرى مفيدة:
// خيارات إضافية عند إنشاء BrowserWindow
mainWindow = new BrowserWindow({
  // الخيارات الأساسية
  width: 800,
  height: 600,
  
  // خيارات إضافية للشكل والمظهر
  title: 'تطبيقي الأول',         // عنوان النافذة
  icon: path.join(__dirname, 'assets/icon.png'),  // أيقونة النافذة
  backgroundColor: '#f5f5f5',    // لون خلفية النافذة (مفيد لتجنب الوميض)
  center: true,                  // توسيط النافذة عند الفتح
  resizable: true,               // السماح بتغيير حجم النافذة
  frame: true,                   // إظهار إطار النافذة
  fullscreenable: true,          // السماح بوضع ملء الشاشة
  
  // الحد الأدنى والأقصى للأبعاد
  minWidth: 400,
  minHeight: 300,
  maxWidth: 1200,
  maxHeight: 900,
  
  // خيارات أمان وأداء متقدمة
  webPreferences: {
    preload: path.join(__dirname, 'preload.js'),
    contextIsolation: true,
    nodeIntegration: false,
    sandbox: true,                // تفعيل الصندوق الرملي لعزل إضافي
    spellcheck: true,             // تفعيل التدقيق الإملائي
    enableRemoteModule: false,    // تعطيل الوحدة البعيدة (متوافق مع إصدارات أقدم)
  }
})

يوفر Electron العديد من الخيارات لتخصيص النوافذ. يمكنك اختيار ما يناسب احتياجات تطبيقك.

الخطوة 2: إنشاء ملف preload.js

// preload.js
const { contextBridge, ipcRenderer } = require('electron')

// كشف معلومات عن الإصدارات للصفحة
contextBridge.exposeInMainWorld('electronInfo', {
  // معلومات عن الإصدار
  versions: {
    node: () => process.versions.node,
    chrome: () => process.versions.chrome,
    electron: () => process.versions.electron
  },
  
  // واجهة برمجة للتواصل مع العملية الرئيسية
  app: {
    // إرسال رسالة للعملية الرئيسية
    sendMessage: (channel, data) => {
      // السماح بقنوات محددة فقط لأسباب أمنية
      const validChannels = ['toMain', 'app-info']
      if (validChannels.includes(channel)) {
        ipcRenderer.send(channel, data)
      }
    },
    
    // تلقي رد من العملية الرئيسية
    onReceiveMessage: (channel, func) => {
      const validChannels = ['fromMain', 'app-info-response']
      if (validChannels.includes(channel)) {
        // حذف معامل الحدث IPC الثاني (event) من الرد
        ipcRenderer.on(channel, (event, ...args) => func(...args))
      }
    },
    
    // استدعاء دالة في العملية الرئيسية وانتظار النتيجة (Promise)
    invoke: (channel, data) => {
      const validChannels = ['dialog:openFile', 'getData']
      if (validChannels.includes(channel)) {
        return ipcRenderer.invoke(channel, data)
      }
      return Promise.reject(new Error('قناة غير مسموح بها'))
    }
  }
})
شرح الكود:
  1. الغرض من preload.js:

    ملف preload.js يعمل كجسر آمن بين عملية العرض والعملية الرئيسية. يتم تحميله قبل صفحة الويب، ويمكنه الوصول إلى وحدات Node.js و Electron مع الحفاظ على أمان التطبيق.

  2. استخدام contextBridge:

    نستخدم contextBridge.exposeInMainWorld لكشف واجهة برمجة آمنة لعملية العرض. هذا يعني أن عملية العرض يمكنها استخدام window.electronInfo للوصول للواجهة المكشوفة.

  3. واجهة برمجة آمنة:

    نكشف جزأين رئيسيين:

    • versions: معلومات عن إصدارات Node.js و Chrome و Electron
    • app: واجهة للتواصل مع العملية الرئيسية، تتضمن:
      • - sendMessage: إرسال رسائل للعملية الرئيسية
      • - onReceiveMessage: الاستماع للرسائل من العملية الرئيسية
      • - invoke: استدعاء دالة في العملية الرئيسية وانتظار النتيجة
  4. التحقق من أمان القنوات:

    لاحظ استخدام validChannels. هذا يمنع الكود في عملية العرض من إرسال رسائل عبر قنوات عشوائية، مما يعزز الأمان.

تحذير أمني مهم:

تجنب استخدام nodeIntegration: true أو contextIsolation: false في البيئات الإنتاجية. هذه الإعدادات تجعل تطبيقك عرضة لهجمات حقن السكريبت. بدلاً من ذلك، استخدم دائمًا contextBridge كما في المثال أعلاه.

لماذا contextBridge أفضل من البدائل؟

قبل Electron 12، كان المطورون يستخدمون طرقًا أخرى للتواصل بين العمليات:

النهج القديم (غير آمن)
// في webPreferences
nodeIntegration: true,
contextIsolation: false

// في عملية العرض مباشرة
const { ipcRenderer } = require('electron')
ipcRenderer.send('some-channel', data)

المشكلة: يمكن لأي سكريبت في الصفحة الوصول إلى Node.js، مما يشكل خطرًا أمنيًا.

النهج الحديث (آمن)
// في webPreferences
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true,
nodeIntegration: false

// في عملية العرض
window.electronInfo.app.sendMessage('toMain', data)

المزايا: الصفحة لا تستطيع الوصول مباشرة إلى Node.js، ويمكن التحكم في ما هو متاح بدقة.

الخطوة 3: إنشاء ملفات واجهة المستخدم

index.html - الصفحة الرئيسية
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
  <meta charset="UTF-8">
  <!-- أمان: سياسة أمان المحتوى -->
  <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>تطبيق Electron</title>
  <link rel="stylesheet" href="styles.css">
</head>
<body>
  <div class="container">
    <header>
      <h1>مرحباً بك في تطبيق Electron</h1>
      <p class="subtitle">تطبيق سطح مكتب مبني بتقنيات الويب</p>
    </header>
    
    <main>
      <section class="info-section">
        <h2>معلومات النظام</h2>
        <div class="info-grid">
          <div class="info-item">
            <span class="info-label">إصدار Electron:</span>
            <span class="info-value" id="electron-version">جاري التحميل...</span>
          </div>
          <div class="info-item">
            <span class="info-label">إصدار Chrome:</span>
            <span class="info-value" id="chrome-version">جاري التحميل...</span>
          </div>
          <div class="info-item">
            <span class="info-label">إصدار Node.js:</span>
            <span class="info-value" id="node-version">جاري التحميل...</span>
          </div>
        </div>
      </section>
      
      <section class="actions-section">
        <h2>اختبار التفاعل</h2>
        <div class="actions-grid">
          <button id="send-message-btn" class="action-button">إرسال رسالة</button>
          <button id="invoke-action-btn" class="action-button">استدعاء أمر</button>
        </div>
        <div class="response-area">
          <p id="response-text">النتائج ستظهر هنا...</p>
        </div>
      </section>
    </main>
    
    <footer>
      <p>تم بناء هذا التطبيق باستخدام Electron و HTML و CSS و JavaScript</p>
    </footer>
  </div>
  
  <!-- تضمين ملف JavaScript الخاص بعملية العرض -->
  <script src="renderer.js"></script>
</body>
</html>
شرح هيكل ملف HTML:
  • سياسة أمان المحتوى (CSP):

    استخدام Content-Security-Policy يمنع هجمات حقن السكريبت (XSS) بتحديد مصادر البيانات المسموح بها. default-src 'self' تسمح فقط بتحميل الموارد من نفس المصدر، وscript-src 'self' تسمح فقط بتنفيذ السكريبتات من نفس المصدر.

  • هيكل المستند:

    استخدمنا تنسيق واضح مع قسم رئيسي <main> يحتوي على قسمين: قسم معلومات النظام وقسم الإجراءات التفاعلية.

  • العناصر الديناميكية:

    عناصر مثل id="electron-version" ستُملأ بواسطة سكريبت renderer.js باستخدام البيانات من preload.js.

styles.css - أنماط واجهة المستخدم
/* إعدادات عامة */
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Almarai', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
  background-color: #f5f5f5;
  color: #333;
  line-height: 1.6;
  direction: rtl;
}

.container {
  max-width: 800px;
  margin: 0 auto;
  padding: 20px;
}

/* العنوان والترويسة */
header {
  text-align: center;
  margin-bottom: 40px;
}

h1 {
  font-size: 2.5rem;
  margin-bottom: 10px;
  color: #2c3e50;
}

.subtitle {
  font-size: 1.2rem;
  color: #7f8c8d;
}

h2 {
  font-size: 1.5rem;
  margin-bottom: 20px;
  color: #2c3e50;
  border-bottom: 2px solid #00c853;
  padding-bottom: 10px;
}

/* أقسام المعلومات */
.info-section, .actions-section {
  background-color: white;
  border-radius: 8px;
  padding: 20px;
  margin-bottom: 30px;
  box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
}

.info-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: 15px;
}

.info-item {
  padding: 10px;
  border-radius: 4px;
  background-color: #f8f9fa;
  display: flex;
  flex-direction: column;
}

.info-label {
  font-weight: bold;
  font-size: 0.9rem;
  color: #7f8c8d;
  margin-bottom: 5px;
}

.info-value {
  font-family: monospace;
}

/* أقسام الإجراءات */
.actions-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 15px;
  margin-bottom: 20px;
}

.action-button {
  background-color: #00c853;
  color: white;
  border: none;
  border-radius: 4px;
  padding: 10px 15px;
  font-size: 0.9rem;
  cursor: pointer;
  transition: background-color 0.3s;
}

.action-button:hover {
  background-color: #00a844;
}

.response-area {
  padding: 15px;
  background-color: #f8f9fa;
  border-radius: 4px;
  min-height: 60px;
}

#response-text {
  font-family: monospace;
  white-space: pre-wrap;
}

/* التذييل */
footer {
  text-align: center;
  margin-top: 40px;
  color: #7f8c8d;
  font-size: 0.9rem;
}
شرح العناصر الرئيسية في CSS:
  • إعدادات الاتجاه:

    استخدمنا direction: rtl لتحديد اتجاه الصفحة من اليمين إلى اليسار للغة العربية.

  • تخطيط الشبكة (Grid Layout):

    استخدمنا display: grid لتنظيم عناصر المعلومات والأزرار بطريقة متجاوبة مع مختلف أحجام الشاشة.

  • التصميم المتجاوب:

    استخدمنا repeat(auto-fill, minmax(...)) في تعريف الشبكة لضمان تجاوب التخطيط مع مختلف أحجام الشاشة.

  • تأثيرات التفاعل:

    أضفنا transition و :hover للأزرار لتحسين تجربة المستخدم عند التفاعل.

renderer.js - منطق واجهة المستخدم
// renderer.js - كود يُنفذ في عملية العرض

// عند تحميل المستند - ننتظر تحميل الصفحة بالكامل
document.addEventListener('DOMContentLoaded', () => {
  // الحصول على مراجع للعناصر في الصفحة
  const electronVersionEl = document.getElementById('electron-version')
  const chromeVersionEl = document.getElementById('chrome-version')
  const nodeVersionEl = document.getElementById('node-version')
  const sendMessageBtn = document.getElementById('send-message-btn')
  const invokeActionBtn = document.getElementById('invoke-action-btn')
  const responseText = document.getElementById('response-text')
  
  // عرض معلومات الإصدارات - استخدام واجهة من preload.js
  electronVersionEl.textContent = window.electronInfo.versions.electron()
  chromeVersionEl.textContent = window.electronInfo.versions.chrome()
  nodeVersionEl.textContent = window.electronInfo.versions.node()
  
  // معالجة النقر على زر "إرسال رسالة"
  sendMessageBtn.addEventListener('click', () => {
    // إرسال رسالة للعملية الرئيسية
    window.electronInfo.app.sendMessage('toMain', {
      time: new Date().toLocaleString(),
      message: 'مرحباً من عملية العرض!'
    })
    
    responseText.textContent = 'تم إرسال الرسالة للعملية الرئيسية...'
  })
  
  // معالجة النقر على زر "استدعاء أمر"
  invokeActionBtn.addEventListener('click', async () => {
    try {
      // استدعاء أمر في العملية الرئيسية وانتظار النتيجة
      const result = await window.electronInfo.app.invoke('getData', {
        query: 'test-data'
      })
      
      // عرض النتيجة كـ JSON منسق
      responseText.textContent = JSON.stringify(result, null, 2)
    } catch (error) {
      responseText.textContent = `خطأ: ${error.message}`
    }
  })
  
  // الاستماع للرسائل من العملية الرئيسية
  window.electronInfo.app.onReceiveMessage('fromMain', (data) => {
    responseText.textContent = `تم استلام رد: ${JSON.stringify(data)}`
  })
  
  // طباعة رسالة تأكيد في وحدة التحكم
  console.log('تم تهيئة واجهة المستخدم بنجاح!')
})
شرح منطق JavaScript:
  • تهيئة واجهة المستخدم:

    استخدمنا DOMContentLoaded للتأكد من تحميل الصفحة بالكامل قبل تنفيذ الكود، ثم قمنا بجلب مراجع لعناصر DOM التي سنتفاعل معها.

  • عرض معلومات النظام:

    استخدمنا window.electronInfo.versions (الذي تم كشفه بواسطة preload.js) للوصول إلى معلومات إصدارات Electron و Chrome و Node.js.

  • التواصل مع العملية الرئيسية:

    استخدمنا الأساليب الثلاثة الرئيسية للتواصل مع العملية الرئيسية:

    1. 1. sendMessage: إرسال رسالة أحادية الاتجاه
    2. 2. invoke: استدعاء دالة وانتظار نتيجة (Promise)
    3. 3. onReceiveMessage: الاستماع للرسائل الواردة
بدائل وتقنيات متقدمة:
  • استخدام إطار عمل UI: بدلاً من JavaScript الأساسي، يمكنك استخدام إطار عمل مثل React أو Vue أو Angular.
  • مكتبات حالة: في التطبيقات الأكبر، يمكن استخدام مكتبات لإدارة الحالة مثل Redux أو Vuex.
  • TypeScript: للمشاريع الكبيرة، يمكن استخدام TypeScript بدلاً من JavaScript للحصول على أمان أنواع البيانات.

الخطوة 4: تحديث العملية الرئيسية للتواصل مع واجهة المستخدم

حتى الآن، قمنا بإعداد ملفات واجهة المستخدم وسكريبت التحميل المسبق، لكننا لم نضف الكود اللازم في العملية الرئيسية للتواصل معها. سنقوم الآن بتحديث ملف main.js لمعالجة الرسائل من واجهة المستخدم.

// إضافة هذا الكود في ملف main.js بعد استيراد الوحدات الأساسية

const { app, BrowserWindow, ipcMain, dialog } = require('electron')
const path = require('path')
const fs = require('fs')

// .... الكود السابق ....

// معالجة الرسائل من عملية العرض
ipcMain.on('toMain', (event, data) => {
  console.log('تم استلام رسالة من عملية العرض:', data)
  
  // الرد على عملية العرض
  event.sender.send('fromMain', {
    time: new Date().toLocaleString(),
    receivedMessage: data,
    reply: 'تم استلام رسالتك بنجاح!'
  })
})

// معالجة استدعاءات من عملية العرض
ipcMain.handle('getData', async (event, data) => {
  console.log('تم استدعاء الحصول على البيانات:', data)
  
  // تأخير صناعي لمحاكاة عملية طويلة
  await new Promise(resolve => setTimeout(resolve, 1000))
  
  // إرجاع بيانات
  return {
    success: true,
    timestamp: Date.now(),
    query: data.query,
    results: [
      { id: 1, name: 'العنصر الأول' },
      { id: 2, name: 'العنصر الثاني' },
      { id: 3, name: 'العنصر الثالث' }
    ]
  }
})

// معالجة استدعاء فتح ملف
ipcMain.handle('dialog:openFile', async () => {
  const { canceled, filePaths } = await dialog.showOpenDialog({
    properties: ['openFile'],
    filters: [
      { name: 'الصور', extensions: ['jpg', 'png', 'gif'] },
      { name: 'النصوص', extensions: ['txt', 'md'] },
      { name: 'كل الملفات', extensions: ['*'] }
    ]
  })
  
  if (canceled) {
    return null
  }
  
  const filePath = filePaths[0]
  
  // قراءة محتوى الملف إذا كان نصًا
  if (filePath.endsWith('.txt') || filePath.endsWith('.md')) {
    try {
      const content = fs.readFileSync(filePath, 'utf8')
      return { filePath, content }
    } catch (error) {
      return { filePath, error: error.message }
    }
  }
  
  return { filePath }
})
شرح التواصل بين العمليات:
  1. استيراد الوحدات الإضافية:

    أضفنا ipcMain للتعامل مع الاتصال بين العمليات و dialog لعرض مربعات حوار النظام و fs للتعامل مع الملفات.

  2. معالجة الرسائل:

    استخدمنا ipcMain.on للاستماع للرسائل من عملية العرض على قناة محددة ('toMain')، ثم الرد عليها باستخدام event.sender.send.

  3. معالجة الاستدعاءات:

    استخدمنا ipcMain.handle للاستماع لاستدعاءات من عملية العرض وإرجاع نتيجة (يمكن أن تكون وعد Promise).

  4. استخدام واجهات برمجة النظام:

    في مثال dialog:openFile، نستخدم dialog.showOpenDialog لعرض مربع حوار اختيار ملف أصلي من نظام التشغيل.

أفضل الممارسات للتواصل بين العمليات:

  • • استخدم أسماء قنوات واضحة ومنظمة، مثل 'وحدة:إجراء'.
  • • فضّل استخدام ipcMain.handle و ipcRenderer.invoke على on/send لأنها تعيد Promise وتبسط التعامل مع الأخطاء.
  • • لا تنقل كميات كبيرة من البيانات عبر IPC لتجنب مشاكل الأداء.
  • • تحقق دائمًا من البيانات المستلمة من عملية العرض قبل استخدامها.

الخطوة 5: تشغيل التطبيق

بعد إنشاء جميع الملفات المطلوبة، يمكننا الآن تشغيل التطبيق باستخدام الأمر التالي:

npm start
ما يحدث عند تشغيل التطبيق:
  1. 1. تبدأ العملية الرئيسية (main.js) وتهيئ التطبيق
  2. 2. تُنشأ نافذة جديدة باستخدام BrowserWindow
  3. 3. يُحمَّل سكريبت التحميل المسبق (preload.js)
  4. 4. تُحمَّل صفحة HTML (index.html) في النافذة
  5. 5. تُنفَّذ سكريبتات عملية العرض (renderer.js)
  6. 6. واجهة المستخدم جاهزة للتفاعل
كيفية تصحيح الأخطاء:
  • أدوات المطور في عملية العرض:

    يمكنك فتح أدوات المطور بإلغاء تعليق هذا السطر في main.js: mainWindow.webContents.openDevTools()

  • سجلات العملية الرئيسية:

    تظهر في وحدة التحكم حيث قمت بتشغيل npm start

  • تمكين إعادة التحميل التلقائي:

    يمكنك استخدام electron-reload للتحميل التلقائي عند تغيير الملفات خلال التطوير.

واجهات برمجة التطبيق الرئيسية

Electron يوفر مجموعة غنية من واجهات برمجة التطبيق (APIs) التي تمكنك من الوصول إلى ميزات نظام التشغيل والتفاعل مع المكونات المختلفة للتطبيق.

أنماط تطوير متقدمة

بعد فهم أساسيات Electron، يمكنك استكشاف أنماط وتقنيات متقدمة لتحسين كفاءة التطوير وأداء التطبيق وتجربة المستخدم.

أنماط الأمان المتقدمة

  • عزل السياق الكامل

    استخدام contextIsolation مع سكريبت preload محدود بدقة لمنع الوصول غير المصرح به.

  • سياسة أمان المحتوى (CSP)

    تطبيق سياسات أمان المحتوى الصارمة لمنع هجمات XSS وحقن الكود.

  • الصندوق الرملي

    استخدام sandbox: true في webPreferences لزيادة عزل عمليات العرض.

تحسين الأداء

  • التحميل الكسول للمكونات

    تحميل الميزات والمكونات عند الحاجة فقط لتسريع وقت بدء التطبيق.

  • عمليات الخدمة الخلفية

    نقل العمليات الثقيلة إلى عمليات منفصلة لتجنب تجميد واجهة المستخدم.

  • تحسين استهلاك الذاكرة

    إدارة إنشاء النوافذ والعمليات بكفاءة لتقليل استهلاك الموارد.