Electron

ChtiJS #14 - 01/10/15

Qui suis-je ? (vite fait)

  • Mathieu Acthernoene, aka @Zoontek
  • Développeur web
  • Un peu touche à tout

Electron…c'est quoi ?

  • Permet de développer des app desktop à l'aide des technologies web
  • Développé initialement par GitHub
  • Compatibilité OSX / Linux / Windows
  • Node.js et Chromium

Qui l'utilise ?

…et bien d'autres

Pourquoi ?

Tout comme NW.js, en fait

Presque ! Mais avec des plus

Now, let's code !

STOP !!!

Gestion des processus

Processus principal

Processus de rendu

Processus de rendu

Processus de rendu

Back to code !

Installation

Évitez l'installation globale !

npm init
{
  "name": "electron-app",
  "main": "main.js",
  "devDependencies": {
    "electron-prebuilt": "^0.33.3"
  },
  "scripts": {
    "start": "electron ."
  }
}
npm install electron-prebuilt --save-dev
touch main.js
'use strict';

const app = require('app');
const BrowserWindow = require('browser-window');

let mainWindow = null;

app.on('ready', () => {
  mainWindow = new BrowserWindow({
    width: 800,
    height: 600
  });

  mainWindow.loadUrl('file://' + __dirname + '/index.html');
  mainWindow.openDevTools();

  mainWindow.on('closed', () => {
    mainWindow = null;
  });
});

app.on('window-all-closed', () => {
  if (process.platform != 'darwin') {
    app.quit();
  }
});

Point d'entrée

touch index.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Electron app</title>
</head>
<body>
  Electron
  <script>document.write(process.versions['electron'])</script><br/>

  Node.js
  <script>document.write(process.version)</script><br/>

  Chromium
  <script>document.write(process.versions['chrome'])</script><br/>
</body>
</html>

Une première fenêtre

npm start

Les modules

Processus principal

Processus de rendu

Les deux

Module ipc

(Communication inter-processus)

Processus principal

Processus de rendu

Processus de rendu

Processus de rendu

channel

souscris

reçois

publie

'use strict';

const app = require('app');
const BrowserWindow = require('browser-window');
const ipc = require('ipc');

let mainWindow = null;

app.on('ready', () => {
  mainWindow = new BrowserWindow({
    width: 800,
    height: 600
  });

  mainWindow.loadUrl('file://' + __dirname + '/index.html');
  mainWindow.openDevTools();

  mainWindow.on('closed', () => {
    mainWindow = null;
  });
});

app.on('window-all-closed', () => {
  if (process.platform != 'darwin') {
    app.quit();
  }
});

ipc.on('say-hello', (event, name) => {
  console.log(`Hello ${name}`);
  event.sender.send('hello-reply');
});
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Electron app</title>
</head>
<body>
  <input type="text" class="hello-name">
  <button class="hello-btn">Say Hello!</button>

  <script>
    'use strict';

    const ipc = require('ipc');

    let helloName = document.querySelector('.hello-name');
    let helloBtn = document.querySelector('.hello-btn');

    helloBtn.addEventListener('click', () => {
      ipc.send('say-hello', helloName.value);
    });

    ipc.on('hello-reply', () => {
      helloName.value = '';
    });
  </script>
</body>
</html>

Module web-contents

(EventEmitter)

ipc.on('say-hello', (event, name) => {
  // event is a WebContents object
  console.log(`Hello ${name}`);
  event.sender.send('hello-reply');
});

Events

  • did-finish-load

  • did-fail-load

  • did-start-loading

  • did-stop-loading

  • dom-ready

  • etc.

Methods

  • webContents.loadUrl(url[, options])

  • webContents.getTitle()

  • webContents.reload()

  • webContents.printToPDF(options, callback)

  • webContents.send(channel[, args...])

  • etc.

'use strict';

const app = require('app');
const BrowserWindow = require('browser-window');
const ipc = require('ipc');

let mainWindow = null;

