Платформа ElectronJS

Основные технологии

  • HTML, CSS, JavaScript
  • Chromium - отображение контента.
  • Node.js - для работы с файловой системой и операционной системой
  • Свое API - для работы с часто используемымы функциями операционной системы.

Настройка среды

  • Установить nodejs.
  • Установить npm.
  • Выбрать редактор кода. Рекомендации: Atom, Visual Studio Code.
  • Почти все поддерживают javascript.

Создание приложения

  • Инициализировать приложение.
  • Установить пакет electron.
  • В секцию scripts, ключ start, прописать "electron ." для запуска приложения
  • Использовать openDevTools
mkdir my-electron-app && cd my-electron-app
npm init -y
npm i --save-dev electron
				    

Структура файлов

my-electron-app/
├── package.json
├── main.js
└── index.html
				    
  • package.json - конфигурационный файл проекта.
  • main.js - логика приложения на JavaScript.
  • index.html - интерфейс приложения.

main.js

const { app, BrowserWindow } = require('electron')

function createWindow () {
  const win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      nodeIntegration: true
    }
  })

  win.loadFile('index.html')
  win.webContents.openDevTools()
}

app.whenReady().then(createWindow)
				    
				    

Класс BrowserWindow

  • Создает и управляет окном.
  • Свойства (например, backgroundColor).
  • События (например, ready-to-show).
  • Функции (например, getAllWindows()).

Пример события для BrowserWindow

const { BrowserWindow } = require('electron')
let win = new BrowserWindow({ show: false })
win.once('ready-to-show', () => {
  win.show()
})
				    

Модальное окно

const { BrowserWindow } = require('electron')

let top = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      nodeIntegration: true
    }
  })
let child = new BrowserWindow({ parent: top, modal: true, show: false })
child.loadURL('https://github.com')
child.once('ready-to-show', () => {
  child.show()
})				    
				    

Процессы

  • Основной (main) процесс (всегда один).
  • Процесс отрисовки (на каждое окно свой).

Основной процесс

  • Создает веб страницы как экземпляры BrowserWindow.
  • Веб страница работает в своем собственном процессе отрисовки.
  • Когда удаляется экземпляр BrowserWindow, то соответствующий процесс отрисовки убивается.
  • Управляет всеми веб страницами и их процессами отрисовки.

Процесс отрисовки

  • Управляет соответствующей страницей.
  • Проблема в одном из процессов отрисовки (например, аварийный остонов) не влияет на другие.
  • Взаимодействуют между собой через IPC.

ipcMain

  • Асинхронное взаимодействие из главного процесса с процессами отрисовки.
// In main process.
const { ipcMain } = require('electron')
ipcMain.on('asynchronous-message', (event, arg) => {
  console.log(arg) // prints "ping"
  event.reply('asynchronous-reply', 'pong')
})

ipcMain.on('synchronous-message', (event, arg) => {
  console.log(arg) // prints "ping"
  event.returnValue = 'pong'
})				    
				    

ipcRenderer

  • Асинхронное взаимодействие из процесса рисования с главным процессом.
// In renderer process (web page).
const { ipcRenderer } = require('electron')
console.log(ipcRenderer.sendSync('synchronous-message', 'ping')) // prints "pong"

ipcRenderer.on('asynchronous-reply', (event, arg) => {
  console.log(arg) // prints "pong"
})
ipcRenderer.send('asynchronous-message', 'ping')
				    

Полный пример.

<html> 
   <head> 
      <meta charset = "UTF-8"> 
      <title>File read using system dialogs 
   </head> 
   
   <body> 
      <script type = "text/javascript">
	    const { ipcRenderer } = require('electron')
	    console.log(ipcRenderer.sendSync('synchronous-message', 'ping')) // prints "pong"

	    ipcRenderer.on('asynchronous-reply', (event, arg) => {
		console.log(arg) // prints "pong"
	    })
	    ipcRenderer.send('asynchronous-message', 'ping')
      </script> 
   </body> 
</html>
				    

HTML

  • Теги: иерархическая структура (открытый, закрытый тек, пустой тег).
  • Начальный тег: html
  • Теги: head, body.
  • Содержимое head: meta, link, scripts.
  • Примеры тегов: h1, h2, h3, h4, ul, li и др.
  • Тег ссылки: a
  • Построение таблиц: table, thead, tbody, tfoot, tr, td.
  • Списки: ul, li.
  • Формы: input.

