An exciting aspect to writing code in JavaScript is the ability to run it in multiple places. The technique of structuring code so that it can be run on the browser or server in Node.js is already popular. There are many folks doing interesting things with running so called "isomorphic" apps which have JavaScript code that can render a page on the server OR the client. This, on its own, is very neat because it solves some of the problems found in single page apps like the slow initial loading of a page and searchability problems without needing to maintain different versions of code to run on the server and client. This concept isn't new, but there seems to be a lot of momentum behind it lately, with many libraries trying to support this.

Being able to do this kind of sharing between client and server is awesome, and given the large number of JavaScript engines out there, the concept can be taken further.

A specific problem we had to solve was supporting an offline mode for our desktop software. We wanted to be able to write our code which was meant to run via node without having to think too much about the offline usecase but, at the same time, have it work offline. Our online node services have dependencies on other online services that work perfectly in the online usecase, but we needed a way to run the business logic within the node applications on the desktop. The nice thing about our desktop application is that all of those dependent services that our node application needed had a corresponding native module. If we could swap out an http call in our node application to one of these services to call one of these native modules this might be able to work.

We also needed a way for the desktop software to invoke this JavaScript based business logic. The ability to register a js function in our code with the native hooks would solve this.

Our native teams were able to implement these hooks in JavaScriptCore and V8. We were able to load a very simple script which the native code could call via this bridge. This simple script also called a function which hooked into a native function. Getting this working was awesome! The next step was to mechanically transform our nodejs codebase into something that could be loaded into this setup.

Browserify to the rescue. Browserify is a tool that lets you bundle node style CommonJS code to be run in the browser. Using a plugin called browserify-swap we could swap our http client code with code that made the same type of request via the native hook. To make this easier we wrapped our usage of the request library. We went from something like this:

function (url, method, params, cb) {  
  var opts = { method: method, url: url, body: params.body }
  request.post(opts, cb);
}

To this:

function (url, method, params, cb) {  
  var serviceId = getServiceIdForUrl(url); // this was a mapping of url to an id the native hook understood
  NativeHook.invoke(serviceId, method, params, cb);
}

Using the Browserify plugin brfs we could inline our static files for configuration and any other situation where we needed to read a file and the filename was known at build time. The last step was replacing the http server code. We use restify, which meant we needed to replace all of the code which configured restify and registered our route handlers with similar code that registered our route handlers on the native hook interface. We created a file which did just this and used that as the entry point to browserify, which created a bundle that we could load and that the native app could call via the native bridge. Our application entry point went from something like:

...
var server = restify.createServer();  
server.get('/some/path', handleGet);  
server.post('/other/path', handlePost);

server.listen(9000, function () {});  

To something like:

...
NativeHook.registerHandler(serviceId, 'GET', handleGet);  
NativeHook.registerHandler(serviceId, 'POST', handlePost);  
...

Pointing Browserify at this new application entry point generated a bundle that could be loaded by the native engines and run without the core application logic having to change at all.

This kind of idea isn't new but is still very exciting. Letting engineers write code that can run in various contexts without having to change the way they write that code has been incredibly useful for us.

About Nikhil

Nikhil Dvivedi is Senior Software Engineer at Intuit's Consumer Tax Group, TurboTax. His focus is on developing capabilities for the front end experience for TurboTax. Prior to his role at Intuit, he was Senior Software Engineer at Sony Network Entertainment. He was also a Programmer Analyst at UC San Diego. Nikhil graduated with BS in Computer Science at University of CA, San Diego.