Let’s Make Desktop Application With Ionic 3 and Electron

In this tutorial we will learn about Developing desktop applications with Ionic 3 and Electron. If you are new to these technologies, please visit my blog https://competenepal.com for further information or their official sites https://ionicframework.com and https://electron.atom.io.

You guys loved this tutorial so much that I had to Make a video tutorial. Here is the link to the video in YouTube. As this is my first video tutorial, I would love to hear feedback from you guys so that I can improve my next tutorials. Thank you all for your love and support

For now lets just understand that, Ionic framework is a Hybrid Mobile application framework that let’s us build mobile application with latest web technologies like HTML5, CSS and Javascript. And, Electron is Github’s framework for developing cross-platform desktop application using same latest web technologies. So as both use latest web technologies they must be compatible.

Ionic was specially configured for mobile applications development. But now, with the addition of components like Split Pane and Responsive Grid, it is easier than ever to develop a desktop application using Ionic Framework, a popular HTML 5 framework for mobile applications development. Until and official support is on it’s way, we want to use electron to develop a desktop application using ionic framework.
 In this tutorial we will

  1. Create an Ionic 3 project and serve
  2. Install electron dependencies in our project
  3. Add webpack config and electron main script and run project as electron application

In Part 2 of this tutorial we will

  1. Create a simple angular 2 service access electron apis
  2. Build a redistributable package of our application for different platforms

Final code for this tutorial including of part 2 can be found at the following github repository

https://github.com/lohanitech/ion-electron

So, let’s get started.

This tutorial assumes that you have already installed Ionic in your development machine. If you have not please go through our Getting started with Ionic tutorial or visit their official websites to find the details.

1. Create an Ionic Project and serve

If you have gone through our getting started with ionic 2 tutorial then, you know this step. It’s very easy.
 Go to your terminal and CD to your working directory, then type the following command
 ionic start ion-electron
 This startes a new project named ion-electron using default tabs starter template by ionic team and install all the dependencies using npm. When that’s done, cd to your ion-electron directory and type the following command
 ionic serve
 This will serve the project with live reloading support at http://localhost:8100, which you can view in your browser.

2. Install electron dependencies in our project

First add the electron dependencies into our project using the following command in the terminal.
 npm install electron electron-builder foreman --save-dev
 This is will install electron a desktop application framework developed by github, electron-builder, scripts that help us build electron based application for different platforms and foreman a node package that allows us to run multiple process simultaneously which is required in development mode to run ionic serve and electron at the same time from single command. After the dependencies are installed, open package.json for your ionic project and add the following fields. Some of the fields might already be there.

"name": "ion-electron",
"author": {
"name": "Damodar Lohani",
"email": "example@example.com",
"url": "https://lohanitech.com/members/damodar-lohani"
},
"description": "ionic framework based electron project",
"main": "electron/electron.js",
"config": {
"ionic_bundler": "webpack",
"ionic_webpack": "./config/webpack.config.js"
},
"build": {
"appId": "com.lohanitech.ionic-electron-test",
"electronVersion": "1.7.5",
"asar":false,
"files": [
"www/**/*",
"electron/*"
]
}

Here, name, author, description fields are required by electron-builder. The build field is the configuration for electron-builder package, the main field is required to run this application as electron application and the config field is for overriding the default webpack config to make it work with electron, which we will add next.

3. Add webpack config and electron main script and run project as electron application

Add webpack config

Inside the project directory, create a folder called config and a file inside config directory called webpack.config.js and paste the following code.

Update
Webpack config has changed with latest ionic update, so we need to change. How we made this webpack config? First get the original config file that comes with Ionic (as of now it is located at node_modules/@ionic/app-scripts/config/webpack.config.js) and in the original file add externals array

externals: [
(function () {
var IGNORES = ["fs","child_process","electron","path","assert","cluster","crypto","dns","domain","events","http","https","net","os","process","punycode","querystring","readline","repl","stream","string_decoder","tls","tty","dgram","url","util","v8","vm","zlib"];
return function (context, request, callback) {
if (IGNORES.indexOf(request) >= 0) {
return callback(null, "require('" + request + "')");
}
return callback();
};
})()
],

Latest webpack config is changed as follows (In the latest config file there are two places where we have to add externals array in dev config and production config):

/*
* The webpack config exports an object that has a valid webpack configuration
* For each environment name. By default, there are two Ionic environments:
* "dev" and "prod". As such, the webpack.config.js exports a dictionary object
* with "keys" for "dev" and "prod", where the value is a valid webpack configuration
* For details on configuring webpack, see their documentation here
* https://webpack.js.org/configuration/
*/
var path = require('path');
var webpack = require('webpack');
var ionicWebpackFactory = require(process.env.IONIC_WEBPACK_FACTORY);
var ModuleConcatPlugin = require('webpack/lib/optimize/ModuleConcatenationPlugin');
var PurifyPlugin = require('@angular-devkit/build-optimizer').PurifyPlugin;
var optimizedProdLoaders = [
{
test: /\.json$/,
loader: 'json-loader'
},
{
test: /\.js$/,
loader: [
{
loader: process.env.IONIC_CACHE_LOADER
},
{
loader: '@angular-devkit/build-optimizer/webpack-loader',
options: {
sourceMap: true
}
},
]
},
{
test: /\.ts$/,
loader: [
{
loader: process.env.IONIC_CACHE_LOADER
},
{
loader: '@angular-devkit/build-optimizer/webpack-loader',
options: {
sourceMap: true
}
},
{
loader: process.env.IONIC_WEBPACK_LOADER
}
]
}
];
function getProdLoaders() {
if (process.env.IONIC_OPTIMIZE_JS === 'true') {
return optimizedProdLoaders;
}
return devConfig.module.loaders;
}
var devConfig = {
entry: process.env.IONIC_APP_ENTRY_POINT,
output: {
path: '{{BUILD}}',
publicPath: 'build/',
filename: '[name].js',
devtoolModuleFilenameTemplate: ionicWebpackFactory.getSourceMapperFunction(),
},
externals: [
(function () {
var IGNORES = ["fs","child_process","electron","path","assert","cluster","crypto","dns","domain","events","http","https","net","os","process","punycode","querystring","readline","repl","stream","string_decoder","tls","tty","dgram","url","util","v8","vm","zlib"];
return function (context, request, callback) {
if (IGNORES.indexOf(request) >= 0) {
return callback(null, "require('" + request + "')");
}
return callback();
};
})()
],
devtool: process.env.IONIC_SOURCE_MAP_TYPE,
resolve: {
extensions: ['.ts', '.js', '.json'],
modules: [path.resolve('node_modules')]
},
module: {
loaders: [
{
test: /\.json$/,
loader: 'json-loader'
},
{
test: /\.ts$/,
loader: process.env.IONIC_WEBPACK_LOADER
}
]
},
plugins: [
ionicWebpackFactory.getIonicEnvironmentPlugin(),
ionicWebpackFactory.getCommonChunksPlugin()
],
// Some libraries import Node modules but don't use them in the browser.
// Tell Webpack to provide empty mocks for them so importing them works.
node: {
fs: 'empty',
net: 'empty',
tls: 'empty'
}
};
var prodConfig = {
entry: process.env.IONIC_APP_ENTRY_POINT,
output: {
path: '{{BUILD}}',
publicPath: 'build/',
filename: '[name].js',
devtoolModuleFilenameTemplate: ionicWebpackFactory.getSourceMapperFunction(),
},
externals: [
(function () {
var IGNORES = ["fs","child_process","electron","path","assert","cluster","crypto","dns","domain","events","http","https","net","os","process","punycode","querystring","readline","repl","stream","string_decoder","tls","tty","dgram","url","util","v8","vm","zlib"];
return function (context, request, callback) {
if (IGNORES.indexOf(request) >= 0) {
return callback(null, "require('" + request + "')");
}
return callback();
};
})()
],
devtool: process.env.IONIC_SOURCE_MAP_TYPE,
resolve: {
extensions: ['.ts', '.js', '.json'],
modules: [path.resolve('node_modules')]
},
module: {
loaders: getProdLoaders()
},
plugins: [
ionicWebpackFactory.getIonicEnvironmentPlugin(),
ionicWebpackFactory.getCommonChunksPlugin(),
new ModuleConcatPlugin(),
new PurifyPlugin()
],
// Some libraries import Node modules but don't use them in the browser.
// Tell Webpack to provide empty mocks for them so importing them works.
node: {
fs: 'empty',
net: 'empty',
tls: 'empty'
}
};
module.exports = {
dev: devConfig,
prod: prodConfig
}

This is the ionic webpack config with some modifications added to make the generated result work with electron.

Add electron main script

In the project folder create folder called electron and inside that folder create a file called electron.js. This is the main script for creating and loading electron browser window. Open electron/electron.js and paste the following code.

'use strict';
const electron = require('electron');
// Module to control application life.
const {
app } = electron;
// Module to create native browser window.
const {
BrowserWindow
} = electron;
let win;
function createWindow() {
// Create the browser window.
win = new BrowserWindow({
width: 1024,
height: 600
});
    var url = 'file://' + __dirname + '/../www/index.html';
var Args = process.argv.slice(2);
Args.forEach(function (val) {
if (val === "test") {
url = 'http://localhost:8100'
}
});
    // and load the index.html of the app.
win.loadURL(url);
    // Open the DevTools.
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 OS X 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 OS X 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();
}
});

This is just a basic script that creates a electron browser window and loads the application.

Add launch script to our package.json

Open package.json for the project and in the scripts field add following three scripts among already added

"scripts": {
"dev": "nf start",
"start":"ionic-app-scripts serve",
"electron dist": "electron .",
"ebuild":"npm run build && node_modules/.bin/build",
"clean": "ionic-app-scripts clean",
"build": "ionic-app-scripts build",
"ionic:build": "ionic-app-scripts build",
"ionic:serve": "ionic-app-scripts serve"
}

Now that the foreman is added, we need to make it’s config file. In the root directory of your project create a file named Procfile and write the following

ionic: npm start
electron: node electron-wait-ionic.js

Lets add electron-wait-ionic.js in the root of your project directory and update as follows.

const net = require('net');
const port = 8100;
process.env.E_URL = `http://localhost:${port}`;
const client = new net.Socket();
let startedElectron = false;
const tryConnection = () => client.connect({port: port}, () => {
client.end();
if(!startedElectron) {
console.log('starting electron');
startedElectron = true;
const exec = require('child_process').exec;
exec('electron .');
}
}
);
tryConnection();
client.on('error', (error) => {
setTimeout(tryConnection, 1000);
});

What this does is, it tries to connect to the port 8100, which is active when electron serves the project. If connection fails, it waits and tries again. If you use other port to serve using ionic, update the port in the file.

So now to run in development with all auto reload and everything as if you were developing ionic application in browser, just run npm run dev.

As electron runs the server before it fully compiles the file, for the first time you might get a blank window, if it happens, wait for electron to compile file (view log in your terminal) and then reload (ctrl + R) the electron browser window and everything works as expected.
 This should show the following window with ionic project loaded.

If you have finished this tutorial and want to access electron APIs from your Ionic project and to build your app for different platforms, visit Part 2 of this tutorial in my blog Competenepal.com

Like what you read? Give Damodar Lohani a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.