Book Image

Cross-platform Desktop Application Development: Electron, Node, NW.js, and React

By : Dmitry Sheiko
Book Image

Cross-platform Desktop Application Development: Electron, Node, NW.js, and React

By: Dmitry Sheiko

Overview of this book

Building and maintaining cross-platform desktop applications with native languages isn’t a trivial task. Since it’s hard to simulate on a foreign platform, packaging and distribution can be quite platform-specific and testing cross-platform apps is pretty complicated.In such scenarios, web technologies such as HTML5 and JavaScript can be your lifesaver. HTML5 desktop applications can be distributed across different platforms (Window, MacOS, and Linux) without any modifications to the code. The book starts with a walk-through on building a simple file explorer from scratch powered by NW.JS. So you will practice the most exciting features of bleeding edge CSS and JavaScript. In addition you will learn to use the desktop environment integration API, source code protection, packaging, and auto-updating with NW.JS. As the second application you will build a chat-system example implemented with Electron and React. While developing the chat app, you will get Photonkit. Next, you will create a screen capturer with NW.JS, React, and Redux. Finally, you will examine an RSS-reader built with TypeScript, React, Redux, and Electron. Generic UI components will be reused from the React MDL library. By the end of the book, you will have built four desktop apps. You will have covered everything from planning, designing, and development to the enhancement, testing, and delivery of these apps.
Table of Contents (9 chapters)

Fulfilling the functional requirements

We've described the semantic structure of our application with HTML. We have defined with CSS how our UI elements shall look. Now, we will teach our application to retrieve and update the content as well as to respond to user events. Actually, we will allocate the following tasks to several modules:

  • DirService: This provides control on directory navigation
  • FileService: This handles file operations
  • FileListView: This updates the file list with the data received from DirService, handles user events (open file, delete file, and so on) using FileService
  • DirListView: This updates the directory list with the data received from DirService and handles navigation events using DirService
  • TitleBarPath: This updates the current location with the path received from DirService
  • TitleBarActions: This handles user iteration with title bar buttons
  • LangSelector: This handles user iteration with language selector

However, before we start coding, let's see what we have in our arsenal.

