The State of Desktop Applications in Node.js
Though Node.js is known for being a server-side platform, interest in using it for building desktop applications is growing rapidly. Bindings exist for GUI toolkits such as GTK+, QT and Cocoa. However, one of the advantages of using Node.js for web development is the ability to use the same language on both the server and client.
The most basic way of running a "desktop" application is to run a local server and use the web browser as the user interface. Chrome's command line exposes an extra flag to run itself in "application" mode, hiding everything but the web view.
One example of this is Morkdown, a cross-platform application to edit GitHub Flavoured Markdown. It starts a Node.js HTTP server locally on a random port, then launches Chrome using
'--app=http://localhost:' + port as a command-line flag.
There are a few downsides to this approach. To be able to use the application, the user will need to have both Node.js and Chrome (or Chromium) installed on their computer. Ideally, desktop applications should be self-contained, allowing the user to launch it and use it straight away without runtime pre-requisites.
Chrome applications don't feel entirely like desktop applications. Outside of the web view, operating-system-specific features and UI items can't be modified, and it isn't possible to brand the application (it will appear as another Chrome window). Since the application is running in Chrome, users still have access to regular web browser features, and can open new windows and tabs and even the Chrome Developer Tools.
The need to have a server running to access the Node.js APIs means having two parts to the application: a HTTP API or WebSocket interface for the browser to talk to, and a web frontend to communicate with the server. This results in an undesirable layer whereby you have to write the server and the client separately, without the ability to run Node.js within the frontend.
node-webkit is a web browser shell built on Chromium, allowing for the Node.js API to be used alongside the DOM API within the same context. As a basic example, you could replace the contents of
body with a file read via
fs.readFile in a
script tag like so:
It's also possible to use modules from npm and require them the exact same way. Native addons are also supported to extend both node and node-webkit, however they must be built using nw-gyp. node-webkit comes with a library to manipulate external parts of the shell, including the menu bar, tray icons and clipboard.
node-webkit has been used to build many high-profile applications, including Popcorn Time, Light Table and Komanda.
node-webkit applications are configured via a
window key in
package.json, outlining various properties of the application such as the entry document, width and height amongst others.
node-webkit has a major advantage to Chrome applications, as both DOM manipulation and Node.js API calls can be used in the same context without needing to run a separate server. The only caveat to this is that modules pulled into the application via
Due to the nature of how node-webkit is built, it can take some time before new Chromium builds are pulled in. node-webkit modifies some of Chromium's code to integrate the Node.js event loop, and as such, a direct pull from upstream isn't feasible.
atom-shell–similarl to node-webkit–is a shell built using components from Chromium. It was designed for the Atom text editor developed by GitHub, with slightly different architectural goals. Rather than needing the entire Chromium source, atom-shell only builds libchromiumcontent. Building atom-shell is much faster than building node-webkit as a result. atom-shell uses an alternative method of integrating libuv with Chromium's event loop, as well as using an upcoming feature in Node.js 0.12, multi-context.
There haven't been many large applications built with atom-shell apart from Atom itself, however atom-shell is fully documented.
The distinction between the browser shell and its runtime allow for cleaner organisation of code that deals with application state management and the logic needed to power the UI, compared with node-webkit. Unlike node-webkit, application configuration is done via an entry script rather than an object in
package.json. As a result, atom-shell is more flexible than node-webkit for application state customisation at startup.
Unlike node-webkit, atom-shell uses APIs exposed by libchromiumcontent instead of modifying Chromium directly, allowing easier upgrading of the renderer. This results in new Blink features being brought into atom-shell at a quicker pace than node-webkit.
Upsides and Downsides
Chromium shells that embed Node.js–such as node-webkit and atom-shell–come with many upsides and downsides, especially when compared with "native" applications.
- Access to the latest web technologies available in Blink
- Easy to package the application for end users
- If you already have an remote web application, you can already reuse most of that codebase to build a desktop application
- Compared to native applications, desktop web applications typically require a much larger amount of RAM and CPU power to run and render
- In terms of appearance – if you want to make your application look good on the platform you're planning on deploying to – you'll need to either recreate common UI elements using CSS or create a totally new UI, including a new design for every operating-system-provided UI element such as the title bar, menu bar and context menus.