This post is trending. 5,978 Views

Let’s Make Desktop Application With Ionic 2 and Electron: Part 1

With the addition of components like Split Pane and Responsive Grid, it is now 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

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.

Update

I have updated this project to make it easier for development without running two terminal instance to serve and load electron. If you have already gone through this tutorial before, go directly to the updates part. Also I have updated the github repository with latest Ionic and electron version.

  1. Create an Ionic 2 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

So, let’s get started.
If you are just getting started with ionic, please go through our Getting started with Ionic 2 tutorial.

1. Create an Ionic 2 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 --v2
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. These steps have already been covered and explained in our Getting Started with Ionic 2 Tutorial.

2. Install electron dependencies in our project

First add the electron dependencies into our project using the following command in the terminal.
npm install @types/electron electron electron-builder --save-dev
This is will install electron a desktop application framework developed by github, @types/electron, required for using electron library with typescript, and electron-builder, scripts that help us build electron based application for different platforms. 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 2 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.6.2",
    "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 2 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": {
    "electron": "electron .",
    "electron dist": "electron .",
    "ebuild":"node_modules/.bin/build",
    ......
}

Now go to the terminal and inside your ion-electron project directory type the following command
npm run electron
This should show the following window with ionic project loaded.
ion8-final.png

For support of live reloading while developing ionic application in electron use the following commands.
In terminal 1, type ionic serve and in terminal 2 type npm run electron test.

This should display following window with your ionic 2 project loaded and if you make any changes to your project file, the window will reload to load the changes.

Update

I know, it might have been difficult for you to load application in development mode, because we had to open two terminals and first we had to run ionic serve to serve project locally, when the serving was working only then in another terminal run npm run electron.
In this update we are solving this problem by using foreman package.

So let’s add the dependency. In terminal inside your project directory run

npm install --save-dev foreman

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.

We also need to update the electron.js file to use the port E_URL in development mode. So open electron/electron.js file in your editor and look at the following lines inside createWindow() function

var url = 'file://' + __dirname + '/../www/index.html';
var Args = process.argv.slice(2);
Args.forEach(function (val) {
    if (val === "test") {
        url = 'http://localhost:8100'
    }
});

Update the above code as

var url = process.env.E_URL || url.format({
    pathname: path.join(__dirname, '/../www/index.html'),
    protocol: 'file:',
    slashes: true
});

Finally, we need to add the start script in our package.json. Open package.json and make the scripts object as follows

"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"
  }

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.

Here, we have also modified ebuild script so that it first builds ionic project then builds using electron builder.

with this update I have updated the github repository, with the latest code and latest version of electron and ionic.

If you have finished this tutorial, visit Part 2 of this tutorial to learn to access electron APIs from inside your Ionic project.

Related Posts

%d bloggers like this: