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-boilerplate, electron-forge, electron-builder are widely used.
For my application, I used the electron-react-boilerplate
. It has support for TypeScript, React, react-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.md
, CODE_OF_CONDUCT.md
, src/__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-ui
, ant-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:
- Linux:
yarn package --linux
- Windows:
yarn package --win
- macOS:
yarn package --mac
The final build doesn’t have any dev-tool support, so enable dev-tool in build version:
- Build debug:
yarn cross-env DEBUG_PROD=true yarn package
This installer will be enough to run the application on any system.