CSS

  • Таблица стилей
  • Подключение: inline, в документе (тег style) и файлом (тег link).

Пример

#tags_form_space ul.tagit {
    border-radius: 0;
    border: none;
    background: black;
    color: white;
    font-size: 18px;
    padding-left: 12px;
} 
				    

Селекторы

  • * - все элементы (универсальный).
  • .class - выбранный класс (свойство class).
  • #id - идентификатор (свойство id).
  • input - по элементу.
  • a[href^=https] - по атрибуту.

Комбинаторы

  • , (запятая) - перечисление (И).
  • ' ' (пробел) - все дочерние.
  • > - первый уровень вложенности.
  • + - следующий соседний.
  • ~ - все соседние в рамках одного родителя

Объект document

  • Представляет собой загруженный DOM документ
  • Класс Element
  • Класс Node

Свойства документа

  • lastModified
  • location
  • cookie

Доступ к элементам (Element)

  • getElementById
  • getElementsByClassName
  • getElementsByName
  • getElementsByTagName

Свойства элемента

  • innerHtml
  • className
  • id
  • scrollTop
  • style

Примеры работы функций доступа к элементам

// Получение элемента по идентификатору и изменение его свойства
var element = document.getElementById(id);
element.style.color = 'red';

// Получение элементов по имени класса. 
var allOrangeJuiceByClass = document.getElementsByClassName('orange juice');
var result = "";
for (var i=0, len=allOrangeJuiceByClass.length; i > len; i=i+1) {
    result += "\n  " + allOrangeJuiceByClass[i].textContent;
}
				    

Открытие диалогового окна

ipcMain.on('openFile', (event, path) => { 
   const {dialog} = require('electron') 
   const fs = require('fs') 
   dialog.showOpenDialog(function (fileNames) { 
      
      // fileNames is an array that contains all the selected 
      if(fileNames === undefined) { 
         console.log("No file selected"); 
      
      } else { 
         readFile(fileNames[0]); 
      } 
   });
})  				    
				    

Открытие диалогового окна

function readFile(filepath) { 
   fs.readFile(filepath, 'utf-8', (err, data) => { 
      
      if(err){ 
         alert("An error ocurred reading the file :" + err.message) 
         return 
      } 
      
      // handle the file content 
      event.sender.send('fileData', data) 
   }) 
} 
				    

Открытие диалогового окна

<html> 
   <head> 
      <meta charset = "UTF-8"> 
      <title>File read using system dialogs</title> 
   </head> 
   
   <body> 
      <script type = "text/javascript"> 
         const {ipcRenderer} = require('electron') 
         ipcRenderer.send('openFile', () => { 
            console.log("Event sent."); 
         }) 
         
         ipcRenderer.on('fileData', (event, data) => { 
            document.write(data) 
         }) 
      </script> 
   </body> 
</html>				    
				

Производительность

  • С осторожностью подключать модули (изучать зависимости и ресурсы, которые на это требуются).
  • Откладывать загрузку модулей на столько позже, насколько это возможно.
  • Все работы с I/O выносить из главного процесса (если блокируется главный процесс, то блокируется все приложение).

Производительность

  • Исключить polyfill. Всегда знаете какой движок используется. Если код переносится с web проекта, то лучше убедиться, что отсутствуют подключения различных polyfill реализаций.
  • Избегать лишних запросов к сети. Лучше включить ресурс в приложение, чем подгрузить его с сети.
  • Объединять код в один файл с помощью специальных средств (webpack, parcel, rollup.js и др.).

Производительность. require

const fs = require('fs')
const fooParser = require('foo-parser')

class Parser {
  constructor () {
    this.files = fs.readdirSync('.')
  }

  getParsedFiles () {
    return fooParser.parse(this.files)
  }
}

const parser = new Parser()

module.exports = { parser }
				
				    

Производительность. require

const fs = require('fs')

class Parser {
  async getFiles () {
    this.files = this.files || await fs.readdir('.')

    return this.files
  }

  async getParsedFiles () {
    const fooParser = require('foo-parser')
    const files = await this.getFiles()

    return fooParser.parse(files)
  }
}				
const parser = new Parser()

module.exports = { parser }