Writing JavaScript Libraries using JS++

One of the primary concerns when writing JavaScript is the issue of cross-browser compatibility. As web browsers continue to evolve and change quickly, it is important for your code to support this rapid change and to provide a consistent user experience across all web browsers and devices. However, while JavaScript libraries like Modernizr provide a web GUI interface for you to manually select and de-select which components you need, experience has shown it is better if this process is seamless and automatic—or, rather, the compiler or build tool should automatically know which components you need or do not need. This automatic process is known as dead code elimination (DCE).

In theory, DCE performs best with static typing and static structure. Since JS++ is the first sound gradually typed language, it is possible for DCE to perform optimally, such as in the case of identifying which function overloads are necessary. This is, generally, not possible with other JavaScript supersets or with JavaScript itself. In particular, given JavaScript’s most popular runtime environment is execution via JIT engines like V8 (for Google Chrome), it is natural that DCE provides advantages by reducing parse times, analysis times, and compile times for JIT environments; thus, page load times are reduced and responsiveness improves. Beyond JIT execution, avoiding the execution of irrelevant operations reduces program running time, and smaller program sizes allow websites to load faster by reducing network payloads.

In this article, we will explore writing a JavaScript library via JS++, which supports DCE, and subsequently show you how that library can be used by JavaScript developers with no knowledge of JS++. In addition to “automatic” and seamless DCE, which is built into the JS++ language, we will also explore “programmable DCE”—a metaprogramming pattern unique to JS++.

What is Dead Code Elimination (DCE)?

Dead code elimination (DCE) means that if you don’t use a module, class, or function, it will not be compiled into your final output.

Here’s a simple example:

void A() {}
void B() {}

A();

In the above example, only the function A gets called. The function B never gets called and never gets executed. Therefore, it is safe for the compiler to not include B in the final compiled output.

A real-world example of the need for dead code elimination in JavaScript (but not JS++) is jQuery. You have to include the entire jQuery library even if you only use one function.

In JS++, it is sufficient to just write the code and compile it. Dead code elimination in JS++ is seamless and automatic—it ships as a default with JS++. The JS++ compiler is able to determine that the function B is never used, and it will not compile the code for function B—by default. In most cases, if all you want is dead code elimination, this is all you need to know, but, for cross-browser and cross-device/mobile development, it is useful to explore more sophisticated DCE.

Programmable DCE: Mobile & Library Development

Arguably, the most important reason to understand DCE is for library development. We will explore library development for mobile devices by example. At the time of this writing, the HTML5 Vibrate API is not supported on the iPhone. In a very basic example, we will develop a small library that allows the user of the library to specify which phones he wants to support (iPhone or Android), and the library will provide notifications to the end user based on the features supported by the requested device(s). We will name this library: Notify.

class Notify
{
}

Save the file as Notify.jspp.

Next, let’s define the implementation:

import Externals.DOM;

class Notify
{
    private static void vibrate() {
        window.navigator.vibrate(2000);
    }
    private static void infobox() {
    	var el = document.createElement("div");
    	el.style.border = "1px solid #000";
    	el.innerText = el.textContent = "You have a new notification.";

    	document.body.appendChild(el);
    }
}

The first line, which imports the Externals.DOM module, allows us to use the JavaScript DOM API.

The method vibrate does exactly what the method name suggests: it will make the phone vibrate (for supported devices).

Finally, the infobox method creates a DIV element and inserts it into the DOM.

Both methods are private and static. The reason is because these methods are platform-specific implementation details. For the library user, we only want to expose to him whether he wants iPhone notifications or Android notifications. Here’s how we expose this to the user:

import Externals.DOM;

class Notify
{
    private static void()? iphoneNotify = null;
    private static void()? androidNotify = null;

    public Notify(int platforms) {
    }
	
    public static property int IPHONE() {
    	iphoneNotify = infobox;
    	return 1 << 0;
    }
    public static property int ANDROID() {
    	androidNotify = vibrate;
    	return 1 << 1;
    }
    
    private static void vibrate() {
        window.navigator.vibrate(2000);
    }
    private static void infobox() {
    	var el = document.createElement("div");
    	el.style.border = "1px solid #000";
    	el.innerText = el.textContent = "You have a new notification.";

    	document.body.appendChild(el);
    }
}

