Author: Eftakher Sazid
Designation: Intern at Vivasoft LTD

Javascript and its available incredible user-friendly frameworks make it very easy to make web applications. But as it runs only on the web and browsers, it is not possible to create a desktop application using Javascript. Here ElectronJS comes to the rescue.

ElectronJS is an open-source framework that allows us to create desktop applications using web technologies. That means Javascript, HTML, CSS, and their entire arsenal of frameworks. That makes it significantly easy to design and create a GUI for a desktop application.

ElectronJS has all the tools as the browser and some additional tools to access the file system for reading and writing operations. So it is not a browser but more. It is pretty easy to create an application in development mode but the tricky part comes while packaging the app for production. We’ll explore all the challenges of creating one.

The very first step is to create a project. Now we can create a Javascript and HTML-only project pretty easily but the hassle begins while adding the frameworks like React and webpack. Now boilerplates for electron applications have solved the hassle of manually configuring the project along with all its dependencies. Popular boilerplates like electron-react-boilerplateelectron-forgeelectron-builder are widely used.

For my application, I used the electron-react-boilerplate. It has support for TypeScriptReactreact-router-dom out of the box and it uses electron-builder to package the application. It has a pretty straightforward project setup that you can find here. Then after project setup, you should get rid of all the unnecessary pre-installed dependencies and config files. Let me name some of them, .github.git(obviously, you don’t want to use that git), CHANGELOG.mdCODE_OF_CONDUCT.mdsrc/__test__ if you don’t need them.

The first issue that you may face that electron-react-boilerplate has all the support needed for Typescript but if you use Javascript only like I did, you have to change the .erb/webpack.config.base.js file in the project directory. By default, while packaging this application using the electron-builder it will only accept files with the .tsx extension.

import path from 'path';
import webpack from 'webpack';
import { dependencies as externals } from '../../src/package.json';

export default {
  externals: [...Object.keys(externals || {})],

  module: {
    rules: [
      {
        test: /\.tsx?$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            cacheDirectory: true,
          },
        },
      },
    ],
  }
}

Now to configure for accepting files with .js.jsx and .ts extensions we need to change,

import path from 'path';
import webpack from 'webpack';
import { dependencies as externals } from '../../src/package.json';

export default {
  externals: [...Object.keys(externals || {})],

  module: {
    rules: [
      {
        test: /\.ts|\.tsx|\.jsx|\.js?$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            cacheDirectory: true,
          },
        },
      },
    ],
  }
}

Now we are good to go.

The second issue that I faced was using third-party UI libraries like semantic-uiant-design was that even after installing the dependencies the imports of their CSS files in the index.tsx was not working.

import 'react-datepicker/dist/react-datepicker.css';
import 'antd/dist/antd.css';

Now to solve this you have to add them inside the <head> tag of your src/index.html file as <link/> .

<head>
  <meta charset="utf-8" />
  <title>App name</title>
  <link
    rel="stylesheet"
    type="text/css"
    href="../node_modules/react-toastify/dist/ReactToastify.css"
  />
  <link
    rel="stylesheet"
    type="text/css"
    href="../node_modules/antd/dist/antd.css"
  />
  ...
</head>

Now all the CSS property of the UI components will be working fine.

For my project, I had to run the executable of my backend when my app was starting. To run an external application electron can use the exec and execFile from the child_process of the node.

import { exec, execFile } from 'child_process';

The execFile executes the executable for my backend. You can run pretty much any executable using this.

execFile(
  backEnd,
  {
    windowsHide: false,
  },
  (err, stdout, stderr) => {
    if (err) {
      console.log(err);
    }
    if (stdout) {
      console.log(stdout);
    }
    if (stderr) {
      console.log(stderr);
    }
  }
);

To close the backend while closing the app you can use exec.

exec(`taskkill /f /t /im ${executableName}`, (err, stdout, stderr) => {
  if (err) {
    log.error(err);
    console.log(err);
    return;
  }
  console.log(`stdout: ${stdout}`);
  console.log(`stderr: ${stderr}`);
});

 

For this to work, I kept my backend in the src/Backend folder. Now to package this along with all the dependencies I need to add this folder to the package.json file. Any file or folder included in the “files” array, will be added to the app.asar file. That does not solve it, because while packaging using electron-builder all these files are exported to a format called .asar format. The problem with this format is that no executable or app can be executed from within this .asar format. So we have to unpack the backend from the packaged app.asar file. The file specified in the “asarUnpack” will be unpacked from the app.asar and will be executable.

"build": {
    "productName": "App name",
    "appId": "org.erb.appName",
    "asarUnpack": [
      "Backend/bin/*"
    ],
    "files": [
      "dist/",
      "node_modules/",
      "index.html",
      "main.prod.js",
      "main.prod.js.map",
      "package.json",
      "Backend"
    ],
 ....
    }
  },

Now while packaging yarn package will create the native installer for the OS in the release folder in the root directory.

To make OS-specific build:

  • Linuxyarn package --linux
  • Windowsyarn package --win
  • macOSyarn package --mac

The final build doesn’t have any dev-tool support, so enable dev-tool in build version:

  • Build debugyarn cross-env DEBUG_PROD=true yarn package

This installer will be enough to run the application on any system.