Intro
I recently had the opportunity to conduct an Electron workshop at ng-conf 2016 and I wanted to share my slides and project, as well as a few screencasts (since I could not help myself). For the uninitiated, Electron is a runtime that allows you to package your HTML web applications into native desktop applications. Native programming is something that I looooooooooooooooove… trying to learn how to like even a little (Brian Regan nod). Thankfully, Electron makes it ridiculously easy!
Our sample application is an Instagram style application build in Electron and after investing a half of a million dollars in market research, we are confident that the brand Electrogram is going to take off. Download the repo and follow along with the screencasts to get your learn on!
CodeThe Slides
Hello Electron!
Let us take a moment to do a quick overview of how Electron works and what a basic app looks like. Electron has two main components: the main process and the renderer process. The main process is what runs when package.json is executed and is responsible for creating the BrowserWindow object which our renderer process runs in.
There are three main files that compose an Electron application. The index.html file contains normal HTML markup. By itself, it would simply serve up in a browser just like any other website. This is also where you will include all your CSS and JS, just like any other web app.
The main.js contains the logic that bootstraps the Electron app. It is basically just a node script that can open windows and tabs, and provide much more. Finally, package.json contains all of the necessary NPM packages for the app. The important piece of this file is the “main” attribute: this is what the packager uses when you want to build the app for native operating systems.
Getting started
To use electron globally, run npm i -g electron-prebuilt. When you want to run an electron application, run electron
Adding Angular 2
Structure
We generally like to include all Angular code in a sub-directory, usually src or app[. Any files that deal strictly with Electron we keep in the root folder; unless, of course, there are a bunch of files, in which case it would make sense to put supporting files in a sub-directory.
Integration
You can reference your javascript files in index.html, just like any other web application or site, and then run electron. Since new windows are just web pages served using Chromium and built off of HTML files, your Angular app will be bootstrapped as soon as the window is opened and will behave identically to a web page.
Webpack
Since we are just serving HTML and CSS, Webpack will still work as before. The main difference for us was that we added a target attribute to the webpack config and set it to electron-renderer. This gave us the ability to import node and electron modules directly.
Typings
Since the Angular app is in typescript and we want to integrate some Electron modules, we had to install the electron typings to use electron packages in our code.
Notifications
HTML5 provides an amazing notification API that is incredibly simple to use. Electron connects the HTML5 notification API with the native notification API; the result is that you write HTML5 notifications in your app, but the end user sees native notifications. To create a notification in your app, follow this format:
let myNotification = new Notification('Image Saved', { body: fileName });
Here we are creating an instance of the Notification class, passing in a title and a configuration object, and that is it. Now when an image is saved, we get a native notification alerting us.
Inter-Process Communication (IPC)
Inter-process communication (IPC) is handled by two different modules: ipcMain in the main process and ipcRenderer in the renderer process. One scenario for IPC is calling a “Save As” task from the main menu of the app. The menu was built in the main process, but files are saved in a renderer process so we need a way to communicate.
When someone clicks the “Save As” menu item, we want to broadcast a save-file message from mainWindow.webcontents. Any listener attached to the save-file channel will be notified and its callback invoked.
// main.js (main process) let mainWindow = new BrowserWindow({width: 800, height: 600}); ... fileMenu.submenu .find(item => item.label === 'Save As...') .click = () => mainWindow.webContents.send('save-file')
We use ipcRenderer to listen for the save-file event in the renderer process and call the class method save. This is an easy way to get communication from the main process to the renderer processes.
// app.ts (renderer process) import {ipcRenderer} from 'electron'; constructor() { ipcRenderer.on('save-file', this.save.bind(this)); }
Packaging Electron
It used to be that we had to worry about packaging for each OS ourselves and it involved a lot of moving pieces. Fortunately, a package has been written that abstracts most of the logic away and allows you to run one script and build for most any OS.
First, you will need the electron-packager npm module via npm i -g electron-packager.
To use this package, just run electron-packager on the command line. The OS, architecture, and other options are set via flags:
electron-packager . <appName> --platform=<platform> --arch=<architecture> --out=<outputDirectory> --overwrite --icon=path/to/custom/icon --asar
For the case of our particular app it was:
electron-packager . Electrogram --platform=darwin --arch=x64 --out=releases/ --overwrite --icon=src/assets/images/electrogram --asar
What we essentially accomplished was taking the current working directory, packaging it with the name “Electrogram”, targeting OSX on the x64 architecture, outputting the result to the “releases” directory, overwriting the OSX package in “releases” if it exists already, using the specified icon, and archiving the source code.
Here is a list of the options we can use:
- platform: the OS you are targeting… could be one or more of darwin, linux, mas, win32 (comma-delimited if multiple)
- arch: the architecture you are targeting… could be one of all, ia32, x64
- out: where the resulting files will go
- overwrite: whether or not to replace the directory for a platform if it has already been created
- icon: a custom icon to use for the app… see “custom icon” section below
- asar: packages the source code within your app into an archive
Custom Icon
We can also specify a custom icon for our app when we package it for distribution. The first thing we need to do is to create an icon for the correct format for our OS. For Mac, it is .icons and for Windows and Linux it is .png or .ico. Note, you do not need to specify the extension for the icon which allows us to create an icon in different formats and let our packager sort it out for us.
Resources
CodeElectron Documentation
electron-packager Documentation
Building a desktop application with Electron Great article!
How do you get around the error that Angular throws when the base href is not set? I am in a situation where if I set the base href (either using or APP_BASE_HREF) then it works in the browser, but Electron tries to load files from the file system root. I see in your electrogram source code that you don’t seem to set the base href.
Nice example. But I wanted to know about the security part of electron app. How secure electron apps are or how to make them more secure?
Thanks in advance
Rahul
@Matt Langston
You may figure it out my now (~6 months late), but you should set the base href to “./”. This won’t break the web part of the application, and will make electron part work as well.
I hope you find it useful, or anyone else. :D.
BTW: Great article. I’m just doing this, and it’s driving me crazy. :P.
Sorry if this is a newbie question, but I am transitioning to ES2015 and TypeScript and NG2 so many things are new to me.
I am trying to use your example with the NG2 Tour of Heroes tutorial.
The big problem I am facing is that lite-server uses bs-config.json (from what I can tell) to find the required node modules.
But when I launch in Electron, I just get errors about files not being found (mostly those from “node_modules” directory).
Have you gotten the “Tour of Heroes” demo running in electron? I can’t imagine that electron is so brittle as to the directory layout and location of the source code and dependencies so I know I am doing something wrong here.
Hi,
how did you get access to the webpack.config.js ? did you use ngEject?
Thank you
Christo
My Angular app has to use a base-href value of ./v3. I want Electron to respect that, which it does when I run it manually. However, when I package or build it using electron-packager or electron-builder it does not respect it… it seems to use a base-href of / Is there a command line param that will force electron to adhere to my desired base-href?