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

22725c2d3eb331146549bf0d5d3c050c?s=47 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.

22725c2d3eb331146549bf0d5d3c050c?s=128

stefan judis

February 16, 2017
Tweet

Transcript

  1. I can't work on my phone @stefanjudis Desktop all the

    things!
  2. Stefan Judis Frontend Developer, Occasional Teacher, Meetup Organizer ❤ Open

    Source, Performance and Accessibility ❤ @stefanjudis
  3. cssclass.es

  4. None
  5. I had a passion for music Photo by Allen Holt

  6. I became a sound engineer Photo by Josep Tomàs

  7. I lost my passion Photo by Guian Bolisay

  8. I got a Bachelors degree in 'Media and Computer Science'

    Photo by Aaron Hawkins
  9. JS I found another passion

  10. 10 Web technology is everywhere SERVER WEB IOT FOUNDER

  11. Everybody agrees that mobile is a thing Photo by jeanbaptisteparis

  12. OPENED JS PULL REQUESTS ON GITHUB IN 2016 1,604,219 The

    state of the Octoverse 2016
  13. ACTIVE USERS IN 2016 +5.8M The state of the Octoverse

    2016
  14. 90% are self taught Stack Overflow Developer Survey 2017

  15. Learning out of curiosity

  16. Stefan, you're working too much!

  17. I spend a lot of time in front of a

    computer because I can't work on my phone!
  18. Electron cross platform desktop apps with JavaScript, HTML, and CSS

  19. Let’s build our dream editor! With Web Technology!

  20. REQUIREMENTS Write to disk Native functionality Separation of concerns

  21. ATOM

  22. Atom-shell is a Node.js programmable Chromium browser Photo by Peter

    Roome
  23. 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
  24. None
  25. Browser process

  26. Renderer process

  27. 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
  28. 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
  29. ELECTRON

  30. Let's get started!

  31. { "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
  32. { "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
  33. { "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
  34. 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
  35. 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
  36. 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
  37. 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
  38. 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
  39. 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
  40. 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
  41. 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
  42. None
  43. None
  44. Browser (main.js) Controlled by Node.js Window Renderer (index.html) HTML JavaScript

    (DOM) Node.js ATOM-SHELL - architecture - // main.js mainWindow = new BrowserWindow();
  45. Facts about the renderer process

  46. None
  47. None
  48. It's a controlled environment!

  49. VAR, LET, CONST GRID LAYOUT CSS CUSTOM PROPERTIES ... ...

    ... Renderer process USE LATEST TECHNOLOGY (without babel, auto-prefixer, ...)
  50. None
  51. None
  52. 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>
  53. 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
  54. Renderer process Renderer process navigator.getUserMedia( { video: true }, (

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

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

    = 'some/folder/some/file.png'; writeFile( fileName, canvasContent, () => { // success } ) });
  57. 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
  58. 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
  59. 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
  60. 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!
  61. <script src="./webcam.js"></script> or <script> require('./webcam'); </script> Renderer process WORKS IN

    AN EXTERNAL FILE, TOO!
  62. <script src="./webcam.js"></script> or <script> require('./webcam'); </script> Renderer process WORKS IN

    AN EXTERNAL FILE, TOO! Build your apps as usual!
  63. Let's have a look at real stuff

  64. BLOCKY - real world examples - I NEED A TOOL

    TO EASILY DISABLE THE SCREENSAVER " "
  65. 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 } ] ) };
  66. 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 } ] ) };
  67. 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
  68. 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
  69. 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 ); }
  70. 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 ] ); }
  71. 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 ] ); }
  72. 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 ] ); }
  73. 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 ] ); }
  74. None
  75. None
  76. 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 );
  77. 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
  78. 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
  79. None
  80. None
  81. But: Follow OS conventions!

  82. github.com/electron/electron/blob/master/docs/api/menu.md Application Menus if ( process.platform === 'darwin' ) {

    ... } if ( process.platform === 'win32' ) { ... } if ( process.platform === 'linux' ) { ... }
  83. 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 ); }
  84. 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 ); }
  85. None
  86. None
  87. blocky - 72 LOC - native functionality is extremely easy

    to implement github.com/stefanjudis/blocky
  88. slack://T082W2AN6/magic-login/2...

  89. slack://T082W2AN6/magic-login/2...

  90. Remember There is always new stuff to learn

  91. None
  92. menubar High level way to create menubar desktop applications with

    electron github.com/maxogden/menubar
  93. menubar iconTemplate.png iconTemplate@2x.png index.html main.js package.json

  94. main.js Browser process const menubar = require( 'menubar' ); const

    mb = menubar(); mb.on( 'ready', () => { console.log( 'app is ready' ); } );
  95. 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>
  96. 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
  97. 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
  98. 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
  99. None
  100. None
  101. None
  102. None
  103. PODCAST APP - real world examples - I NEED A

    TOOL TO TO LISTEN TO PODCASTS. " "
  104. None
  105. 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>
  106. 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
  107. 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
  108. None
  109. None
  110. 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
  111. 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 );
  112. ( 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
  113. None
  114. None
  115. showItemInFolder openItem openExternalUrl moveItemToTrash require( 'electron' ).shell beep

  116. webview example - productivity boost - default system functionality is

    extremely easy to implement
  117. nativefier Desktop all the things! github.com/jiahaog/nativefier/

  118. None
  119. None
  120. FORREST - real world examples - I NEED A TOOL

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

    ); ipcRenderer.send( 'delete-repo', repo.id ); - one renderer process -
  124. 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 -
  125. 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 -
  126. 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 -
  127. 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 -
  128. 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 -
  129. 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
  130. 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
  131. 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 -
  132. 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 -
  133. 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
  134. How hard can it be to build proper terminal output?

    Photo by Craig Sunter
  135. 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 -
  136. 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 -
  137. 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 -
  138. 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 -
  139. I was stuck for several days! Photo by madstreetz

  140. hyper github.com/zeit/hyper

  141. 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 -
  142. 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 -
  143. 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 -
  144. 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 -
  145. hterm www.npmjs.com/package/hterm-umdjs

  146. None
  147. Community Photo by rotesnichts

  148. CONTRIBUTORS 626

  149. ELECTRON RELATED PACKAGES ON NPM 1600+

  150. COMMUNITY www.facebook.com/groups/electronjs/ atom-slack.herokuapp.com/

  151. github.com/sindresorhus/awesome-electron AWESOME-ELECTRON

  152. github.com/electron-userland ELECTRON USERLAND

  153. Distribution tooling (from the community) Photo by GrahamAndDairne

  154. github.com/electron-userland/electron-packager ELECTRON-PACKAGER

  155. None
  156. None
  157. blocky.app > 200MB

  158. github.com/electron-userland/electron-builder ELECTRON-BUILDER

  159. 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" } }
  160. 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" } }
  161. 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" } }
  162. DX (developer experience) matters Photo by Thomas Leuthard Vladimir Krivosheev

    @develar
  163. It should just work! Photo by Thomas Leuthard

  164. 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 <stefanjudis@gmail.com>", "license": "MIT", "devDependencies": { "electron": "^1.4.15", "electron-builder": "^13.5.0" } }
  165. 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 <stefanjudis@gmail.com>", "license": "MIT", "devDependencies": { "electron": "^1.4.15", "electron-builder": "^13.5.0" } }
  166. DISTRIBUTION - electron-builder -

  167. 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 <stefanjudis@gmail.com>", "license": "MIT", "devDependencies": { "electron": "^1.4.15", "electron-builder": "^13.5.0" } }
  168. None
  169. None
  170. Where are we today?

  171. None
  172. None
  173. None
  174. None
  175. None
  176. None
  177. None
  178. None
  179. None
  180. None
  181. None
  182. None
  183. None
  184. “Web Dev is still delightfully fascinating and surprising and interesting

    and wonderful!” Laurie Voss Photo by eltpics
  185. 160 My missing piece SERVER WEB IOT DESKTOP

  186. Passion is everything!

  187. Thanks! @stefanjudis Slides Feedback bit.ly/desktop-all-the-things bit.ly/desktop-all-the-things-feedback

  188. AWESOME APPS HYPER.IS ATOM.IO CODE.VISUALSTUDIO.COM THOMAS101.GITHUB.IO/WMAIL BRAVE.COM GETKAP.CO MEETFRANZ.COM SLACK.COM

    GITHUB.COM/STEFANJUDIS/FORREST
  189. Be careful

  190. Renderer process memory management