معرفی Electron.js

معرفی Electron.js

انتشار:

الکترون (Electron) یک فریم‌ورک توسعه نرم‌افزار است که توسط GitHub توسعه یافته است و به توسعه‌دهندگان امکان ساخت اپلیکیشن‌های دسکتاپ Cross-Platform (سیستم‌عامل‌های مختلف از جمله Windows، macOS وLinux ) با استفاده از تکنولوژی وب (HTML, CSS, JS) را فراهم می‌کند.

با استفاده ازElectron ، توسعه‌دهندگان قادر هستند اپلیکیشن‌هایی با ویژگی‌های حرفه‌ای، پیشرفته و پیچیده‌تر از وب مانند امکان دسترسی به منابع و سخت‌افزار سیستم کاربر/میزبان، ایجاد و مدیریت پنجره، و اتصال به سرویس‌های اینترنتی را ایجاد کنند. Electron به عنوان یک فریم‌ورک محبوب در توسعه اپلیکیشن‌های دسکتاپ به دلیل سهولت استفاده و انعطاف‌پذیری آن شناخته شده است و توسعه دهندگان از سطوح مبتدی تا پیشرفته می‌توانند از آن استفاده کنند.

 

 

مقدمه و پیش‌نیازها

توجه داشته باشید برای توسعه نرم‌افزار با استفاده از الکترون نیاز به حداقل دانش در حوزه Front-end و Node.js داشته باشید.

ابزار‌های مورد نیاز و پیشنهادی:

  • استفاده از Visual Studio Code جهت نوشتن کد
  • داشتن Node.js در سیستم (توجه کنید که electron باندل میکنه) استفاده از nvm پیشنهاد می‌شود
  • جهت ساخت برنامه نهایی و عملیاتی از electron forge‌ استفاده کنید. در این آموزش استفاده نمیشه

 

معماری و مدل فرآیند الکترون

بنابر توضیحات در وبسایت رسمی الکترون، معماری چند فرآیندی (multi-process) الکترون شبیه به کرومیوم (Chromium) است که در این معماری، هر تب (Tab) مرورگر یک process مستقل است که توسط بخش main (اصلی) مدیریت و توسط renderer نمایش داده می‌شود.

کافیست بدانیم که کدهای Node.js در main اجرا می‌شود و امکان استفاده از API‌های Node.js در آن وجود دارد. وظیفه اصلی main شروع و مدیریت برنامه به واسطه ماژول BrowserWindow است. در ادامه به همراه نمونه کدها با BrowserWindow آشنا می‌شویم.

هر نمونه از کلاس BrowserWindow یک پنجره جدید ایجاد میکند که هر پنجره توسط یک renderer process مجزا رندر می‌شود.

 

راه‌اندازی

برای شروع، یک پروژه ایجاد می‌کنیم: npm init -y

نصب الکترون به عنوان devDependency: npm i -D electron

ایجاد فایل main.js به عنوان نقطه شروع برنامه در پوشه src:

// @ts-check

const { app, BrowserWindow } = require("electron");

app
    .whenReady()
    .then(() => {
        const appWindow = new BrowserWindow();
        appWindow.loadURL("https://blog.sepehrsamavati.ir");
    });

در این مرحله چنین ساختاری خواهیم داشت:

 

تغییر package.json:

{
/* ... */

  "main": "./src/main.js",
  "scripts": {
    "start": "electron ."
  },

/* ... */
}

 

حال می‌توانیم با دستور npm start برنامه را اجرا کنیم. این چند خط برای اجرای الکترون، بارگذاری و کنترل یک صفحه کافی است. از نظر رفتاری و ظاهری با مرورگر تفاوت چندانی ندارد اما نکته مهم که باید به آن توجه کرد، امکان مدیریت و کنترل window است که انجام کارهای متنوع و زیادی را ممکن می‌سازد.

 

برای بارگذاری و نمایش UI از فایل‌های HTML, CSS, JS استفاده می‌کنیم. برای مثال می‌توان از خروجی build شده React به عنوان UI استفاده کرد. اما اینجا با مثال‌های ساده پیش می‌رویم:

