Wichert Akkerman

Software design? Strange hacks? All of the above please!

Developing for the browser

In a recent blog post Andy Mckay asked if javascript apps are ready for business. Having recently spent a fair amount of writing JavaScript I think I can answer that question. Unfortunately the answer at this point in time is a resounding no.

Platform incompatibilities

The most obvious problem that anyone who develops for a browser runs into is that no two browsers are the same. For a developer that means that you develop your app for the browser you are using, and when you are finished you get to spend many days trying to get the same results in other browsers. This generally means you need to debug CSS, modify your Javascript to work around differences in DOM implementations, try to see why minified code runs on one browser but not the other, etc. It is like having to write an application that runs on not one or two but five different versions of Windows, and for each version you must use a completely different set of debugging tools.

Now at this point people will say that modern browsers are much better at standards compliance so this pain should just go away. I am sceptical of this. First it will take a long time before enough people are using those modern browsers that we can stop having to support the older browsers. How bad this is will depend on your specific audience: if your target audience works in large organisations that only update their standard desktop every couple of years, or if they are in a country where people generally do not upgrade their software (I'm looking at you China) you may be in for a long wait. I can recommend looking at the excellent Can I Use... site to check what your audience can support - the result is likely to be depressing.

Furthermore I am not yet convinced that this standardisation will really work out. Right now we are seeing that support for CSS 2, ECMAScript 5 and HTML 4 DOM is reaching a point where we can mostly assume they work on all browsers. Those however are old standards and no longer suffice. For modern interfaces and apps we need things like Web Storage, WebSockets, CSS animations, HTML 5 input types and other technologies which are still far from being supported by all modern browsers, and if they are supported there are still way too many inconsistencies. That means all the compatibility problems are still here today; they just moved from the standards of five years ago to the standards of last year.

I know that there are tools out there to help with this such as Modernizr, -prefix-free, jquery-placeholder, Reset CSS, ExplorerCanvas and many others. The fact we must use these for every thing we do is a sign that things are really bad, not a sign that things are going well. None of them should be needed.

No useful toolchain

There is no decent way to manage dependencies between javascript files. Even the terminology in this area seems to be unclear: people use terms like widgets, modules, packages and libraries seemingly without defining exactly what they are. I'll use the term module here. The most common approach is that you write some javascript for your specific app and you manually include dependencies that you need such as jQuery, jQuery tools, Modernizr, etc. When you releasing your app you will then generally want to combine and minimise these to generate a single file to reduce the number of HTTP requests a browser has to make. There are many different tools to do that, all of them producing slightly different results and everyone appears to just write their own scripts or makefiles to manage that process.

RequireJS improves this situation by providing a framework for declaring dependencies between javascript modules, loading javascript modules on-demand as needed and supporting bundling and minimisation. You do this by wrapping all your code with something like this:

define([
    'require',
    'jquery.placeholder'
], function(require) {
    ...
});

RequireJS exposes some problems though: there is no standardisation in naming of javascript modules. Your code might require jQuery, but the filename might be jquery-1.8.2.min.js. Or jquery-1.8.2.js. Or jquery-min.js. You do not want hardcode those filenames everywhere in your code, so you can tell RequireJS to use a different filename. You do that with code like this:

requirejs.config({
    paths: {
        "jquery": "3rdparty/require-jquery",
        "prefixfree": "3rdparty/prefixfree.min",
        "modernizr": "3rdparty/modernizr-2.0.6",
        "jquery.anythingslider": "3rdparty/jquery.anythingslider",
        "jquery.autoSuggest": "3rdparty/jquery.autosuggest",
        "jquery.fancybox": "3rdparty/jquery.fancybox-1.3.4",
        "jquery.form": "lib/jquery.form/jquery.form",
        "jquery.placeholder": "3rdparty/jquery.placeholder",
        "jquery.tools": "3rdparty/jquery.tools.min"
    }
});

Looks better, right? Ah, but there is a new problem lurking here: RequireJS still assumes a model where you are writing a single application that pulls in some third party javascript files. What if you are dealing with a larger library that itself has dependencies and use that in your application? It turns out that at that point things break down again: RequireJS has no way to merge the dependencies from our library with those of the application. Currently that seems to mean that you will either have to load things twice, or the application needs to be intimately aware of library internals.

This appears to be an unsolvable problem unless there is a way to identify javascript modules. Since we only have a filename and the filename is not fixed there is no way to do that. Contrast this with how other languages work: C/C++ has global library names (the lib*.so or *.dll files on your system), Java has class paths, C# has assembly names, Python has module paths: all ways to uniquely identify code. This is used by their linkers or interpreters to resolve all dependencies and make sure everything needed is loaded, and never loaded more than one time.

What is desperately needed is a standard toolchain for javascript that does the following:

  • Give a unique way to identify javascript modules.
  • Allow you to specify dependencies between modules.
  • Can generate single-file builds that include all dependencies.
  • Can deal with single-file builds of libraries that include modules that are also included elsewhere.

This problem has been solved for (almost) every other language. It is time that someone solves this for the javascript as well.

Tracking down errors

When you are writing or deploying an application fixing errors is always important. This can be broken down in two phases: during development you use tests and debuggers, and after deployment you can use error logging.

The test situation is luckily pretty good now: with test frameworks such as Jasmine and QUnit and headless browsers like PhantomJS all the necessary tools are available.

Debugging is bit more problematic: Internet Explorer, Chrome and Safari include a decent debugger. Firefox requires the third party Firebug extension which is powerful but unfortunately has a slight tendency to crash the browser. Since every browser version is essentially a different platform you will need to be proficient with all debugging tools of all browsers. Doable, but annoying. This only works during development: as soon as you start using minified code debugging becomes almost impossible since you no longer have readable source code. And it happens just a bit too often that something only breaks when running after minimisation.

Error logging is very, very useful: by catching all errors and sending them to a central log collection system you can get a great view of where your software is breaking and why. There simply is no substitute for this. This is also an area where browser support is minimal to non-existing: they do not provide any way to catch unhandled errors in any meaningful way. You can register a window.onerror handler but the only information you get is the URL and a line number where the error happened. And since you always deploy with minimised files this has no useful information at all. The alternative is that you use lots of try..catch blocks in your code. This means many changes throughout your code. Unfortunately browsers again do not give you much information: you only get the exception object but no stack trace, which makes it much much harder to figure out what went wrong.

Summary

At this moment I can honestly say that after working on web apps for an entire day I sometimes long for the time years ago when I was writing Windows applications using MFC. And that is a very sad thought.