Telerik blogs
electron_header

This year at Build, Microsoft grandly announced Visual Studio Code, the first code editor from Microsoft that works on the Mac. A full featured desktop IDE. And the crowd goes wild.

I myself downloaded and used Visual Studio Code, and I gotta admit, it’s pretty good. Especially considering this is a v1 release we're talking about here.

But it didn’t take long for someone to uncover Code's little secret…

vs-code

Original Filename atom.exe? What does that mean?

What it means, is that Visuals Studio Code is not a Mac desktop application in the traditional sense. It is actually a hybrid application built with web technologies.

GitHub Atom

Atom is a code editor that was released by GitHub last year. As code editors go, it’s similar to the developer’s darling, Sublime Text, with one incredible difference: the Atom editor itself is HTML, CSS and JavaScript. It is then wrapped in a native shell with device access. This makes it appear as though it is a native desktop application, and for all intents and purposes, it totally is. If you've used Atom, you know how fast and good it is. As Steve Jobs would say, "It's no slouch."

GitHub split out and open sourced the portion of Atom that wraps the actual editor and called it Electron (originally it was called Atom Shell).

This is not the first time we've seen something like this. Adobe's Brackets editor was built the same way. Also, Google did it several years ago when they released Chrome Packaged Apps. I worked on a team here at Telerik that built the very first Camera application that shipped on ChromeOS. I know full well about what it's like to build a desktop application on web technologies. Let me be completely honest about my feelings around building desktop apps this way.

I LOVE IT.

Seriously. I had never built a desktop application so fast in my life. I thought "This is too good to be true!" And alas, it was.

Shortly after packaged apps were announced, they were relegated to the back corner of a Google open work area under a flickering florescent light somewhere in the damp basement of a Mountain View building next to a planter full of mushrooms. As far as I can tell, packaged apps are dead.

I knew that it was too good to be true. And then I met Electron.

Electron

Electron is essentially what Packaged Apps should have been (opinion mine - obviously). It is a brilliant implementation of the native-wrapper-for-a-web-app concept that we see so popular in technologies like PhoneGap. So what makes it better than Chrome Packaged Apps, or MacGap, or any of the other countless projects that have already tried to do this?

One word: Standards

Electron, unlike many of it's predecessors, relies almost entirely on web standards that you already know for project structure and modules. To show you what I mean, I'm going to retrofit a site that I did for the Kendo Flippable project into an Electron Desktop app. Before we do that though, I think it's worth discussing why one would build a desktop application.

When To Go Desktop

We often have a hard time determining when to build a desktop application (native), and when to provide a web application. There is little that one can do that the other cannot. For instance, one common misconception is to pose the question like this:

"Does the application need device access?"
A: YES - Native Desktop
B: NO - Web

Yet consider what the web is now capable of:

  • Camera Access
  • Microphone
  • Audio Manipulation
  • File System (to a degree)
  • Notifications
  • WebSockets
  • Accelerometer

…and the list goes on. Writing off the web because you need device access isn't an all or nothing argument anymore. So what are legitimate reasons to go the desktop route? The way I see it, the two main benefits are:

  1. No cross-browser compatibility issues
  2. No loading of remote assets (no latency)

There are some other potential benefits such as accessing connected devices (bluetooth, USB, etc.) and desktop apps are generally offline by deault, but these are my personal favorites.

The beauty of a desktop application is that you are bundling all your assets and code into a package which then loads directly from the user's machine. On the web, you have no idea where you application will end up running, and you analyze every byte going across the wire to ensure that's it's absolutely necessary. Desktop applications worry about none of these things.

Ideally, you would build a web application where you knew exactly what browser your application would run in (down to the version), and all your assets were already loaded locally.

This, my friend, is exactly what Electron allows you to do.

Creating A New Electron App

The first thing you will need to do is to get Electron. The best way to do that is to install it via npm.

npm install -g electron-prebuilt

This will install Electron on your system. You can now run any web application as an Electron app, provided you have the right config in your package.json file, and this is where we get back to the beauty of standards.

The nice thing about standards is that you have so many to choose from.

Andrew S. Tanenbaum

But seriously, Electron is fun to work with because it's already using concepts that you are most likely familiar with. One of those is the package.json file. No goofy manifest to work with. All you need to do to specify that your application is an Electron app is to add the "main" field to the package.json file. The following is a dead simple electron package.json.

{
    "name": "Sample App",
    "version": "0.0.1",
    "main": "main.js"
}

That `main.js` file is the key. It can be called whatever you want, but this is the file that Electron will execute when it loads your site. In that `main.js` file, there needs to be a little bit of boilerplate code to open the new browser window and kick the tires.

