JavaScript: keep it working in different runtimes
The programming language JavaScript is seeing more and more use. Software written in it can run in many different environments. Not only do web browsers support it, there are quite a few programming environments that can integrate and run JavaScript code. Qt has support for it with the QtScript module. GNOME has JavaScript bindings via gjs. Node.JS is gaining popularity on the server and Java has the Rhino runtime.
Support for the basic language features of JavaScript is good among these runtimes. You can have a look at the list of dialects of JavaScript/ECMAScript to see that "ECMA-262, edition 3" is the most common specification that is implemented. Nevertheless, each of these environments has different facilities for accessing parts of the environment they are running in. Modularizing the code, access to the file system, logging, starting a new execution thread, running unit tests, these are but a few of the use cases for which there is no common solution.
There are few good practices that have helped me to keep my JavaScript code working in multiple runtimes. Most of the code for WebODF, an ODF project written in JavaScript, runs in the popular browsers, in QtScript, in Rhino and in Node.JS.
Abstraction
First of, I have written a small abstraction layer that wraps loading of modules, logging, unit testing and a few other things. This abstraction layer is not very large, it is a single file. The code contains an abstract class with implementations for the different runtimes. Whenever I need to access a runtime-specific funtion, I resort to this class, extending it where needed.
JSLint and Closure Compiler
JavaScript is a dynamic language, there is no compiler. This means that there are no steps required between writing the code and running the code. Code errors can easily slip in to released code. It is therefore very important to do static testing of the code. Two good tools for this are JSLint and the Closure Compiler. JSLint is a JavaScript program that analyzes code for correctness and style. Some features of the JavaScript language do more harm than good and JSLint brings occurrences of these to your attention so you can avoid them. The Closure Compiler can compile a collection of JavaScript files into one smaller file. But that is not why I use it. While 'compiling' the JavaScript, the Closure Compiler performs a number of checks on the code and catches certain problems before the code is actually run.
Unit testing
Running JavaScript on the command line, in a desktop program or on a website are very different. So sharing unit tests across these environments is a bit of work initially. Having good unit tests is invaluable though, so it is an investment you just have to make if you want to stay confident of your code. For WebODF, I have written a small script or web page for each environment in which I want to run the unit tests. So unit tests are written only once but tested in all environments where they are relevant.
An amazing tool for checking how much of your code is covered by unit tests is jscoverage. It can 'instrument' your code. While running the instrumented code, reports are created that show how often each line of JavaScript was run. This makes it easy to find for what parts of your code could benefit most from an additional unit test.
Conclusion
JavaScript is nearly everywhere. But to write JavaScript that can go nearly everywhere too, you need to take portability into account. The best way to do that is to develop for at least three runtimes in parallel.