יצירת אפליקציית דסקטופ ב- Node.js

יצירת אפליקציית דסקטופ ב- Node.js

כיום צריך פחות אפליקציות דסקטופ – תוכנות שמתוקנות מקומית מחשב האישי. הרבה מהיכולות עברו אל תוך הדפדפן עם התפתחות HTML5, ויכולות אחרות עברו לענן.

אבל עדיין יש צורך באפליקציות כאלו. אני רוצה להראות איך אפשר להשתמש ב- Node.js כדי לכתוב אפליקציה בצורה פשוטה – ושתהיה cross platform.

analyze

 הקדמה

הרעיון בכתיבת אפליקציה עם Node.js, זה לנצל את המגוון העצום של הספריות שקיימות ב- npm, ולחבר את זה ל- GUI שאותו נכתוב כמו דף web רגיל – html/js/css. בצורה כזו נוכל (יחסית) מהר להגיע לאפליקציה שנראית טוב. וגם עובדת טוב.

ישנן מספר ספריות שפועלות באזור הזה. אני בחרתי ב- Electron. כפי שנראה מייד, אלקטרון נותנת כלים נוחים לעבודה – ליצירת פרוייקט בצורה מהירה, אבל גם עוזרת לארוז את המוצר הסופי באפליקציה מוגמרת. (אפשרות אחרת שמצאתי: AppJS).

הכי פשוט כדי להתחיל ולכתוב עם אלקטרון זה להשתמש ב- boilerplate שלהם, שנמצא ב- git:

git clone https://github.com/electron/electron-quick-start
cd electron-quick-start
npm install

 נעבור על חלק מהקבצים שנוצרו. נתחיל עם main.js:

const electron = require('electron');
// Module to control application life.
const app = electron.app;
// Module to create native browser window.
const BrowserWindow = electron.BrowserWindow;
 
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let mainWindow;
 
function createWindow () {
  // Create the browser window.
  mainWindow = new BrowserWindow({width: 800, height: 600});
 
  // and load the index.html of the app.
  mainWindow.loadURL(`file://${__dirname}/index.html`);
 
  // Open the DevTools.
  mainWindow.webContents.openDevTools();
 
  // Emitted when the window is closed.
  mainWindow.on('closed', function () {
    // Dereference the window object, usually you would store windows
    // in an array if your app supports multi windows, this is the time
    // when you should delete the corresponding element.
    mainWindow = null;
  })
}
 
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', createWindow);
 
// Quit when all windows are closed.
app.on('window-all-closed', function () {
  // On OS X it is common for applications and their menu bar
  // to stay active until the user quits explicitly with Cmd + Q
  if (process.platform !== 'darwin') {
    app.quit();
  }
})
 
app.on('activate', function () {
  // On OS X it's common to re-create a window in the app when the
  // dock icon is clicked and there are no other windows open.
  if (mainWindow === null) {
    createWindow();
  }
})

בקובץ זה נמצא ה- entry point של התוכנית. שם נוצרים webPages חדשים, ושם מנוהל ה- main process. מה זה אומר? כל חלון מתנהל לעצמו, עם המשאבים שלו, ב- process שנקרא renderer process. אם חלון מסויים רוצה ליצור לדוגמא חיבור עם ממשק נייטיבי כלשהו – זה לא אפשרי – זה חייב לעבור דרך ה- main process. בהמשך נראה דוגמא לזה.

דבר שימושי נוסף שיש כאן, זה ()openDevTools. כל זמן שאנחנו עדיין בתהליכי הפיתוח נשאיר את השורה הזו. כשנריץ את התוכנית נראה שמייד ייפתח ה- Chrome DevTool מה שייקל עלינו את תהליך הדיבאג.

דמו

כדי להדגים את היכולות של אלקטרון, נכתוב תוכנית בה נבחר תיקיה מהמחשב, ונקבל גרף שמציג את התפלגות סוגי הקבצים שנמצאים בה. זו דוגמה לתוכנה שצריכה לרוץ מקומית, ולא יכולה לרוץ בדפדפן.

נתחיל בלהוסיף עוד כמה שורות קוד לסוף של main.js:

const ipc = require('electron').ipcMain
const dialog = require('electron').dialog
 
ipc.on('open-file-dialog', function (event) {
  dialog.showOpenDialog({
    properties: ['openFile', 'openDirectory']
  }, function (files) {
    if (files) event.sender.send('selected-directory', files)
  })
})

כדי לאפשר לנו להציג דיאלוג נייטיבי של בחירת תיקיה אנחנו צריכים ליצור את הדיאלוג עצמו ב- main process, ורק אז נוכל לקרוא לו מתוך הדף עצמו.

עכשיו נעבור לדף עצמו, index.html. נתחיל ביצירת ה"שלד":

...
<body>
<div class="wrapper">
	<button class="dirButton" id="select-directory">Select Directory</button>
<div id="pie"></div>
</div>
...

אחרי הלחיצה על הכפתור ובחירת התיקיה, ב- div שנקרא "pie" יופיע הגרף. עכשיו נתחיל להוסיף את הלוגיקה:

