Upgrade to Pro — share decks privately, control downloads, hide ads and more …

I can't work on my phone - desktop all the things

stefan judis
February 16, 2017

I can't work on my phone - desktop all the things

The web platform gets stronger every month. Bluetooth, offline capability and even virtual reality are coming into our browsers.

Does this mean that we don't need desktop apps anymore?

No, a big trend is happening in parallel. A new go-to platform to build desktop applications entered the stage – Electron.

Everything in Electron is built with HTML, CSS and JavaScript. Now frontend developers build almost everything with it. From small tiny apps solving everyday problems to full fledged IDEs – developers build tools for the environment they spend at least eight hours a day in: the desktop.

Let's have a look at how this works and what it takes to build a desktop app.

stefan judis

February 16, 2017
Tweet

More Decks by stefan judis

Other Decks in Technology

Transcript

  1. Stefan Judis Frontend Developer, Occasional Teacher, Meetup Organizer ❤ Open

    Source, Performance and Accessibility ❤ @stefanjudis
  2. I spend a lot of time in front of a

    computer because I can't work on my phone!
  3. CHROMIUM BROWSER - architecture - Browser / Main https://speakerdeck.com/zcbenz/practice-on-embedding-node-dot-js-into-atom-editor Controlled

    by C++ Window … Tray Menu Dialog IPC Renderer HTML JavaScript (DOM) Renderer HTML JavaScript (DOM) Renderer HTML JavaScript (DOM) Window Window
  4. CHROMIUM BROWSER - architecture - https://speakerdeck.com/zcbenz/practice-on-embedding-node-dot-js-into-atom-editor Controlled by C++ Window

    … Tray Menu Dialog IPC Renderer HTML JavaScript (DOM) Renderer HTML JavaScript (DOM) Renderer HTML JavaScript (DOM) Window Window Browser / Main
  5. https://speakerdeck.com/zcbenz/practice-on-embedding-node-dot-js-into-atom-editor Controlled by Node.js Window … Tray Menu Dialog IPC

    Renderer HTML JavaScript (DOM) Node.js Renderer HTML JavaScript (DOM) Node.js Renderer HTML JavaScript (DOM) Node.js Window Window ATOM-SHELL - architecture - Browser / Main
  6. { "name": "electron-quick-start", "version": "1.0.0", "description": "A minimal Electron application",

    "main": "main.js", "scripts": { "start": "electron ." }, "repository": "https://github.com/electron/electron-quick-start", "keywords": [], "devDependencies": { "electron": "^1.4.1" } } package.json
  7. { "name": "electron-quick-start", "version": "1.0.0", "description": "A minimal Electron application",

    "main": "main.js", "scripts": { "start": "electron ." }, "repository": "https://github.com/electron/electron-quick-start", "keywords": [], "devDependencies": { "electron": "^1.4.1" } } package.json
  8. { "name": "electron-quick-start", "version": "1.0.0", "description": "A minimal Electron application",

    "main": "main.js", "scripts": { "start": "electron ." }, "repository": "https://github.com/electron/electron-quick-start", "keywords": [], "devDependencies": { "electron": "^1.4.1" } } package.json
  9. const electron = require( 'electron' ); const app = electron.app;

    const BrowserWindow = electron.BrowserWindow; const path = require( 'path' ); const url = require( 'url' ); let mainWindow; function createWindow () { mainWindow = new BrowserWindow({ width: 800, height: 600 }); mainWindow.loadURL( url.format({ pathname : path.join( __dirname, 'index.html' ), protocol : 'file:', slashes : true }) ); mainWindow.on( 'closed', function () { mainWindow = null } ); } app.on( 'ready', createWindow ); main.js Browser process
  10. const electron = require( 'electron' ); const app = electron.app;

    const BrowserWindow = electron.BrowserWindow; const path = require( 'path' ); const url = require( 'url' ); let mainWindow; function createWindow () { mainWindow = new BrowserWindow({ width: 800, height: 600 }); mainWindow.loadURL( url.format({ pathname : path.join( __dirname, 'index.html' ), protocol : 'file:', slashes : true }) ); mainWindow.on( 'closed', function () { mainWindow = null } ); } app.on( 'ready', createWindow ); main.js Browser process
  11. const electron = require( 'electron' ); const app = electron.app;

    const BrowserWindow = electron.BrowserWindow; const path = require( 'path' ); const url = require( 'url' ); let mainWindow; function createWindow () { mainWindow = new BrowserWindow({ width: 800, height: 600 }); mainWindow.loadURL( url.format({ pathname : path.join( __dirname, 'index.html' ), protocol : 'file:', slashes : true }) ); mainWindow.on( 'closed', function () { mainWindow = null } ); } app.on( 'ready', createWindow ); main.js Browser process
  12. const electron = require( 'electron' ); const app = electron.app;

    const BrowserWindow = electron.BrowserWindow; const path = require( 'path' ); const url = require( 'url' ); let mainWindow; function createWindow () { mainWindow = new BrowserWindow({ width: 800, height: 600 }); mainWindow.loadURL( url.format({ pathname : path.join( __dirname, 'index.html' ), protocol : 'file:', slashes : true }) ); mainWindow.on( 'closed', function () { mainWindow = null } ); } app.on( 'ready', createWindow ); main.js Browser process
  13. main.js const electron = require( 'electron' ); const app =

    electron.app; const BrowserWindow = electron.BrowserWindow; const path = require( 'path' ); const url = require( 'url' ); let mainWindow; function createWindow () { mainWindow = new BrowserWindow({ width: 800, height: 600 }); mainWindow.loadURL( url.format({ pathname : path.join( __dirname, 'index.html' ), protocol : 'file:', slashes : true }) ); mainWindow.on( 'closed', function () { mainWindow = null } ); } app.on( 'ready', createWindow ); Browser process
  14. index.html <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Hello World!</title> </head>

    <body> <h1>Hello World!</h1> We are using Node.js <script>document.write(process.versions.node)</script>, Chromium <script>document.write(process.versions.chrome)</script>, and Electron <script>document.write(process.versions.electron)</script>. </body> <script> require( './renderer.js' ); </script> </html> Renderer process
  15. index.html <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Hello World!</title> </head>

    <body> <h1>Hello World!</h1> We are using Node.js <script>document.write(process.versions.node)</script>, Chromium <script>document.write(process.versions.chrome)</script>, and Electron <script>document.write(process.versions.electron)</script>. </body> <script> require( './renderer.js' ); </script> </html> Renderer process
  16. index.html <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Hello World!</title> </head>

    <body> <h1>Hello World!</h1> We are using Node.js <script>document.write(process.versions.node)</script>, Chromium <script>document.write(process.versions.chrome)</script>, and Electron <script>document.write(process.versions.electron)</script>. </body> <script> require( './renderer.js' ); </script> </html> Renderer process
  17. Browser (main.js) Controlled by Node.js Window Renderer (index.html) HTML JavaScript

    (DOM) Node.js ATOM-SHELL - architecture - // main.js mainWindow = new BrowserWindow();
  18. VAR, LET, CONST GRID LAYOUT CSS CUSTOM PROPERTIES ... ...

    ... Renderer process USE LATEST TECHNOLOGY (without babel, auto-prefixer, ...)
  19. Renderer process Renderer process <!DOCTYPE html> <html> <head> <meta charset="UTF-8">

    <title>Take a picture!!!</title> <style>...</style> </head> <body> <video id="camera" autoplay> </video> <canvas id="canvas"></canvas> <div role="alert" id="success"></div> <button type="button" aria-label="Make picture"> <svg xmlns="http://www.w3.org/2000/svg">...</svg> </button> <script> // 50 lines of JS </script> </body> </html>
  20. Renderer process <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Take a

    picture!!!</title> <style>...</style> </head> <body> <video id="camera" autoplay> </video> <canvas id="canvas"></canvas> <div role="alert" id="success"></div> <button type="button" aria-label="Make picture"> <svg xmlns="http://www.w3.org/2000/svg">...</svg> </button> <script> // 50 lines of inlined JS </script> </body> </html> Renderer process
  21. Renderer process Renderer process navigator.getUserMedia( { video: true }, (

    stream ) => { videoElem.src = URL.createObjectURL( stream ); }, () => { alert('could not connect stream'); } );
  22. Renderer process Renderer process videoElem.addEventListener('play', () => { setInterval(() =>

    { context.fillRect(0, 0, width, height); context.drawImage(videoElem, 0, 0, width, height); }, 33); });
  23. Renderer process Renderer process button.addEventListener('click', () => { const fileName

    = 'some/folder/some/file.png'; writeFile( fileName, canvasContent, () => { // success } ) });
  24. Renderer process <script> const userHome = require( 'user-home' ); const

    { join } = require( 'path' ); const { writeFile } = require( 'fs' ); const canvasElem = document.getElementById( 'canvas' ); button.addEventListener( 'click', () => { const fileName = join( userHome, 'desktop', 'test-images', + (new Date()) + '.png' ); writeFile( fileName, new Buffer( canvasElem.toDataURL( 'image/png' ).replace( /^data:image\/png;base64,/, '' ), 'base64' ), ( error ) => { if ( error ) { return console.log(error); } successElem.innerHTML = `${fileName} written!` setTimeout( _ => successElem.innerHTML = '', 3000 ); } ) } ); </script> Renderer process
  25. Renderer process <script> const userHome = require( 'user-home' ); const

    { join } = require( 'path' ); const { writeFile } = require( 'fs' ); const canvasElem = document.getElementById( 'canvas' ); button.addEventListener( 'click', () => { const fileName = join( userHome, 'desktop', 'test-images', + (new Date()) + '.png' ); writeFile( fileName, new Buffer( canvasElem.toDataURL( 'image/png' ).replace( /^data:image\/png;base64,/, '' ), 'base64' ), ( error ) => { if ( error ) { return console.log(error); } successElem.innerHTML = `${fileName} written!` setTimeout( _ => successElem.innerHTML = '', 3000 ); } ) } ); </script> Renderer process
  26. Renderer process <script> const userHome = require( 'user-home' ); const

    { join } = require( 'path' ); const { writeFile } = require( 'fs' ); const canvasElem = document.getElementById( 'canvas' ); button.addEventListener( 'click', () => { const fileName = join( userHome, 'desktop', 'test-images', + (new Date()) + '.png' ); writeFile( fileName, new Buffer( canvasElem.toDataURL( 'image/png' ).replace( /^data:image\/png;base64,/, '' ), 'base64' ), ( error ) => { if ( error ) { return console.log(error); } successElem.innerHTML = `${fileName} written!` setTimeout( _ => successElem.innerHTML = '', 3000 ); } ) } ); </script> Renderer process
  27. Renderer process <script> const userHome = require( 'user-home' ); const

    { join } = require( 'path' ); const { writeFile } = require( 'fs' ); const canvasElem = document.getElementById( 'canvas' ); button.addEventListener( 'click', () => { const fileName = join( userHome, 'desktop', 'test-images', + (new Date()) + '.png' ); writeFile( fileName, new Buffer( canvasElem.toDataURL( 'image/png' ).replace( /^data:image\/png;base64,/, '' ), 'base64' ), ( error ) => { if ( error ) { return console.log(error); } successElem.innerHTML = `${fileName} written!` setTimeout( _ => successElem.innerHTML = '', 3000 ); } ) } ); </script> Renderer process Node.js lives in the DOM!
  28. BLOCKY - real world examples - I NEED A TOOL

    TO EASILY DISABLE THE SCREENSAVER " "
  29. main.js Browser process const { app, Menu, Tray, powerSaveBlocker }

    = require( 'electron' ); const path = require( 'path' ); const icons = { active : path.join( __dirname, 'media', 'icon_active.png' ) , notActive : path.join( __dirname, 'media', 'icon.png' ) }; const menus = { active : Menu.buildFromTemplate( [ { label: 'Time for sleeping again', click : toggleSleepPermission }, { type: 'separator' }, { label: 'Quit', click : app.quit } ] ), notActive : Menu.buildFromTemplate( [ { label: 'Don\'t go to sleep', click : toggleSleepPermission }, { type: 'separator' }, { label: 'Quit', click : app.quit } ] ) };
  30. main.js Browser process const { app, Menu, Tray, powerSaveBlocker }

    = require( 'electron' ); const path = require( 'path' ); const icons = { active : path.join( __dirname, 'media', 'icon_active.png' ) , notActive : path.join( __dirname, 'media', 'icon.png' ) }; const menus = { active : Menu.buildFromTemplate( [ { label: 'Time for sleeping again', click : toggleSleepPermission }, { type: 'separator' }, { label: 'Quit', click : app.quit } ] ), notActive : Menu.buildFromTemplate( [ { label: 'Don\'t go to sleep', click : toggleSleepPermission }, { type: 'separator' }, { label: 'Quit', click : app.quit } ] ) };
  31. main.js const { app, Menu, Tray, powerSaveBlocker } = require(

    'electron' ); const path = require( 'path' ); const icons = { active : path.join( __dirname, 'media', 'icon_active.png' ) , notActive : path.join( __dirname, 'media', 'icon.png' ) }; const menus = { active : Menu.buildFromTemplate( [ { label: 'Time for sleeping again', click : toggleSleepPermission }, { type: 'separator' }, { label: 'Quit', click : app.quit } ] ), notActive : Menu.buildFromTemplate( [ { label: 'Don\'t go to sleep', click : toggleSleepPermission }, { type: 'separator' }, { label: 'Quit', click : app.quit } ] ) }; Browser process
  32. main.js const { app, Menu, Tray, powerSaveBlocker } = require(

    'electron' ); const path = require( 'path' ); const icons = { active : path.join( __dirname, 'media', 'icon_active.png' ) , notActive : path.join( __dirname, 'media', 'icon.png' ) }; const menus = { active : Menu.buildFromTemplate( [ { label: 'Time for sleeping again', click : toggleSleepPermission }, { type: 'separator' }, { label: 'Quit', click : app.quit } ] ), notActive : Menu.buildFromTemplate( [ { label: 'Don\'t go to sleep', click : toggleSleepPermission }, { type: 'separator' }, { label: 'Quit', click : app.quit } ] ) }; Browser process
  33. main.js Browser process let appTray = null; app.on( 'ready', initTray

    ); function initTray() { if ( app.dock ) { app.dock.hide(); } appTray = new Tray( path.resolve( __dirname, 'media', 'icon.png' ) ); appTray.setToolTip( 'Blocky' ); appTray.setContextMenu( menus.notActive ); }
  34. main.js Browser process let currentBlocker = null; function toggleSleepPermission() {

    let state; if ( currentBlocker === null ) { currentBlocker = powerSaveBlocker.start( 'prevent-display-sleep' ); state = 'active'; } else { powerSaveBlocker.stop( currentBlocker ); currentBlocker = null; state = 'notActive'; } appTray.setContextMenu( menus[ state ] ); appTray.setImage( icons[ state ] ); }
  35. main.js Browser process let currentBlocker = null; function toggleSleepPermission() {

    let state; if ( currentBlocker === null ) { currentBlocker = powerSaveBlocker.start( 'prevent-display-sleep' ); state = 'active'; } else { powerSaveBlocker.stop( currentBlocker ); currentBlocker = null; state = 'notActive'; } appTray.setContextMenu( menus[ state ] ); appTray.setImage( icons[ state ] ); }
  36. main.js Browser process let currentBlocker = null; function toggleSleepPermission() {

    let state; if ( currentBlocker === null ) { currentBlocker = powerSaveBlocker.start( 'prevent-display-sleep' ); state = 'active'; } else { powerSaveBlocker.stop( currentBlocker ); currentBlocker = null; state = 'notActive'; } appTray.setContextMenu( menus[ state ] ); appTray.setImage( icons[ state ] ); }
  37. main.js Browser process let currentBlocker = null; function toggleSleepPermission() {

    let state; if ( currentBlocker === null ) { currentBlocker = powerSaveBlocker.start( 'prevent-display-sleep' ); state = 'active'; } else { powerSaveBlocker.stop( currentBlocker ); currentBlocker = null; state = 'notActive'; } appTray.setContextMenu( menus[ state ] ); appTray.setImage( icons[ state ] ); }
  38. Application Menus Browser process github.com/electron/electron/blob/master/docs/api/menu.md const { Menu } =

    require( 'electron' ); const template = [ ... { label : 'KCDC', submenu : [ { label : 'Say "Barbecue"', accelerator : 'CmdOrCtrl+B', click() { console.log('Barbecue...'); } }, ... ] } ]; const menu = Menu.buildFromTemplate( template ); Menu.setApplicationMenu( menu );
  39. Browser process github.com/electron/electron/blob/master/docs/api/menu.md const { Menu } = require( 'electron'

    ); const template = [ ... { label : 'KCDC', submenu : [ { label : 'Say "Barbecue"', accelerator : 'CmdOrCtrl+B', click() { console.log('Barbecue...'); } }, ... ] } ]; const menu = Menu.buildFromTemplate( template ); Menu.setApplicationMenu( menu ); Application Menus
  40. const { Menu } = require( 'electron' ); const template

    = [ ... { label : 'KCDC', submenu : [ { label : 'Say "Barbecue"', accelerator : 'CmdOrCtrl+B', click() { console.log('Barbecue...'); } }, ... ] } ]; const menu = Menu.buildFromTemplate( template ); Menu.setApplicationMenu( menu ); Browser process github.com/electron/electron/blob/master/docs/api/menu.md Application Menus
  41. github.com/electron/electron/blob/master/docs/api/menu.md Application Menus if ( process.platform === 'darwin' ) {

    ... } if ( process.platform === 'win32' ) { ... } if ( process.platform === 'linux' ) { ... }
  42. Global Shortcuts Browser process github.com/electron/electron/blob/master/docs/api/global-shortcut.md function initTray() { if (

    app.dock ) { app.dock.hide(); } globalShortcut.register( 'CmdOrCtrl+B', toggleSleepPermission ); appTray = new Tray( path.resolve( __dirname, 'media', 'icon.png' ) ); appTray.setToolTip( 'Block screensaver' ); appTray.setContextMenu( menus.notActive ); }
  43. Global Shortcuts Browser process github.com/electron/electron/blob/master/docs/api/global-shortcut.md function initTray() { if (

    app.dock ) { app.dock.hide(); } globalShortcut.register( 'CmdOrCtrl+B', toggleSleepPermission ); appTray = new Tray( path.resolve( __dirname, 'media', 'icon.png' ) ); appTray.setToolTip( 'Block screensaver' ); appTray.setContextMenu( menus.notActive ); }
  44. blocky - 72 LOC - native functionality is extremely easy

    to implement github.com/stefanjudis/blocky
  45. main.js Browser process const menubar = require( 'menubar' ); const

    mb = menubar(); mb.on( 'ready', () => { console.log( 'app is ready' ); } );
  46. index.html Renderer process <!DOCTYPE html> <html> <head>...</head> <body> <h1>Today I

    learned</h1> <ul class="list" id="posts"></ul> <script> const contentful = require( 'contentful' ); const client = contentful.createClient( {...} ) .getEntries( {...} ) .then( entries => { document.getElementById( 'posts' ).innerHTML = entries.items.map( entry => { return `<li><a href="...">${entry.fields.title}</a></li>`; } ).join( '' ); } ); </script> </body> </html>
  47. index.html <!DOCTYPE html> <html> <head>...</head> <body> <h1>Today I learned</h1> <ul

    class="list" id="posts"></ul> <script> const contentful = require( 'contentful' ); const client = contentful.createClient( {...} ) .getEntries( {...} ) .then( entries => { document.getElementById( 'posts' ).innerHTML = entries.items.map( entry => { return `<li><a href="...">${entry.fields.title}</a></li>`; } ).join( '' ); } ); </script> </body> </html> Renderer process
  48. index.html <!DOCTYPE html> <html> <head>...</head> <body> <h1>Today I learned</h1> <ul

    class="list" id="posts"></ul> <script> const contentful = require( 'contentful' ); const client = contentful.createClient( {...} ) .getEntries( {...} ) .then( entries => { document.getElementById( 'posts' ).innerHTML = entries.items.map( entry => { return `<li><a href="...">${entry.fields.title}</a></li>`; } ).join( '' ); } ); </script> </body> </html> Renderer process
  49. index.html <!DOCTYPE html> <html> <head>...</head> <body> <h1>Today I learned</h1> <ul

    class="list" id="posts"></ul> <script> const contentful = require( 'contentful' ); const client = contentful.createClient( {...} ) .getEntries( {...} ) .then( entries => { document.getElementById( 'posts' ).innerHTML = entries.items.map( entry => { return `<li><a href="...">${entry.fields.title}</a></li>`; } ).join( '' ); } ); </script> </body> </html> Renderer process
  50. PODCAST APP - real world examples - I NEED A

    TOOL TO TO LISTEN TO PODCASTS. " "
  51. index.html Renderer process <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Pocketcast

    App</title> <style> body, html { height: 100%; margin: 0; padding: 0; } webview { width: 100%; height: 100%; } </style> </head> <body> <webview src="https://play.pocketcasts.com/web"></webview> </body> </html>
  52. index.html <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Pocketcast App</title> <style>

    body, html { height: 100%; margin: 0; padding: 0; } webview { width: 100%; height: 100%; } </style> </head> <body> <webview src="https://play.pocketcasts.com/web"></webview> </body> </html> Renderer process
  53. index.html <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Pocketcast App</title> <style>

    body, html { height: 100%; margin: 0; padding: 0; } webview { width: 100%; height: 100%; } </style> </head> <body> <webview src="https://play.pocketcasts.com/web"></webview> </body> </html> Renderer process
  54. index.html <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Pocketcast App</title> <style>

    body, html { height: 100%; margin: 0; padding: 0; } webview { width: 100%; height: 100%; } </style> </head> <body> <webview src="https://play.pocketcasts.com/web" preload="./webview.js"></webview> </body> </html> Renderer process
  55. webview.js Renderer process ( function( window ) { const openExternal

    = require( 'electron' ).shell.openExternal; window.addEventListener( 'click', ( event ) => { const baseRegex = new RegExp( `${ window.location.hostname}` ); if ( event.target.tagName === 'A' && ! baseRegex.test( event.target.href ) ) { openExternal( event.target.href ); event.preventDefault(); } } ); } )( window );
  56. ( function( window ) { const openExternal = require( 'electron'

    ).shell.openExternal; window.addEventListener( 'click', ( event ) => { const baseRegex = new RegExp( `${ window.location.hostname}` ); if ( event.target.tagName === 'A' && ! baseRegex.test( event.target.href ) ) { openExternal( event.target.href ); event.preventDefault(); } } ); } )( window ); webview.js Renderer process
  57. FORREST - real world examples - I NEED A TOOL

    TO GET LONG RUNNING PROCESSES OUT OF MY TERMINAL. " "
  58. IPC Renderer process const { ipcRenderer } = require( 'electron'

    ); ipcRenderer.send( 'delete-repo', repo.id ); - one renderer process -
  59. Renderer process const { ipcRenderer } = require( 'electron' );

    ipcRenderer.send( 'delete-repo', repo.id ); const { ipcMain } = require( 'electron' ); ipcMain.on( 'delete-repo', ( event, repoId ) => { // delete repo and save settings deleteRepoWithId( repoId ); event.sender.send( 'update-repos', getRepos() ); } ); Browser process IPC - one renderer process -
  60. Renderer process const { ipcRenderer } = require( 'electron' );

    ipcRenderer.send( 'delete-repo', repo.id ); const { ipcMain } = require( 'electron' ); ipcMain.on( 'delete-repo', ( event, repoId ) => { // delete repo and save settings deleteRepoWithId( repoId ); event.sender.send( 'update-repos', getRepos() ); } ); const { ipcRenderer } = require( 'electron' ); ipcRenderer.on( 'update-repos', ( event, repos ) => { // update application state } ); Browser process Renderer process IPC - one renderer process -
  61. Browser Controlled by Node.js Window … Tray Menu Dialog IPC

    Renderer HTML JavaScript (DOM) Node.js Renderer HTML JavaScript (DOM) Node.js Renderer HTML JavaScript (DOM) Node.js Window Window IPC - one renderer process -
  62. Browser Controlled by Node.js Window … Tray Menu Dialog IPC

    Renderer HTML JavaScript (DOM) Node.js Renderer HTML JavaScript (DOM) Node.js Renderer HTML JavaScript (DOM) Node.js Window Window delete-repo IPC - one renderer process -
  63. Browser Controlled by Node.js Window … Tray Menu Dialog IPC

    Renderer HTML JavaScript (DOM) Node.js Renderer HTML JavaScript (DOM) Node.js Renderer HTML JavaScript (DOM) Node.js Window Window delete-repo update-repos IPC - one renderer process -
  64. IPC - several renderer processes - const { ipcMain }

    = require( 'electron' ); const windows = [ new BrowserWindow( { /* ... */ }, new BrowserWindow( { /* ... */ }, new BrowserWindow( { /* ... */ } ]; ipcMain.on( 'delete-repo', ( event, repoId ) => { // delete repo and save settings deleteRepoWithId( repoId ); windows.forEach( window => window.webContents.emit( 'update-repos', getRepos() ) ); } ); Browser process
  65. IPC - several renderer processes - const { ipcMain }

    = require( 'electron' ); const windows = [ new BrowserWindow( { /* ... */ }, new BrowserWindow( { /* ... */ }, new BrowserWindow( { /* ... */ } ]; ipcMain.on( 'delete-repo', ( event, repoId ) => { // delete repo and save settings deleteRepoWithId( repoId ); windows.forEach( window => window.webContents.emit( 'update-repos', getRepos() ) ); } ); Browser process
  66. Browser Controlled by Node.js Window … Tray Menu Dialog IPC

    Renderer HTML JavaScript (DOM) Node.js Renderer HTML JavaScript (DOM) Node.js Renderer HTML JavaScript (DOM) Node.js Window Window IPC - one renderer process -
  67. Browser Controlled by Node.js Window … Tray Menu Dialog IPC

    Renderer HTML JavaScript (DOM) Node.js Renderer HTML JavaScript (DOM) Node.js Renderer HTML JavaScript (DOM) Node.js Window Window delete-repo IPC - one renderer process -
  68. Browser Controlled by Node.js Window … Tray Menu Dialog IPC

    Renderer HTML JavaScript (DOM) Node.js Renderer HTML JavaScript (DOM) Node.js Renderer HTML JavaScript (DOM) Node.js Window Window delete-repo IPC - one renderer process - update-repos
  69. runScript() { this.processCmd = options.isCustom ? `npm run ${ script.name

    }` : script.command; this.$set( 'process', this.exec( this.processCmd, { cwd : options.cwd } ) ); this.process.stdout.on( 'data', this.handleData ); this.process.stderr.on( 'data', this.handleData ); this.process.on( 'error', this.handleScriptExit ); this.process.on( 'close', this.handleScriptExit ); this.process.on( 'exit', this.handleScriptExit ); } BUILDING PROPER TERMINAL OUTPUT - execute a command -
  70. runScript() { this.processCmd = options.isCustom ? `npm run ${ script.name

    }` : script.command; this.$set( 'process', this.exec( this.processCmd, { cwd : options.cwd } ) ); this.process.stdout.on( 'data', this.handleData ); this.process.stderr.on( 'data', this.handleData ); this.process.on( 'error', this.handleScriptExit ); this.process.on( 'close', this.handleScriptExit ); this.process.on( 'exit', this.handleScriptExit ); } BUILDING PROPER TERMINAL OUTPUT - execute a command -
  71. runScript() { this.processCmd = options.isCustom ? `npm run ${ script.name

    }` : script.command; this.$set( 'process', this.exec( this.processCmd, { cwd : options.cwd } ) ); this.process.stdout.on( 'data', this.handleData ); this.process.stderr.on( 'data', this.handleData ); this.process.on( 'error', this.handleScriptExit ); this.process.on( 'close', this.handleScriptExit ); this.process.on( 'exit', this.handleScriptExit ); } BUILDING PROPER TERMINAL OUTPUT - execute a command -
  72. runScript() { this.processCmd = options.isCustom ? `npm run ${ script.name

    }` : script.command; this.$set( 'process', this.exec( this.processCmd, { cwd : options.cwd } ) ); this.process.stdout.on( 'data', this.handleData ); this.process.stderr.on( 'data', this.handleData ); this.process.on( 'error', this.handleScriptExit ); this.process.on( 'close', this.handleScriptExit ); this.process.on( 'exit', this.handleScriptExit ); } BUILDING PROPER TERMINAL OUTPUT There is no guarantee that these events are emitted in order. - execute a command -
  73. module.exports = class Session extends EventEmitter { constructor () {

    super(); const baseEnv = Object.assign( {}, process.env, { LANG : app.getLocale().replace( '-', '_' ) + '.UTF-8', TERM : 'xterm-256color' } ); this.pty = spawn( defaultShell, [ '--login' ], { env : baseEnv } ); this.pty.stdout.on( 'data', ( data ) => { this.emit( 'data', data.toString( 'utf8' ) ); } ); } write ( data ) { this.pty.stdin.write( data ); } }; BUILDING PROPER TERMINAL OUTPUT - let a shell execute the command -
  74. module.exports = class Session extends EventEmitter { constructor () {

    super(); const baseEnv = Object.assign( {}, process.env, { LANG : app.getLocale().replace( '-', '_' ) + '.UTF-8', TERM : 'xterm-256color' } ); this.pty = spawn( defaultShell, [ '--login' ], { env : baseEnv } ); this.pty.stdout.on( 'data', ( data ) => { this.emit( 'data', data.toString( 'utf8' ) ); } ); } write ( data ) { this.pty.stdin.write( data ); } }; BUILDING PROPER TERMINAL OUTPUT - let a shell execute the command -
  75. module.exports = class Session extends EventEmitter { constructor () {

    super(); const baseEnv = Object.assign( {}, process.env, { LANG : app.getLocale().replace( '-', '_' ) + '.UTF-8', TERM : 'xterm-256color' } ); this.pty = spawn( defaultShell, [ '--login' ], { env : baseEnv } ); this.pty.stdout.on( 'data', ( data ) => { this.emit( 'data', data.toString( 'utf8' ) ); } ); } write ( data ) { this.pty.stdin.write( data ); } }; BUILDING PROPER TERMINAL OUTPUT - let a shell execute the command -
  76. module.exports = class Session extends EventEmitter { constructor () {

    super(); const baseEnv = Object.assign( {}, process.env, { LANG : app.getLocale().replace( '-', '_' ) + '.UTF-8', TERM : 'xterm-256color' } ); this.pty = spawn( defaultShell, [ '--login' ], { env : baseEnv } ); this.pty.stdout.on( 'data', ( data ) => { this.emit( 'data', data.toString( 'utf8' ) ); } ); } write ( data ) { this.pty.stdin.write( data ); } }; BUILDING PROPER TERMINAL OUTPUT - let a shell execute the command -
  77. DISTRIBUTION - electron-builder - { "scripts": { "start": "electron .",

    "clean": "rm -rf ./dist", "clean:osx": "rm -rf ./dist/osx", "clean:win": "rm -rf ./dist/win", "pack": "npm run clean && npm run pack:osx && npm run pack:win", "pack:osx": "npm run clean:osx && electron-packager . \"Blocky\" --out=dist/osx --platform=darwin \ --arch=x64 --version=0.36.1 --icon=media/icon.icns --ignore=\"(node_modules|dist)\"", "pack:win": "npm run clean:win && electron-packager . \"Blocky\" --out=dist/win --platform=win32 \ --arch=ia32 --version=0.36.1 --icon=media/icon.ico --ignore=\"(node_modules|dist)\"", "build": "npm run build:osx && npm run build:win", "build:osx": "npm run pack:osx && electron-builder \"dist/osx/Blocky-darwin-x64/Blocky.app\" \ --platform=osx --out=\"dist/osx\" --config=builder.json", "build:win": "npm run pack:win && electron-builder \"dist/win/Blocky-win32-ia32\" \ --platform=win --out=\"dist/win\" --config=builder.json" } }
  78. DISTRIBUTION - electron-builder - { "scripts": { "start": "electron .",

    "clean": "rm -rf ./dist", "clean:osx": "rm -rf ./dist/osx", "clean:win": "rm -rf ./dist/win", "pack": "npm run clean && npm run pack:osx && npm run pack:win", "pack:osx": "npm run clean:osx && electron-packager . \"Blocky\" --out=dist/osx --platform=darwin \ --arch=x64 --version=0.36.1 --icon=media/icon.icns --ignore=\"(node_modules|dist)\"", "pack:win": "npm run clean:win && electron-packager . \"Blocky\" --out=dist/win --platform=win32 \ --arch=ia32 --version=0.36.1 --icon=media/icon.ico --ignore=\"(node_modules|dist)\"", "build": "npm run build:osx && npm run build:win", "build:osx": "npm run pack:osx && electron-builder \"dist/osx/Blocky-darwin-x64/Blocky.app\" \ --platform=osx --out=\"dist/osx\" --config=builder.json", "build:win": "npm run pack:win && electron-builder \"dist/win/Blocky-win32-ia32\" \ --platform=win --out=\"dist/win\" --config=builder.json" } }
  79. DISTRIBUTION - electron-builder - { "scripts": { "start": "electron .",

    "clean": "rm -rf ./dist", "clean:osx": "rm -rf ./dist/osx", "clean:win": "rm -rf ./dist/win", "pack": "npm run clean && npm run pack:osx && npm run pack:win", "pack:osx": "npm run clean:osx && electron-packager . \"Blocky\" --out=dist/osx --platform=darwin \ --arch=x64 --version=0.36.1 --icon=media/icon.icns --ignore=\"(node_modules|dist)\"", "pack:win": "npm run clean:win && electron-packager . \"Blocky\" --out=dist/win --platform=win32 \ --arch=ia32 --version=0.36.1 --icon=media/icon.ico --ignore=\"(node_modules|dist)\"", "build": "npm run build:osx && npm run build:win", "build:osx": "npm run pack:osx && electron-builder \"dist/osx/Blocky-darwin-x64/Blocky.app\" \ --platform=osx --out=\"dist/osx\" --config=builder.json", "build:win": "npm run pack:win && electron-builder \"dist/win/Blocky-win32-ia32\" \ --platform=win --out=\"dist/win\" --config=builder.json" } }
  80. DISTRIBUTION - electron-builder - { "name": "blocky", "build": { "appId":

    "com.electron.blocky" }, "version": "1.0.0", "description": "A tray app to block the screensaver", "main": "index.js", "scripts": { "start": "electron .", "pack": "build --dir", "dist": "build" }, "author": "stefanjudis <[email protected]>", "license": "MIT", "devDependencies": { "electron": "^1.4.15", "electron-builder": "^13.5.0" } }
  81. DISTRIBUTION - electron-builder - { "name": "blocky", "build": { "appId":

    "com.electron.blocky" }, "version": "1.0.0", "description": "A tray app to block the screensaver", "main": "index.js", "scripts": { "start": "electron .", "pack": "build --dir", "dist": "build" }, "author": "stefanjudis <[email protected]>", "license": "MIT", "devDependencies": { "electron": "^1.4.15", "electron-builder": "^13.5.0" } }
  82. DISTRIBUTION - electron-builder - { "name": "blocky", "build": { "appId":

    "com.electron.blocky" }, "version": "1.0.0", "description": "A tray app to block the screensaver", "main": "index.js", "scripts": { "start": "electron .", "pack": "build --dir", "dist": "build" }, "author": "stefanjudis <[email protected]>", "license": "MIT", "devDependencies": { "electron": "^1.4.15", "electron-builder": "^13.5.0" } }