Saturday, June 03, 2017

Nw.js vs. Electron

Today, I tried porting an html5 web app of mine into a desktop application. When it comes to running JavaScript programs on the desktop, there are two main choices: node-webkit (nw.js) and Electron. I wasn't sure which one to choose. I didn't think that my web app was very complicated, so I decided to use nw.js. It's simpler, older, has an easier programming model, and I've been happy when I've used apps based on nw.js in the past.

Using nw.js was great. It was so simple and easy to use. I just unzipped nw.js somewhere, dropped my own web pages there, and off it went. It was nothing like the days and days of agony involved in making a Cordova app. The amount of documentation was very manageable, so I was soon diving through it to figure out various polishing issues. And it was all pretty simple. Fixing the taskbar icon was one line. Making it remember the window size from when it was last closed--also one line. Putting in a "save as" dialog was a little more work, but, again, nothing to sweat about.

Then, I decided that I wanted the save dialog to default to saving to Windows' My Documents folder. And that was hours and hours of agony. The nw.js API is pretty small, so I went through all the documentation with a fine-tooth comb, looking for how to do it, and I couldn't find anything. I then thought that maybe that API was in node.js, so I went through all the node.js documentation to find out how to do it--nothing. Then I thought there might be an NPM package to do it. After much searching, I turned up nothing. I think most people use node.js for server stuff, so they never need to store stuff in a user's Documents folder.

After hours of this, I took a peek at Electron, and it was right there. Electron has an extensive platform integration API for not only getting at the documents folder, but also for crazy MacOS dock stuff, etc. Electron is used by bigger companies that ship more complicated applications, so they care deeply about all the subtle platform integration issues needed for a polished app. As a result, Electron has a much deeper and much more extensive platform integration API than nw.js. Of course, the Electron programming model is more complicated than nw.js, so it seems like it will require a lot more code to be written to get things going. And there's a lot more documentation, so I don't think it's possible to read it all, like I could with nw.js. And I'm concerned there might be annoying configuration issues. But it looks like I'll have to move to using Electron.

So if you need extensive platform integration APIs, use Electron, despite the fact that it's more complicated. If you're making something more self-contained, like, say, a game, then nw.js is probably fine though, and you'll save time because it's so easy to set-up.