Here we are defining two getter methods: IPHONE and ANDROID. These two getter methods will allow the user to specify which platforms he wants to support. In order to specify the desired platforms, we instantiate the library like so:

new Notify(Notify.IPHONE | Notify.ANDROID); // iPhone *and* Android support
new Notify(Notify.IPHONE);  // iPhone support only
new Notify(Notify.ANDROID); // Android support only

You can try compiling with the variations in the instantiation and confirm that, indeed, only the specified platform code is compiled into the final output. In essence, we get “programmable DCE.” Furthermore, rather than specifying an int return type on the getter methods, one can define an enum to create a more specific type, but this is left as an exercise to the reader.

While the example we’ve explored is very basic, in real-world applications with complex dependency graphs, a library user can experience significant reductions in code size.

Exporting to JavaScript

While JavaScript cannot support DCE—and especially not the advanced DCE patterns of JS++—we can still “pre-DCE” our code before shipping it to JavaScript users. In order to do this, we should first wrap our class in a module. In Notify.jspp:

module NotifyLib
{
    class Notify
    {
        // ...
    }
}

In JS++, there is a toExternal design pattern for exposing JS++ code and libraries to JavaScript users. We need to define a `toExternal` method in our class:

module NotifyLib
{
	class Notify
	{
		// ...

		public function toExternal() {
			void() send;
			
			if (null != iphoneNotify) {
				send = iphoneNotify ?? dummy;
			}
			else if (null != androidNotify) {
				send = androidNotify ?? dummy;
			}
			else {
				send = dummy;
			}
			
			return {
				send: void() {
					send();
				}
			};
		}
		
		private static void dummy() {
			/* INTENTIONALLY EMPTY */
		}
	}
}

Notice how, in the toExternal method, we are transitioning from static to dynamic programming. We use if statements to determine, at runtime, which method to execute. In statically-typed programming languages, one would normally have the compiler resolve the method(s) to call. The purpose of the toExternal design pattern in JS++ is to facilitate complex transitions between the static and dynamic worlds.

Next, we create three files:

  1. Notify.iPhone.jspp
  2. Notify.Android.jspp
  3. Notify.All.jspp

In this tutorial, we will implement Notify.iPhone.jspp, and the other files are left as an exercise for the reader.

In Notify.iPhone.jspp:

import NotifyLib;
import Externals.JS;

auto notify = new Notify(Notify.IPHONE);
global.Notify = notify.toExternal();

First, we import the NotifyLib library. We also import Externals.JS, which defines all JavaScript (ECMAScript 3) symbols as external (such as `Math`, `Array`, `Object`, and so forth). However, Externals.JS does define one symbol that is not in the ECMAScript 3 specification: global. It gives us universal access to JavaScript’s global object, and this non-standard object was added for convenience so that JS++ users would not need to learn all the edge cases that come with trying to access JavaScript’s global scope (such as window being a DOM API object that is not defined in Node.js). Once we are able to access JavaScript’s global scope, we just export our JS++ library to it by converting it to the `external` type (via calling the toExternal method).

Compile Notify.iPhone.jspp:

> js++ Notify.iPhone.jspp Notify.jspp -o Notify.iPhone.js

Now, it should be straightforward to use the `Notify` library you developed completely in JS++ from plain JavaScript:

<!DOCTYPE html>
<html>
<head>
<title>Notify</title>
</head>
<body>
<script type="text/javascript" src="Notify.iPhone.js"></script>
<script type="text/javascript">
Notify.send();
</script>
</body>
</html>

The example for the iPhone above will insert a DOM notification on page load. (Note that, for Android devices, which will vibrate, user interaction is required before the vibration will trigger on the phone for security reasons. Keep the security restriction in mind when compiling Notify.Android.js.) As you compile the remaining files, you will observe that the file sizes are very different—reflecting how only the code for the specified platforms are shipped.

Conclusion

In this article, we have learned several advanced techniques unique to JS++, from programmable DCE to exporting an entire library to JavaScript. It should be clear JS++ is a powerful language, but, since its libraries can be used in JavaScript, there is no “vendor lock-in.”

Roger PoonRoger Poon
JS++ Designer and Project Lead. Follow me on Twitter or GitHub.

Leave a Reply