var app = require('app');  // Module to control application life.
var BrowserWindow = require('browser-window');  // Module to create native browser window.

// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the javascript object is GCed.
var mainWindow = null;

// Quit when all windows are closed.
app.on('window-all-closed', function() {
  if (process.platform != 'darwin') {
    app.quit();
  }
});

// This method will be called when Electron has done everything
// initialization and ready for creating browser windows.
app.on('ready', function() {
  // Create the browser window.
  mainWindow = new BrowserWindow({width: 800, height: 600});

  // and load the index.html of the app.
  mainWindow.loadUrl('file://' + __dirname + '/index.html');

  // Emitted when the window is closed.
  mainWindow.on('closed', function() {
    // 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.
    mainWindow = null;
  });
});

That was taken almost verbatim from the Electron docs, but I trimmed out a few lines that aren't necessary (like crash reporting and opening the developer tools when the app is launched).

Notice how the file is requiring in the "app" and "BrowserWindow" modules? This is just CommonJS. This is the second brilliant thing about Electron. It assumes that all your JavaScript files will be CommonJS. That means that there is no need for you to add in a build system to get module support for your scripts. We implemented this same concept in NativeScript and, to me, this is how life should be.

The main.js file is going to attempt to load a file called index.html. That is where you enter back into pure web territory. Your index.html file can contain anything you want! Any CSS you want, any JavaScript you want. Anything that goes on the web is legit in Electron.

<!DOCTYPE html>
<html>
<head>
  <title>Kendo Electron</title>
</head>
<body>
  <h1>Hello World!</h1>
</body>
</html>

If you are already in the project directory, you can now run this in Electron by executing electron . from the command line or terminal.

hello-world

Application vs. Render

Before we go any further, it's worth noting here how Electron runs applications. There are two threads: 1) the application thread and 2) the render thread. The application thread is what is running that main.js file and the render thread is running the index.html browser instance. The reason why this is important to know, is that the native features of electron are only available on the Application thread. This is a security feature that "sandboxes" the render thread so that malicious code cannot be executed at runtime.

That's all a bit confusing, so let's see how this plays out by looking at one of the Application methods.

On Windows, there is an API on the BrowserWindow object that allows you to leverage the built-in progress bar feature in Windows. We could write a set interval that kicks off this process from the main.js file.

// This method will be called when Electron has done everything
// initialization and ready for creating browser windows.
app.on('ready', function() {
  // Create the browser window.
  mainWindow = new BrowserWindow({width: 800, height: 600});

  // and load the index.html of the app.
  mainWindow.loadUrl('file://' + __dirname + '/index.html');

  // Emitted when the window is closed.
  mainWindow.on('closed', function() {
    // 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.
    mainWindow = null;
  });

  // if the current platform is windows...
  if (process.platform === 'win32') {
    var counter = 0;
    // every 1 second, increment the progress bar value
    var progress = setInterval(function() {
      if (counter < 1) {
        mainWindow.setProgressBar(counter);
        counter += .1;
      }
      else {
        mainWindow.setProgressBar(0);
        clearInterval(progress);
      }
    }, 1000);
  }

});

This API is only available on Windows. If you run this code on Mac, you will get an error. Tha's why the whole thing is wrapped in a process.platform conditional, making sure the platform is win32.

This is all running in that main.js which is the Application thread. What if we wanted to kick this progress bar action off from the UI? We would put a button in and handle it's click event.

<!DOCTYPE html>
<html>
<head>
  <title>Kendo Electron</title>
</head>
<body>
  <button id="start-progress">Start Progresss</button>

  <script src="index.js"></script>
</body>
</html>
// index.js
var button = document.getElementById('start-progress');
button.onclick = function() {
  // now what?
};

I split the JavaScript for the UI out into an index.js file and included it in the index.html page. I mentioned that Electron treats all JavaScript files like they are CommonJS modules. This might make you think that you could just export the BrowserWindow object from the main.js file and require it into the index.js file. Due to the restrictions on native methods from the render thread, this won't work. The module will come up undefined.

In order to execute application thread methods from the render thread, you have to use something called the ipc module.

IPC Module For Thread Communication

This is really simple stuff. In fact, this is exactly what I did back when I was building Chrome Packaged Apps since Packaged Apps have similar restrictions about what type of code is allowed to run on the same thread that has access to native features. I even open sourced that project as the Pkg plugin for jQuery.

The ipc module is incredibly similar. You dispatch a message from the render thread and the main thread picks it up.

First, we require in the ipc module in the main.js file, and then listen for an event that we'll ultimately dispatch from the index.js file.

var app = require('app');  // Module to control application life.
var BrowserWindow = require('browser-window');  // Module to create native browser window.
var ipc = require('ipc');

// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the javascript object is GCed.
var mainWindow = null;