Update (2017-6-7): Apparently, there's another difference in philosophy between nw.js and Electron too. nw.js tries to create a programming environment that imitates a normal web environment as much as possible. Platform integration is implemented as minor embellishments on existing web APIs with reasonable defaults chosen. With Electron, using normal web APIs will work, but not well. Lots of platform integration features are available, but the programmer has to explicitly write separate Electron code to take advantage of those features, and the API isn't that nice (due to Electron's multi-process architecture and lots and lots of optional parameters). For example, to open a file dialog in nw.js, you can simply reuse the existing html5 file dialog APIs, and the return results are augmented with some extra path information that you can use to open files. To open a file dialog in Electron, you can't reuse your existing html5 file dialog code because Electron's implementation is missing a couple of features, so instead you have to make use of Electron's file dialog APIs. Electron's file dialog APIs are fine, but a little messy to set-up, and by default they aren't modal, so you have to jump through some hoops to get normal file dialog behavior.

Update (2017-8-28): Despite what some people say, the nw.js documentation is much better than the Electron documentation. Electron has a lot of documentation, but it's not well-written. For example, I found the Electron docs would often just list a bunch of method names and method parameter names without really saying what the parameters do (this is similar to the node.js documentation, actually). The documentation that Intel and others have provided for nw.js is very clear and almost a pleasure to read. To show you how good the nw.js documentation is, when I was making a Mac App Store version of an Electron app, I consulted the nw.js documentation on how to do it because the nw.js documentation was just so much more clear and detailed.

15 comments:

  1. What about performance? Any difference?

    ReplyDelete
    Replies
    1. I didn't do any extensive testing, but for my app, they seemed similar. They both use the same Chromium code underneath, after all. If you end up needing to call a lot of node.js or native APIs, nw.js might theoretically be faster, but my application didn't need to do that, so I don't know.

      Delete
  2. I need to a cross platform app for super heavy files (3+ tb) between offices. Electron looks better for this, what do you think?

    ReplyDelete
    Replies
    1. I haven't worked specifically with large files with either Electron or nw.js, but they both use the default Node.js libraries to manipulate files, so I think they would both behave largely the same.

      I found the Node.js "fs" file io library to be underdocumented and a little buggy. I don't know if it even supports 64-bit file pointers. It uses some sort of custom Buffer object to store binary data read from a file. There was supposed to be an arraybuffer underneath the Buffer object, but I found that sometimes that arraybuffer contained garbage data, and so I instead had to access the Buffer object by manually indexing it.

      So basically, I think nw.js and electron will both be equally bad for your use case. You shouldn't use JavaScript for manipulating large amounts of data. Unless your application just works with a small part of large files, then it will likely be too slow. I would suggest starting with nw.js then because it's easier to use, so you'll more quickly discover whether JavaScript will work at all for you. You should first test whether you can even index past the 4GB boundary on large files. There's a chance that you won't be able to. Then I would test to see how long it takes to actually read in and manipulate 1 TB of data in JavaScript. There's a good chance that it will be too slow.

      Delete
  3. Really? You could not find the nwworkingdir attribute in the docs, so you dropped NW.js altogether? It took me 10 sec. to find it using Google.
    In my opinion, NW.js is underrated.

    ReplyDelete
    Replies
    1. I don't think you actually read the blog post or perhaps you don't understand how the "My Documents" directory works in Windows.

      Delete
    2. I have read your blog post and I don't get the hassle with setting the nwworkingdir to
      require('os').homedir() + '\\Documents' which is a standard path of My Documents.

      Delete
    3. My blog post was overwhelmingly positive about nw.js, and I recommended it for everything except for those situations where you need greater platform integration.

      Microsoft occasionally changes the name of the Documents folder, and I believe Windows can be configured so that documents are stored in an entirely different directory (historically, Windows does not use symlinks for stuff). So accessing the My Documents directory through a hardcoded path like that is incorrect. You need a system call to ask Windows where it is. nw.js does not provide a function for doing that.

      Delete
  4. Enjoyed the write-up. I am thinking about going with nw-js myself for Nevergrind 2, a multiplayer rogue-like RPG I am making. Do you have any resources on how REST calls to my web server's back-end even works? Do I have to enable CORS? What does the nw-js location object return? So many questions...

    ReplyDelete
    Replies
    1. I think you can disable most if not all the standard browser security restrictions in nw.js, but I don't know how to do it off the top of my head.

      Delete
  5. I have also found nw.js architectural design choices to make clear sense to me. HTML is the first class citizen and JS is a second class citizen, that's why you just point nw.js at your index.html and it works. Electron reverses that and it's more like you are coding for node.js instead for the browser.

    ReplyDelete
  6. Thanks for taking the time to write this up. I come from the position of having created nwjs apps back in the day before Electron existed (I think Atom was in development), but lately have been developing a pretty massive Electron app.

    I agree with everything you said. I wish you'd gotten into the process of developing a build system with auto-update. AFAIK electron has a pretty good story here, but I'm having trouble figuring out if nwjs has similar capabilities (now) — it certainly didn't when I was working with it. With Electron I can install electron-build and do some configuration and essentially create a .dmg for macOS and an installer for Windows, code-signed automatically with support for automatic updates.

    One problem we've encountered lately is that Electron has fallen significantly behind Chromium — Electron 2 beta 2 is currently tracking Chromium 61 (the release version is on 59) while nwjs is on Chromium 65 (Chrome itself is on 64 as I write this). This means that we are waiting on bug fixes we know are in Chromium that affect us via Electron and there's really no production-quality solution. Frankly, I'm not sure all of Electron's benefits outweigh the degree to which it has fallen behind Chromium.

    ReplyDelete
    Replies
    1. I'm a pretty risk-averse, so I really don't like using "do-everything" build tools written by complete strangers that don't carefully describe what they are doing. Hence, I really disliked the tendency of the electron community to shove anything difficult with building into electron-build or electron-packager or whatever. I don't want to use outside build tools unless they're backed by a reputable software vendor. That's why I liked how nw.js has good documentation on what steps are needed to do things like create a code-signed .dmg, so then I could just manually insert those steps into my own build tools. The electron auto-updater seemed interesting, but the documentation on it seemed so vague about what it was actually doing that I didn't feel comfortable using it. In fact, for the Mac version of my app, I ended up rolling my own app using WKWebView, resulting in a tiny executable with much better platform integration.

      It is worrisome that electron is behind on their Chrome releases though recent versions of Chrome disabled some pretty useful features like SharedArrayBuffer in order to protect against Spectre, so I wouldn't want to use an Electron built against those versions of Chrome.

      Delete
  7. One more thing — you may be able to find the directory you want via nodejs's 'os' module:

    const os = require('os');
    console.log(os.homedir());

    https://stackoverflow.com/questions/9080085/node-js-find-home-directory-in-platform-agnostic-way/32556337#32556337

    ReplyDelete
    Replies
    1. I believe that the location of the "My Documents" folder in Windows is user-configurable. It is not necessarily in the home directory. Since Windows can run on file systems that don't properly support symlinks, Windows can't use symlinks to the proper location of the "My Documents" folder either. You need to make a system call to Windows to get its location.

      Delete