NW.js gets distributed together with the latest stable version of Node.js, which has a great support for ES2015/ES2016 (http://node.green). It means that we can use any of the inherent new JavaScript features, but modules (http://bit.ly/2moblwB). Node.js has its own CommonJS-compliant module loading system. When we request a module by path, for example, require( "./foo" ), the runtime searches for a corresponding file (foo.js, foo.json, or foo.node) or a directory (./foo/index.js). Then, Node.js evaluates the module code and returns the exported type.

For example, we can create a module that exports a string:

./foo.js

console.log( "foo runs" ); 
exports.message = "foo's export";

and another one, which imports from the first module:

./bar.js

const foo = require( "./foo" ); 
console.log( foo.message );

If we run it, we get the following:

$node bar.js
foo runs
foo's export

One should note here that regardless of how many times we require a module, it gets executed just once, and every time, its exports are taken from the cache.

Starting with ES2015

As I have already mentioned, NW.js provides a complete support of JavaScript of ES2015 and ES2016 editions. To understand what it really means, we need a brief excursion into the history of the language. The standardized specification for JavaScript was first released in 1997 (ECMA-262 1st Edition).

Since then, the language has not really changed for 10 years. The 4th edition proposed in 2007 called for drastic changes. However, the working group (TC39) failed to agree on the feature set. Some proposals have been deemed unsound for the Web, but some were adopted in a new project code named Harmony. The project turned into the 6th edition of the language specification and was released in 2015 under the official name ES2015. Now, the committee is releasing a new specification every year.

New JavaScript is backward compatible with an earlier version. So, you can still write code with the syntax of the ECMAScript 5th edition or even 3rd one, but why should we lose the opportunity to work with the new advanced syntax and feature set? I think it would be helpful if we now go through some new language aspects that will be used in the application.

Scoping

In the old days, we used to always go with the var statement for variable declarations. ES2015 introduces two new declaration variables--let and const. The var statement declares a variable in a function scope:

(function(){ 
var foo = 1;
if ( true ) {
var foo = 2;
console.log( foo );
}
console.log( foo );
}()); $ node es6.js
2
2

A variable declared with var (foo) spans the entire function scope, meaning that every time we reference it by name, we target the same variable. Both let and const operate on block scopes (if statement, for/while loops, and so on) as shown:

 (function(){ 
let foo = 1;
if ( true ) {
let foo = 2;
console.log( foo );
}
console.log( foo );
}()); $ node es6.js
2
1

As you can see from the preceding example, we can declare a new variable in a block and it will exist only within that block. The statement const works the same, except it defines a constant that cannot be reassigned after it was declared.

Classes

JavaScript implies a prototype-based, object-oriented programming style. It differs from class-based OOP that is used in other popular programming languages, such as C++, C#, Objective-C, Java, and PHP. This used to confuse newcomer developers. ES2015 offers a syntactic sugar over the prototype, which looks pretty much like canonical classes:

 
class Machine {
constructor( name ){
this.name = name;
}
}
class Robot extends Machine {
constructor( name ){
super( name );
}
move( direction = "left" ){
console.log( this.name + " moving ", Robot.normalizeDirection( direction ) );
}
static normalizeDirection( direction ) {
return direction.toLowerCase();
}
}

const robot = new Robot( "R2D2" );
robot.move();
robot.move( "RIGHT" ); $ node es6.js
R2D2 moving left
R2D2 moving right

Here, we declare a Machine class that during instantiation assigns a value to a prototype property, name. A Robot class extends Machine and, therefore, inherits the prototype. In subtype, we can invoke the parent constructor with the super keyword.

We also define a prototype method--move--and a static method--normalizeDirection. The move method has a so-called default function parameter. So, if we omit the direction argument while calling move method, the parameter automatically sets to "left".

In ES2015, we can use a short syntax for the methods and do not need to repeat function keywords with every declaration. It's also available for object literals:

 
const R2D2 = {
name: "R2D2",
move(){
console.log( "moving" );
},
fly(){
console.log( "flying" );
}
};

The template literal

Another great addition to JavaScript is template literals. These are string literals that can be multiline and can include interpolated expressions (`${expression}`). For example, we can refactor our move method body, as follows:

  
console.log( `
${this.name} moving ${Robot.normalizeDirection( direction )}
` );

Getters and setters

Getters and setters were added back in ES5.1. In ES2015, it was extended for computed property names and goes hand in hand with a short method notation:

 
class Robot {
get nickname(){
return "But you have to prove first that you belong to the Rebel
Alliance!";
}
set nickname( nickname ){
throw new Error( "Seriously?!" );
}
};

const robot = new Robot();
console.log( robot.nickname );
robot.nickname = "trashcan"; $ node es6.js
But you have to prove first that you belong to the Rebel Alliance!
Error: Seriously?!

Arrow functions

A function declaration also obtained syntactic sugar. We write it now with a shorter syntax. It's remarkable that a function defined this way (fat arrow function) automatically picks up the surrounding context:

class Robot extends Machine { 
//...
isRebel(){
const ALLOWED_NAMES = [ "R2D2", "C3PO" ];
return ALLOWED_NAMES.find(( name ) => {
return name === this.name;
});
}
}

When using old function syntax, the callback function passed to an array's method, find, would lose the context of the Robot instance. Arrow functions, though, do not create their own context and, therefore, outer context (this) gets in the closure.

In this particular example, as it often goes with array extras, the callback body is extremely short. So, we can use an even shorter syntax:

return ALLOWED_NAMES.find( name => name === this.name ); 

Destructuring

In new JavaScript, we can extract specific data from arrays and objects. Let's say, we have an array that could be built by an external function, and we want its first and second elements. We can extract them as simple as this:

const robots =  [ "R2D2", "C3PO", "BB8" ]; 
const [ r2d2, c3po ] = robots;
console.log( r2d2, c3po );

So here, we declare two new constants--r2d2 and c3po--and assign the first and the second array elements to them, respectively.

We can do the same with objects:

const meta = { 
occupation: "Astromech droid",
homeworld: "Naboo"
};

const { occupation, homeworld } = meta;
console.log( occupation, homeworld );

What did we do? We declared two constants--occupation and homeworld--that receive values from correspondingly named object members.

What is more, we can even alias an object member while extracting:

const { occupation: affair, homeworld: home } = meta; 
console.log( affair, home );

In the last example, we delegated the values of object members--occupation and homeworld--to newly created constants--affair and home.