// Quit when all windows are closed.
app.on('window-all-closed', function() {
  if (process.platform != 'darwin') {
    app.quit();
  }
});

// This method will be called when Electron has done everything
// initialization and ready for creating browser windows.
app.on('ready', function() {
  // Create the browser window.
  mainWindow = new BrowserWindow({width: 800, height: 600});

  // and load the index.html of the app.
  mainWindow.loadUrl('file://' + __dirname + '/index.html');

  // Emitted when the window is closed.
  mainWindow.on('closed', function() {
    // 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.
    mainWindow = null;
  });

});

// respond to the 'progress' event
ipc.on('update-progress', function(event, arg) {
  if (process.platform === 'win32') {
    mainWindow.setProgressBar(arg);
  }
});

We just need to send the `update-progress` event from the button click. That's done by requiring the same ipc module and then just executing `send`, with whatever parameters we want to pass. In this case, it's the current value of the counter.

// index.js
var ipc = require('ipc');

var button = document.getElementById('start-progress');
button.onclick = function() {
  var counter = 0;

  // increment the progress value by 0.1 every second
  var progress = setInterval(function() {
    if (counter < 1) {
      ipc.send('update-progress', counter);
      counter += 0.1;
    }
    else {
      // reset the progress value to 0;
      ipc.send('update-progress', 0);

      // clear out the set interval
      clearInterval(progress);
    }
  }, 1000);
};

Clicking the button now updates the progress bar in the taskbar icon. In addition, the value for the progress bar is being passed from the UI to the render thread. And that's all there is to it.

Native-ish Features

I mentioned earlier in the article about how much just the web on it's own can do. It can do so many native-ish things just by getting a little permission from the user. Since the user will be running our Electron app locally, we don't need to ask for permission, we already have it.

As an example, we could kick of a native notification just by using the Web Notifications API.

var ipc = require('ipc');

var button = document.getElementById('start-progress');
button.onclick = function() {
  var counter = 0;

  // increment the progress value by 0.1 every second
  var progress = setInterval(function() {
    if (counter < 1) {
      ipc.send('update-progress', counter);
      counter += 0.1;
    }
    else {
      // reset the progress value to 0;
      ipc.send('update-progress', 0);

      // clear out the set interval
      clearInterval(progress);

      // display a notification saying that we did everything
      new Notification('Progress Test Finished!');
    }
  }, 1000);
};

Now we can add in any JavaScript or CSS library we want. In my case, I built a small application that searches iTunes for artists, displays their albums, and plays the track samples. Of course, I used Kendo UI Core to build the application. Is there any other JavaScript library? :)

Here is a quick video of my demo application, including a native desktop notification.

Note: If the video is blurry, make sure you are watching it in 720p.

Introducing Proton

Visual Studio and Github aren't the only shops in town building IDEs on Electron. Telerik is currently working on a full fledged desktop client (code name "Proton") for building iOS and Android apps on any operating system. You may know this IDE in it's current incarnation as the AppBuilder Windows Client. It's been one of our most popular editors to date for building mobile applications, but it is currently Windows only (built in WPF). Electron gives us the power to take this same functionality, and make a cross-browser mobile development environment that you can love no matter what platform you're on.

Proton Beta Testers Wanted

We're getting close to releasing this new desktop IDE, but we need your help. If you're interested in getting your hands on an early release of Proton and don't mind giving us your raw feedback, we'd love to hook you up. Contact @rdlauer on Twitter. He's the keeper of the Proton keys and will make sure you're one of the first ones to experience this exciting new IDE

Thank You Notes

I wanted to wrap-up by sending out a "Thank You" to GitHub and the folks that work there for all they've done on Electron. Open source projects are sometimes a thankless job where mostly all you hear about are the problems your software has, not all of the wonderful things it does right. I'm incredibly thankful that GitHub has ressurected the ashes of what I loved about Google Packaged Apps and improved it many times over.

You can find the code for the iTunes Artist Search app on GitHub. Just make sure you have Electron Prebuilt installed and you're good to go.

Make sure you also grab Kendo UI Core, either from Bower, JSPM, GitHub or however you like to install your packages. I really enjoyed putting this project together and had a lot of fun working with Kendo UI and Electron. Desktop development should be as easy as web development, and Electron + Kendo UI makes that a reality.


Burke Holland is the Director of Developer Relations at Telerik
About the Author

Burke Holland

Burke Holland is a web developer living in Nashville, TN and was the Director of Developer Relations at Progress. He enjoys working with and meeting developers who are building mobile apps with jQuery / HTML5 and loves to hack on social API's. Burke worked for Progress as a Developer Advocate focusing on Kendo UI.

Comments

Comments are disabled in preview mode.