یک پوشه با عنوان public ایجاد می‌کنیم که فایل‌های static را در آن قرار میدهیم، در اینجا از 2 فایل HTML و JS استفاده می‌کنیم:

فایل index.html که script را اجرا می‌کند:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta http-equiv="Content-Security-Policy" content="script-src 'self';">
    <title>Just testing electron</title>
</head>

<body>
    <script src="./script.js"></script>
</body>

</html>

 

فایل script.js، فعلا فقط یک متن را به body اضافه می‌کند:

(() => {
    document.body.innerText += "Hello World!";
})();

 

فایل main.js با کمی تغییرات؛ قسمت ایجاد window را جدا میکنیم (تابع createWindow)، با ایجاد event listener بر روی ایونت window-all-closed، وقتی که تمام پنجره‌ها بسته شدند برنامه را می‌بندیم (پیاده‌سازی رفتار پیش فرض در ویندوز و لینوکس):

// @ts-check

const { app, BrowserWindow } = require("electron");

const createWindow = () => {
    const appWindow = new BrowserWindow();
    appWindow.loadFile("../public/index.html");
};

app.on('window-all-closed', function () {
    app.quit();
});

app
    .whenReady()
    .then(() => {
        createWindow();
    });

ساختار فایل‌ها:

 

ارتباط بین جاوا اسکریپت مرورگر (UI) و Node.js الکترون (client)

به طور کلی 2 روش برای ایجاد ارتباط بین UI و Process الکترون وجود دارد:

  • از طریق شبکه (مثلا سرور HTTP)
  • ارتباط مستقیم IPC

 

ارتباط از طریق شبکه (سرور) ساختار تمیزتری را می‌تواند داشته باشد اما ممکن است مشکلاتی هم ایجاد کند، مثلا اگر در محیط اجرایی محدودیت استفاده از شبکه و اشغال کردن پورت داشته باشیم، یا رعایت کردن بحث امنیت و سربار پروتکل‌های ارتباطی هم قابل ملاحظه است. اما اگر به هر حال بخوایم از شبکه استفاده کنیم کافیه یک HTTP API server سمت الکترون اجرا کنیم (از فایل main.js فراخوانی بشود) و از سمت UI درخواستی را جهت اجرا به localhost و پورت مورد نظر ارسال کنیم.

برای ایجاد ارتباط مستقیم IPC از دو ماژول ipcMain و ipcRenderer که الکترون در اختیارمان قرار داده است استفاده می‌کنیم. همانطور که از اسم ماژول‌ها مشخص است، به ترتیب سمت process اصلی الکترون و process رندر UI استفاده می‌شوند. ارتباط یا به صورت یک طرفه (event) یا به صورت دو طرفه (promise) مقدور است.

 

برای expose کردن ipcRenderer از یک فایل رابط تحت عنوان pre-load استفاده می‌کنیم که توابع مورد نظرمان را به window مرورگر وصل میکند و میتوانیم از جاوا اسکریپت UI آن‌ها را صدا بزنیم.

یک فایل با نام preLoad.js می‌سازیم، جهت اجرا شدن باید آدرس فایل را برای پنجره‌ای که توسط الکترون ایجاد می‌شود مشخص کنیم:

const path = require("node:path"); // افزودن ماژول path

const createWindow = () => {
    const appWindow = new BrowserWindow({
        webPreferences: {
            preload: path.join(__dirname, "preload.js") // مشخص کردن آدرس فایل
        }
    });
    appWindow.loadFile("../public/index.html");
};

 

ارتباط یک طرفه (فراخوانی توابع process اصلی توسط UI)

در این مثال یک دکمه ایجاد می‌کنیم که در صورت کلیک شدن، process اصلی الکترون توسط جاوا اسکریپت مرورگر بسته می‌شود.

 

فایل preLoad.js جهت expose توابع به پنجره مرورگر:

// @ts-check
const { contextBridge, ipcRenderer } = require('electron/renderer');

contextBridge.exposeInMainWorld('electronAPI', {
  exit: (title, message) => ipcRenderer.send('exit', title, message)
});