app.on('ready', () => {
  mainWindow = new BrowserWindow({
    width: 800,
    height: 600
  });

  mainWindow.loadUrl('http://chtijs.francejs.org');
  let webContents = mainWindow.webContents;

  webContents.on('dom-ready', () => {
    console.log('DOM is ready');
  });

  mainWindow.on('closed', () => {
    mainWindow = null;
  });
});

app.on('window-all-closed', () => {
  if (process.platform != 'darwin') {
    app.quit();
  }
});

Module remote

(Controller le processes principal depuis un processus de rendu)

'use strict';

const app = require('app');
const BrowserWindow = require('browser-window');

let mainWindow = null;

app.on('ready', () => {
  mainWindow = new BrowserWindow({
    width: 800,
    height: 600
  });

  mainWindow.loadUrl('file://' + __dirname + '/index.html');

  mainWindow.on('closed', () => {
    mainWindow = null;
  });
});

app.on('window-all-closed', () => {
  if (process.platform != 'darwin') {
    app.quit();
  }
});
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Electron app</title>
</head>
<body>
  <input type="text" class="new-window-url">
  <button class="new-window-btn">Open in a new window</button>

  <script>
    'use strict';

    const remote = require('remote');
    const BrowserWindow = remote.require('browser-window');

    let newWindowUrl = document.querySelector('.new-window-url');
    let newWindowBtn = document.querySelector('.new-window-btn');

    newWindowBtn.addEventListener('click', () => {
      let newWindow = new BrowserWindow({
        width: 800,
        height: 600
      });

      newWindow.loadUrl(newWindowUrl.value);
    });
  </script>
</body>
</html>

Quelques modules plus "fun"

(ceux qui interagissent avec l'OS)

Modules menu / menu-item

<!-- index.html -->
<script>
  'use strict';

  const remote = require('remote');
  const Menu = remote.require('menu');
  const MenuItem = remote.require('menu-item');

  let menu = new Menu();

  menu.append(
    new MenuItem({
      label: 'Log a message',
      click: () => { console.log('Item was clicked'); }
    })
  );

  menu.append(
    new MenuItem({
      label: 'Something status',
      type: 'checkbox',
      checked: true
    })
  );

  window.addEventListener('contextmenu', (e) => {
    e.preventDefault();
    menu.popup(remote.getCurrentWindow());
  }, false);
</script>
// main.js
let menuTemplate = [{
  label: 'View',
  submenu: [{
      label: 'Toggle Developer Tools',
      accelerator: (() => {
        if (process.platform == 'darwin')
          return 'Alt+Command+I';
        else
          return 'Ctrl+Shift+I';
      })(),
      click: (item, focusedWindow) => {
        if (focusedWindow)
          focusedWindow.toggleDevTools();
      }
  }]
}];

if (process.platform == 'darwin') {
  let name = require('app').getName();

  menuTemplate.unshift({
    label: name,
    submenu: [{
      label: 'Quit',
      accelerator: 'Command+Q',
      click: () => { app.quit(); }
    }]
  });
}

let menu = Menu.buildFromTemplate(menuTemplate);
Menu.setApplicationMenu(menu);

Modules tray

'use strict';

const app = require('app');
const Menu = require('menu');
const Tray = require('tray');

let appIcon = null;

app.on('ready', () => {
  appIcon = new Tray('./assets/icon.png');

  let contextMenu = Menu.buildFromTemplate([
    { label: 'Value 1', type: 'radio', checked: true },
    { label: 'Value 2', type: 'radio' },
    { label: 'Value 3', type: 'radio' },
    { label: 'Value 4', type: 'radio' }
  ]);

  appIcon.setToolTip('Tooltip example');
  appIcon.setContextMenu(contextMenu);
});

Et bien d'autres !

Résumons

Richesse de Chromium

Richesse de Node.js

Richesse d'Electron

Couche d'abstraction forte

+

+

=

Démo time

Et ensuite ?

Merci de votre attention !

Des questions ?

Présentation de Electron

By Mathieu Acthernoene

Présentation de Electron

  • 3,036