const ipc = require('electron').ipcRenderer;
	const selectDirBtn = document.getElementById('select-directory');
 
	selectDirBtn.addEventListener('click', function (event) {
	  ipc.send('open-file-dialog')
	});
 
	ipc.on('selected-directory', function (event, path) {
	  initDict();
	  readDirectory(path.toString(), function(){
	  	showGraph(filesDict, path.toString());
	  });
	});

בצורה כזו אנו מתקשרים עם הקוד שהוספנו ב- main.js – כאשר יש לחיצה על הכפתור, אנחנו קוראים ל- main process לקרוא לדיאלוג נייטיבי. כאשר באמת נבחרה תיקיה נרוץ על כל הקבצים שיש בה, נקטלג אותם, ובסוף נציג את הנתונים בגרף.

var fs = require('fs');
var pathReq = require("path");
function readDirectory(path, cb) {
	fs.readdir(path, function (err, files) { // '/' denotes the root folder
		  if (err) throw err;
		  var counter = 0;
		   files.forEach( function (file) {
		     fs.lstat(path + pathReq.sep + file, function(err, stats) {
		       if (!err && stats.isDirectory()) { //conditing for identifying folders
		         var seperator = pathReq.sep;
		         if (path === pathReq.sep) {
		         	seperator = '';
		         }
		         readDirectory(path + seperator + file, null);
		       }
		       else{
		        addFile(file, stats.size);
		      }
		      counter++;
		      if (counter == files.length) {
		      	if (cb != null) {
					cb();
				}
		      }
		     });
		   });
		});
	}

אנחנו עוברים על כל הקבצים רקורסיבית: כל פעם שברשימה ישנה תיקיה נפעיל עליה מחדש את הפונקציה. כדי שתהיה לנו אינדיקציה מתי סיימנו לעבור על כל הקבצים, רק הקריאה הראשונית לפונקציה היא עם callback. כשהקריאה הזו מסתיימת – שסיימנו לעבור על כל רשימת הקבצים הראשונית – אפשר לחזור ל- callback ולצייר את הגרף.

נקודה מעניינת שכדאי לשים לב אליה – seperator משתנה בין מערכות ההפעלה. כלומר, ב- windows כשרוצים לעבור 'רמה' בתיקיה משתמשים ב- '\', לדוגמא 'c:\some\path\to'. לעומת לינוקס וכדו': '/some/path/to'. לכן, אם רוצים שהתוכנה תעבוד בכל מערכות ההפעלה, לא כדאי לכתוב במפורש '/' אלא להשתמש בספריית path, ב- property שנקרא sep.

כדי לצייר את הגרף נשתמש בספרייה שנכתבה על גבי d3, שכל מה שהיא יודעת זה לצייר pies:

var pie = null;
function showGraph(dict, title){
	var method = 'counter'; // 'size'
	if (pie != null) {
		pie.destroy();
		pie = null;
	}
	pie = new d3pie("pie", {
		header: {
			title: {
				text: title
			}
		},
		data: {
			content: [
				{ label: "Images", value: dict.images[method] },
				{ label: "Videos", value: dict.videos[method] },
				{ label: "Docs", value: dict.docs[method] },
				{ label: "Code", value: dict.code[method] },
				{ label: "Others", value: dict.others[method] }
			]
		}
	});
}

ישנן שתי אפשרויות לפיהן אפשר להראות את האחוזים – לפי גדלי הקבצים, או לפי מספר הקבצים. לפני שניצור גרף חדש נוודא שמחקנו את הישן (אם יש). אחרת עם כל ריצה שכזו יתווסף svg נוסף לתוכן של ה- <div>.

תארוז לי

טוב, אז יצרנו תוכנה, הכל מצויין. מה עושים עם זה הלאה?

עכשיו צריך לארוז את התוכנה לקובץ מסודר, שגם משתמש שלא יודע להריץ 'npm start' יוכל להשתמש בה. למרבה המזל, גם לזה electron מספקים לנו כלי בשם 'electron-packager'. כדי להתקין אותו צריך רק:

npm install electron-packager -g

ואז מתוך התיקיה בה נמצא הפרוייקט צריך להריץ את הפקודה electron-packager עם הפרמטרים המתאימים – לאיזו ארכיטקטורה ומערכת הפעלה אנחנו רוצים לבנות.

הכי מגניב (בעיניי) זו האפשרות הזו:

electron-packager . --all

כלומר לבנות מייד גירסאות לכל הפלטפורמות והארכיטקטורות. וואו.

סיכום

כתיבת אפליקצייה ב- node.js יכולה להיות נוחה מאד בעזרת כלי כמו electron. ניתן להשתמש בכל כלי הפרונט אנד המוכרים מה- web ובכל הספריות הקיימות ב- npm ביחד.

אפשר למצוא כאן את הקוד של הדמו.

Share on FacebookShare on Google+Tweet about this on TwitterShare on LinkedIn

One thought on “יצירת אפליקציית דסקטופ ב- Node.js

  1. פינגבאק: HyperTerm |

כתיבת תגובה

האימייל לא יוצג באתר. שדות החובה מסומנים *