A Fundamental Disconnect

Yesterday at BlendConf, Scott Hanselman gave a fantastically-entertaining keynote entitled “JavaScript, The Cloud, and the rise of the New Virtual Machine.” In it, he chronicled all of the ways Web development and deployment has changed—for the better—over the years. He also boldly declared that JavaScript is now, effectively, a virtual machine in the browser.

This is a topic that has been weighing on my mind for quite some time now. I’ll start by saying that I’m a big fan of JavaScript. I write a lot of it and I find it incredibly useful, both as a programming language and as a way to improve the usability and accessibility of content on the Web. That said, I know its limitations. But I’ll get to that in a minute.

In the early days of the Web, “proper” software developers shied away from JavaScript. Many viewed it as a “toy” language (and felt similarly about HTML and CSS). It wasn’t as powerful as Java or Perl or C in their minds, so it wasn’t really worth learning. In the intervening years, however, JavaScript has changed a lot.

Most of these developers first began taking JavaScript seriously in the mid ’00s when Ajax became popular. And with the rise of JavaScript MVC frameworks and their ilk—Angular, Ember, etc.—many of these developers made their way onto the Web. I would argue that this, overall, is a good thing: We need more people working on the Web to make it better.

The one problem I’ve seen, however, is the fundamental disconnect many of these developers seem to have with the way deploying code on the Web works. In traditional software development, we have some say in the execution environment. On the Web, we don’t.

I’ll explain.

If we’re writing server-side software in Python or Rails or even PHP, one of two things is true:

  1. We control the server environment: operating system, language versions, packages, etc.; or
  2. We don’t control the server environment, but we have knowledge of it and can author your program accordingly so it will execute as anticipated.

In the more traditional installed software world, we can similarly control the environment by placing certain restrictions on what operating systems our code can run on and what the dependencies for its use may be in terms of hard drive space and RAM required. We provide that information up front and users can choose to use our software or use a competing product based on what will work for them.

On the Web, however, all bets are off. The Web is ubiquitous. The Web is messy. And, as much as we might like to control a user’s experience down to the very pixel, those of us who have been working on the Web for a while understand that it’s a fool’s errand and have adjusted our expectations accordingly. Unfortunately, this new crop of Web developers doesn’t seem to have gotten that memo.

We do not control the environment executing our JavaScript code, interpreting our HTML, or applying our CSS. Our users control the device (and, thereby, its processor speed, RAM, etc.). Our users choose the operating system. Our users pick the browser and which version they use. Our users can decide which add-ons they put in the browser. Our users can shrink or enlarge the fonts used to display our Web pages and apps. And the Internet providers that sit between us and our users, dictating the network speed, latency, and ultimately controlling how—and what part of—our content makes it to our users.

All we can do is author a compelling, adaptive experience, cross our fingers, and hope for the best.

The fundamental problem with viewing JavaScript as the new VM is that it creates the illusion of control. Sure, if we are building an internal Web app, we might be able to dictate the OS/browser combination for all of our users and lock down their machines to prevent them from modifying those settings, but that’s not the reality on the open Web.

The fact is that we can’t absolutely rely on the availability of any specific technology when it comes to delivering a Web experience. Instead, we must look at how we construct that experience and make smarter decisions about how we use specific technologies in order to take advantage of their benefits while simultaneously understanding that their availability is not guaranteed. This is why progressive enhancement is such a useful philosophy.

The history of the Web is littered with JavaScript disaster stories. That doesn’t mean we shouldn’t use JavaScript or that it’s inherently bad. It simply means we need to be smarter about our approach to JavaScript and build robust experiences that allow users to do what they need to do quickly and easily even if our carefully-crafted, incredibly well-designed JavaScript-driven interface won’t run.


Comments