توجه کنید که کل آبجکت ipcRenderer یا توابع آن را به مرورگر expose نکنید.

 

اضافه کردن دکمه UI با استفاده از script.js:

(() => {
    document.body.innerText += "Hello World!";

    /**
     * 
     * @param {string} title 
     * @param {() => void} onClick 
     */
    const createButton = (title, onClick) => {
        const breakLine = document.createElement("br");

        const button = document.createElement("button");
        button.addEventListener("click", onClick);
        button.innerText = title;

        document.body.append(breakLine, button);
    };

    createButton("❌ Exit", () => {
        window.electronAPI.exit("Exit application", "Quit the app?");
    });
})();

 

تا اینجا، از سمت UI با صدا زدن تابع window.electronAPI.exit()، event خروج تریگر می‌شود اما هنوز listener ندارد. با اضافه کردن کد زیر به main.js کار کامل می‌شود.

const { app, ipcMain, dialog, BrowserWindow } = require("electron"); // افزودن ماژول‌های دیالوگ و آی پی سی

ipcMain.on('exit', (_event, title, message) => {
    const userInput = dialog.showMessageBoxSync({ // سوال پرسیدن از کاربر جهت خروج از برنامه
        type: 'question',
        title, message,
        buttons: ['Yes', 'No']
    });

    if (userInput === 0) // اگر دکمه اول کلیک شد
        app.quit(); // بستن برنامه
});

 

خروجی و ساختار فایل‌ها:

 

 

ارتباط دو طرفه (دریافت اطلاعات توسط UI)

دو event به main.js اضافه می‌کنیم که از آن‌ها داخل UI استفاده خواهیم کرد:

ipcMain.handle('showDialog', (_event, type, title, message) => {
    return dialog.showMessageBoxSync({
        type, title, message,
        buttons: ['Yes', 'No']
    });
});

ipcMain.handle('getCpuInfo', () => {
    return os.cpus();
});

 

افزودن توابع به preLoad.js:

contextBridge.exposeInMainWorld('electronAPI', {
    exit: (title, message) => ipcRenderer.send('exit', title, message),
    showDialog: (type, title, message) => ipcRenderer.invoke('showDialog', type, title, message),
    getCpuInfo: () => ipcRenderer.invoke('getCpuInfo'),
});

 

اضافه کردن دکمه‌های UI با script.js:

createButton("ℹ Info dialog", async () => {
    const res = await window.electronAPI.showDialog("info", "Info title", "Click a button");
    document.body.append(res);
});

createButton("⚠ Warning dialog", async () => {
    const res = await window.electronAPI.showDialog("warning", "Warning title", "Yes or No?");
    document.body.append(res);
});

createButton("🖥 CPU info", async () => {
    const res = await window.electronAPI.getCpuInfo();
    document.body.append(JSON.stringify(res));
});

 

 

ایجاد package و فایل installer

برای بسته‌بندی برنامه و آماده‌سازی برای کاربر باید از ابزارهایی استفاده کنیم تا نرم‌افزار را بسته‌بندی (package) کنیم و همچنین فایل نصب (installer) درست کنیم تا نرم‌افزار قابل نصب (و حذف) شود. در این مثال از ابزار electron-builder استفاده کردم، هرچند ابزارهای جدیدتری مانند electron-forge وجود دارند ولی electron-builder محبوب‌تر است و برای شروع، کار کردن باهاش راحت‌تر می‌باشد.

با نصب electron-builer با استفاده از دستور npm i -D electron-builder شروع می‌کنیم. بعد از نصب کافی است تنظیمات زیر را به package.json اضافه کنید:

{
/* ... */

  "scripts": {
    "start": "electron .",
    "package": "electron-builder build --win --publish never"
  },
  "build": {
    "productName": "AppName",
    "win": {
      "target": "nsis"
    },
    "files": [
      "./src/**/*",
      "./public/**/*"
    ]
  }

/* ... */
}

در این مثال، installer برای سیستم عامل ویندوز (با استفاده از nsis) ساخته می‌شود (دستور --win و تنظیم target: nsis).