Thursday, March 5, 2020

Exposing Electron inside Aurelia App

Expose Electron inside Aurelia App


So far we have been able to load the Aurelia app inside an Electron wrapper and successfully build and deploy a desktop solution. In order to take the app to the next level and interact with the desktop we need to be able to expose the Electron API inside our Aurelia app.

If you have tried to include Electron in the Aurelia bundle you will quickly discover that this doesn't work. The reason is because Electron is an environment dependent package ... much like Cordova. It is a set of binaries tailored to the local install based on your machine OS. However the good news is that like Cordova, it exposes a javascript API to cross the bridge into the native OS binaries. But how do we expose that API inside Aurelia?

You may remember that Cordova generated an API in the form of a cordova.js file. We then created a bootstrap file to include the browser version of this file into our Aurelia app. Cordova exposes it's API as globals so we can just go ahead and use it inside the Aurelia app without the need to import any modules.

Electron Main and Child Processes

In order to help clarify our mission, we need some fundamental understanding of how electron works.

Main process -> When we launch our electron app we begin by running the main.js file in the app root. That file uses require to load the electron object model. We then use the electron model to open a Browser Window in which we launch the Aurelia app. You can think of the main process as the equivalent of a console window running node ... same as when we run the Aurelia app in debug mode at dev time.

Child process -> The child process loads our Aurelia app. This is a separate or spawned process running isolated from the main process. You can think of the child process as equivalent to the chrome browser hosting the application ... same as when we run the Aurelia app in debug mode at dev time.

There is no way for the Child process to communicate or pass events back to the Main process directly, however Electron has provided us with some tools to do exactly that!

These tools are readily available in the Main process, however we need to find a way to load them in the child process and this is where the difficulty resides.

Include Electron in Aurelia App-Bundle

Aurelia bundles all of the app dependencies into 2 javascript files. A vendor-bundle and an app-bundle. The bundler uses all the dependencies defined in the root package.json file. If you recall we cannot include electron in this file ... the builder complains. This is because the electron node package does not expose a "hard copy" javascript API ... instead the electron binaries produce a dynamic object model at runtime.

So we are left with trying to include electron in the app-bundle but fundamentally we have the same problem. The solution is to construct a polyfill that uses require to load an electron object model, then export that model as an AMD object that can be bundled in the app-bundle.

Electron Polyfill

  1. Add a new file called electron.js to the client directory
  2. Open the file in the editor and enter the following code
const _electron = window.nodeRequire
                  window.nodeRequire("electron") : null;
export default _electron;

We use window.nodeRequire here ... remember we had to add a chunk of script to the root/index.html page that resolves a bootstrapping conflict?

    <script>
      // following needed by electron
      // to eleiminate bootstrapping conflict
      if (typeof require !== 'undefined') {
        window.nodeRequire = require;
        delete window.require;
        delete window.exports;
        delete window.module;
      }
    </script>

The code form index.html exposes the require module used in the main process as window.nodeRequire. It has access to the node_modules folder where the electron binaries reside. The Aurelia loader uses an instance of require from the child process which only includes modules loaded by the Aurelia bundler and since electron is not included in the bundle the child instance of require cannot find the electron module.

The code from our electron polyfill uses require from the main process to load the electron object model then it exports that model as an AMD module that Aurelia can include in the app-bundle ... pretty slick!

Keep in mind that during dev when we are running the app in a chrome browser the window.nodeRequire does not get instantiated therefore electron will be set to null. This makes sense because the electron environment isn't running. However when we launch the electron app windows.nodeRequire is active and we can require electron and expose it through the app bundle!

Now you can simply import electron into any Aurelia module as follows

import electron from 'electron';
console.log('***** Electron =>'electron);

You can see the result in the developer tools window


If you inspect the same code running in dev in the browser you will see


By creating the polyfill in the client directory and naming it 'electron' the Aurelia bundler picks it up automatically and exposes it in the Aurelia require stack as 'electron' ... It doesn't get much easier than that!