Webmentions

  1. At Velocity NY, Daniel Espeset of Etsy gave a great talk about how Etsy profiles their JavaScript parse and execution time. Even better, after the talk, they released the tool on GitHub.

    Daniel shared a few examples in his deck, but I couldn’t wait to take Daniel’s tool and fire it up on a bunch of random browsers and devices that I have sitting around.

    For this test, I decided to profile just jQuery 2.1.1, which weighs in at 88kb when minimized. jQuery was selected for its popularity, not because it’s the worst offender. There are many libraries much worse (hey there Angular and your 120kb payload). The results above are based on the median times taken from 20 tests per browser/device combination.

    The list of tested devices isn’t exhaustive by any means—I just took some of the ones I have sitting around to try and get a picture of how much parse and execution time would vary.

    Parse and execution times of minimized jQuery 2.1.1
            Device
            Browser
            Median Parse
            Median Execution
            Median Total
            Blackberry 9650
            Default, BB6
            171ms
            554ms
            725ms
            UMX U670C
            Android 2.3.6 Browser
            168ms
            484ms
            652ms
            Galaxy S3
            Chrome 32
            39ms
            297ms
            336ms
            Galaxy S3
            UC 8.6
            45ms
            215ms
            260ms
            Galaxy S3
            Dolphin 10
            2ms
            222ms
            224ms
            Kindle Touch
            Kindle 3.0+
            63ms
            132ms
            195ms
            Geeksphone Peak
            Firefox 25
            51ms
            109ms
            160ms
            Kindle Fire
            Silk 3.17
            16ms
            139ms
            155ms
            Lumia 520
            IE10
            97ms
            56ms
            153ms
            Nexus 4
            Chrome 36
            13ms
            122ms
            135ms
            Galaxy S3
            Android 4.1.1 Browser
            3ms
            125ms
            128ms
            Kindle Paperwhite
            Kindle 3.0+
            43ms
            71ms
            114ms
            Lumia 920
            IE10
            70ms
            37ms
            107ms
            Droid X
            Android 2.3.4 Browser
            6ms
            96ms
            102ms
            Nexus 5
            Chrome 37
            11ms
            81ms
            92ms
            iPod Touch
            iOS 6
            26ms
            37ms
            63ms
            Nexus 5
            Firefox 32
            20ms
            41ms
            61ms
            Asus X202E
            IE10
            31ms
            14ms
            45ms
            iPad Mini
            iOS6
            16ms
            30ms
            46ms
            Macbook Air (2014)
            Chrome 37
            5ms
            29ms
            34ms
            Macbook Air (2014)
            Opera 9.8
            14ms
            5ms
            19ms
            iPhone 5s
            iOS 7
            2ms
            16ms
            18ms
            Macbook Air (2014)
            Firefox 31
            4ms
            10ms
            14ms
            iPad (4th Gen)
            iOS 7
            1ms
            13ms
            14ms
            iPhone 5s
            Chrome 37
            2ms
            8ms
            10ms
            Macbook Air (2014)
            Safari 7
            1ms
            4ms
            5ms
    

    As you can see from the table above, even in this small sample size the parsing and execution times varied dramatically from device to device and browser to browser. On powerful devices, like my Macbook Air (2014), parse and execution time was negligible. Powerful mobile devices like the iPhone 5s also fared very well.

    But as soon as you moved away from the latest and greatest top-end devices, the ugly truth of JS parse and execution time started to rear its head.

    On a Blackberry 9650 (running BB6), the combined time to parse and execute jQuery was a whopping 725ms. My UMX running Android 2.3.6 took 652ms. Before you laugh off this little device running the 2.3.6 browser, it’s worth mentioning I bought this a month ago, brand new. It’s a device actively being sold by a few prepaid networks.

    Another interesting note was how significant the impact of hardware has on the timing. The Lumia 520, despite running the same browser as the 920, had a median parse and execution time that was 46% slower than the 920. The Kindle Touch, despite running the same browser as the Paperwhite, was 71% slower than it’s more powerful replacement. How powerful the device was, not just the browser, had a large impact.

    This is notable because we’re seeing companies such as Mozilla and Google targeting emerging markets with affordable, low-powered devices that otherwise run modern browsers. Those markets are going to dominate internet growth over the next few years, and affordability is a more necessary feature than a souped up device.

    In addition, as the cost of technology cheapens, we’re going to continue seeing an incredibly diverse set of connected devices. With endless new form factors being released (even the Android Wear watches quickly got a Chromium based browser), the adage about not knowing where our sites will end up has never been more true.

    The truly frightening thing about these parse and execution times is that this is for the latest version of jQuery, and only the latest version of jQuery. No older versions. No additional plugins or frameworks. According to the latest run of HTTP Archive, the median JS transfer size is 230kb and this test includes just a fraction of that size. I’m not even asking jQuery to actually do anything. Basically, I’m lobbing the browsers a softball here—these are best case results.

    This re-affirms what many have been arguing for some time: reducing your dependency on JS is not healthy merely for the minor percentage of people who have JS disabled—it improves the experience for everyone. When anything over 100ms stops feeling instantaneous and anything over 1000ms breaks the users flow, taking 700ms to parse your JavaScript cripples the user experience before it really has a chance to get started.

    So what’s a web developer to do?

    1. Use less JavaScript. This is the simple one. Anything you can offload onto HTML or CSS, do it. JavaScript is fun and awesome but it’s also the most brittle layer of the web stack and, as we’ve seen, can seriously impact performance.

    2. Render on the server If you’re using a client-side MVC framework, make sure you pre-render on the server. If you build a client-side MVC framework and you’re not ensuring those templates can easily be rendered on the server as well, you’re being irresponsible. That’s a bug. A bug that impacts performance, stability and reach.

    3. Defer all the scripts. Defer every bit of JavaScript that you can. Get it out of the critical path. When it makes sense, take steps to defer the parsing as well. Google had a great post a few years back about how they reduced startup latency for Gmail. One of the things they did was initially comment out blocks of JavaScript so that it wouldn’t be parsed during page load. The result was a 10x reduction in startup latency. That number is probably different on today’s devices, but the approach still stands.

    4. Cut the mustard. I’m a big fan of “cutting the mustard”, an approach made popular by the BBC. This doesn’t solve the problem of low-end devices with modern browsers, but it will make a better experience for people using less capable browsers. Better yet, by consciously deciding not to overwhelm less capable browsers with excess scripts you not only provide a better experience for those users, but you reduce the need for extra polyfills and frameworks for modern browsers as well. On one recent project where we did this, the entire JavaScript for the site was about 43% of the size of jQuery alone!

    There are certainly cases to be made for JS libraries, client-side MVC frameworks, and the like, but providing a quality, performant experience across a variety of devices and browsers requires that we take special care to ensure that the initial rendering is not reliant on them. Frameworks and libraries should be carefully considered additions, not the default.

    When you consider the combination of weight, parse time and execution time, it becomes pretty clear that optimizing your JS and reducing your site’s reliance on it is one of the most impactful optimizations you can make.

    | Permalink

Likes

Shares