Electron on Windows and Linux

Create offline executable from your online app

Posted by creatic on Tue 01 October 2019

One code - running online and offline

For a long time the developer had to decide whether to develop for the web or for an offline enironment. It required a different mindset, different approach and - in most cases - different audience. Nowadays this gap narrowed down drastically. One of the various techniques of developing to offline and online in parallel is Electron. I used it to create a Windows executable to the Simplex project. So basically what you see on the site also works in a Windows 10 environment.

Steps of Electron development

An Electron app requires to have a base index.html that contains the initial view of the app and the Javascript payload that contains the application logic itself. Having a database is also possible - I used sqlite and the npm library sqlite3. Of course when it comes to DB the app logic has to be aware if it runs offline (and uses sqlite) or online and uses the API to fetch and store data. So the DB related functions are created in a way that they work in either way depending on configuration. My development OS is Linux, so the first offline target was Linux, too. The development process is outlined quite detailed at Electron docs. The online app I wanted to create was built up from React and npm packages, so the basic code and file structure was already there.

The main steps are:

  • npm install --save-dev electron

  • create main.js which will be the core of your Electron app, like:

const { app, BrowserWindow, Menu  } = require('electron')
const os = require("os");
const fs = require("fs");
const path = require("path");

// 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 win

var DEBUG = false;

function createWindow () {
  // Create the browser window.
  win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      nodeIntegration: true
    }
  })

  // and load the index.html of the app.
  win.loadFile('electron/index.html')

  win.setFullScreen(true)

  // Open the DevTools.
  if (DEBUG) {
    win.webContents.openDevTools()
  }

  // Emitted when the window is closed.
  win.on('closed', () => {
    // 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.
    win = 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', () => {
  // On macOS 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', () => {
  // On macOS 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 (win === null) {
    createWindow()
  }
})
  • Create the index.html which is the main page of your app, like:
<!DOCTYPE html>
<html lang="en"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <meta name="description" content="">
  <meta name="keywords" content="">
    <meta name="author" content="pragai.robert@abasys.hu">
    <meta http-equiv="cleartype" content="on">

    <!-- Responsive and mobile friendly stuff -->
    <meta name="HandheldFriendly" content="True">
    <meta name="MobileOptimized" content="400">

  <link rel="stylesheet" href="./css/frontend.min.css">

  <title></title>
</head>
<body class="">
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div class="container">
      <div class="app" id="app">
        <!-- Here comes the App -->
      </div>
    </div>
  <script type="text/javascript" src="./js/config.js"></script>
  <script type="text/javascript">    
    window.sqlite3 = require('sqlite3').verbose();
  </script>
  <script src="./js/app.js"></script>
</body></html>
  • And prepare the package.json file, so the app can be built. Mine looks like:
{
  "name": "simplex_lp",
  "version": "0.1.0",
  "description": "Simplex V4",
  "main": "electron/main.js",
  "build": {
    "appId": "Abasys - Simplex LP",
    "mac": {
      "category": "elearning.simplex"
    },
    "files": [
      "src/**/*",
      "node_modules/**/*",
      "package.json"
    ],
    "productName": "Simplex LP"
  },
  "scripts": {
    "translations-scan": "i18next-scanner --config src/js/additionals/i18next-scanner.js src/js/**/*.js",
    "start": "electron .",
    "pack": "electron-builder --dir",
    "dist": "electron-builder",
    "rebuild": "electron-rebuild -f -w sqlite3",
    "postinstall": "electron-builder install-app-deps"
  },
  "keywords": [
    "SimplexV4"
  ],
  "author": "Robert Pragai",
  "license": "ISC",
  "devDependencies": {
...
  },
  "dependencies": {
...
  }
}

Running the Electron app

Having all prerequisites above you can now npm start which brings up the application window with the index.html file inside and your running Javascript code. If you use Gulp or any other automation tool you can simply "reload" the app while the code changes.

Building the executable on Linux

Sooner or later you are ok with the result and want to create an executable. I used electron-packager for this task. (npm install electron-packager -g) This command builds the Linux app using the configurations above:

electron-packager . simplex_lp --overwrite --platform=linux --arch=x64 --icon=electron/favicon/apple-icon.png --prune=true --out=release-builds

You get the output in the directory set by the --out parameter (here release-builds) and into separated subdirectories per target OS.

As sqlite3 requires native package electron-packager first tries to locate an already built binary or if there is none it builds one from source. This process of course needs some tools on the system you use. On Linux the NodeJS 8.0 and unzip is enough. On Windows however it is a bit trickier.

Building the executable on Windows

As my client wanted an executable on Windows I had to come up with an environment the sqlite3 native package could be compiled to my Windows app. Here is what I did:

  • Downloaded a Windows development image VirtualBox

  • This contains Visual Studio, but not all necessary parts. Open up the Visual Studio Installer and add "10.0.18362 or higher Windows 10 SDK", “Debugging Tools For Windows”, “Desktop development with C++” component and the “MFC/ATL support”.

  • Download Python 2.7

  • Download NodeJS

  • Use PowerShell

  • npm config set msvs_version 2017

  • npm config set python C:/Python/python.exe

Here are the possible caveats in Windows - Electron development: https://electronjs.org/docs/development/build-instructions-windows

When the installation is ready, you can simply run:

electron-packager . simplex_lp --overwrite --platform=win32 --arch=x64 --icon=electron/favicon/apple-icon.png --prune=true --out=release-builds

and the app is built into the release directory.