Saturday, November 19, 2011

Porting from jQuery to Zepto

In my quest for better performance in my PhoneGap Android app, I finally decided to switch from using jQuery to Zepto, a framework that boasts a similar API with a much smaller footprint and additionally offers mobile specific touch events. Of course, since Zepto is designed to be lighter weight than jQuery, it does not offer all the same methods — and the methods that it does offer sometimes differ in their arguments. This makes sense but it means that porting from jQuery to Zepto requires learning how their differences affect your code, and how you can workaround them.

Since other developers might start going down the porting path too, I thought I'd write a post on the differences I encountered while porting. Because I reuse much of my application code between my Phonegap app and desktop web app and I'm not yet ready to eliminate jQuery in my desktop app, I wanted to port my code over in a way that would mean it would work with either jQuery or Zepto as a base framework. That effectively meant that I tried to keep my code the same, and simply add to the Zepto JS file where needed. I'll explain my ports below and then show my Zepto additions at the end.

This post got a bit long, so here are quick links to the sections:

I spent most of my time porting 3rd party jQuery plugin code, as they tend to use the more esoteric jQuery features, but I also found a few places in my own application code where I was using jQuery functionality that's missing from Zepto:

  • CORS:

    I use CORS (cross-domain XMLHttpRequests) to communicate from my mobile app to my server, and that means setting xhrFields in jQuery.ajax(), something that isn't yet supported in Zepto. Luckily, there's a patch for it, and I applied that. I also discovered a bug with sending empty strings in CORS requests, and I work around that by sending null instead.

  • scrollTop:

    I use jQuery.scrollTop() to reset the scroll in my mobile app on page transition, so I took and Zepto-ified the jQuery implementation.

  • position:

    I use jQuery.position() to calculate where to scroll to in some cases, and Zepto only supports offset(). I checked out the jQuery source code for calculating position and stuck that inside Zepto.

  • non-standard selectors:

    jQuery supports several convenience selectors that aren't defined in the w3 spec and simply special cases them in their code, and these won't work as selectors in Zepto. There are some that correspond to methods - ":first" is .first() in Zepto, and ":last" is last(). There are others with no equivalents, like ":hidden", and ":visible", and I just removed my usage of them or checked display values manually. (I didn't feel like checking the jQuery source for them.)

  • valueless attributes:

    There are times when I set and remove valueless attributes on form elements, like "checked", "readonly", and "disabled". In jQuery, I used .attr('disabled', false). That's apparently been replaced in jQuery 1.7 by .prop('disabled', false). Neither will work in Zepto, however, but removeAttr('disabled') will. Since I want my code working with both, I now have .attr('disabled', false).removeAttr('disabled'), just to be sure.

Porting plugins

I use five jQuery plugins in my mobile app, only one of which I wrote myself, and they took the longest to port. For all of these plugins, I started off by changing the final line in the file from '})(window.jQuery);' to '})(window.jQuery || window.Zepto);' and then hoped for the best.

  • ColorSlider:

    I wrote this plugin to give users a colorful slider (red to green), and the only jQuery function it was missing was outerWidth(), so I wrote one based on the jQuery implementation. In doing so, I discovered that the Zepto width() function includes the padding and border, which jQuery.width() doesn't, so I submitted a patch clarifying that in the Zepto docs.

  • jQuery Templates:

    I was pretty worried about this port, but then found out that the jQuery templates was being deprecated in favor of a non-jQuery dependent library, jsRender/jsViews. That library is in beta and the docs are minimal, but the templating syntax is largely the same, and the author responded quickly to my upgrade question. One thing to note, though: the jsViews library tries to take the global $ variable if it doesn't see jQuery defined, so you have to call $.noConflict() after loading it if you want to use $ with Zepto instead — but you don't want to call that if you are actually using jQuery. Here's what I have after I load my scripts: if (window.Zepto) $.noConflict();

  • timeago:

    This handy library turns timestamps into pretty times like "1 day ago" and also auto refreshes elements with timestamps on an interval. It relies on jQuery.trim, which I just copied from their codebase. Along with the DateInput plugin, it relies on the ability to store objects as data attributes (instead of just strings), and thankfully, there's a data.js in the Zepto codebase that I copied into my Zepto JS to handle that.

  • Twitter Bootstrap modal:

    This is a simple modal library that's designed to work with either jQuery or Ender(a lightweight package library), so it already relied on minimal jQuery features. It does attach functions to $.support, which Zepto doesn't define, so I added a one-liner to define it. It also uses $.proxy, which I copied from the jQuery codebase.

  • jQuery Tools DateInput:

    This was by far the hardest plugin to port, and I did look around briefly to see if I should just switch to a completely different pure JS datepicker library, but I quite like this one so I stuck with with it. First, it uses $.expr, a jQuery object that isn't actually documented and (from what rumors tell me) might actually go away soon. I wasn't actually using the results of that function, so I simply defined the object so that the code would not error out. It also uses jQuery.clone and Event.isDefaultPrevented, which were simple enough to write my own versions of.

    Now we get to the tricky parts - methods that exist in both frameworks but don't have the same interface or behavior. I filed bugs on most of these, but the Zepto team could decide to keep their interface the same for simplicity: Zepto doesn't handle multiple self-closing tags in a creation string the same way as jQuery (Issue 322), Zepto does not assign this to the current iterated object in each() (Issue 295), and Zepto does not include an option for deep copy in the extend() function, and Zepto doesn't handle non-DOM objects in expressions/events (Issue 321, That last one unfortunately required a bit of dirty finagling in the DateInput code itself, as its non obvious how to extend Zepto to support binding and firing events on non-DOM objects.

Summary: What's different

To summarize, the following jQuery functionality is missing from Zepto (and much of it is in the gist below):

  • scrollTop()
  • position()
  • ":first", ":last", ":hidden", ":visible"
  • prop()
  • outerWidth()
  • trim()
  • support()
  • proxy()
  • expr()
  • isDefaultPrevented()

And this functionality differs in behavior/arguments:

  • attr('disabled', false);
  • ajax() CORS
  • width()
  • data()
  • each()
  • extend()

My Zepto modifications

Most of the above issues were resolved by defining functions or objects in Zepto, and this gist shows all of those:

I also made a few tweaks to the core Zepto code, and you can see those in this diff (but not all of them are necessarily successful tweaks, nor are they tested.)

Performance comparison

Since my main point in porting over to Zepto was to improve my loading performance, I celebrated the successful porting by measuring the loading times for my page. I used this timing code and compared the differences between including a minified jQuery 1.7 script tag and a minified Zepto script tag, both for a fresh app install and an app re-launch. On average, switching to Zepto shaved 22% off my total loading time. For the detailed results, check out this spreadsheet.

Was it worth it?

I'm happy that I ported, but it's not a decision to be taken lightly. jQuery alternatives are still in early stages, so if you use them, you have to think of yourself as a beta user, be prepared for issues, be prepared for lack of documentation, and do the responsible thing — report whatever issues or workarounds you find. And on that note, thanks to the Zepto authors for bearing with my barrage of issues over the last few days. :)

Wednesday, October 26, 2011

Logging JS Errors on iOS with PhoneGap

I've spent the last few days getting the EatDifferent PhoneGap app working on an iPhone (an app which previously worked on Android). The hardest part has been learning to debug in the iOS browser, so I thought I'd post on my findings:

  • To view the output of console.log, you must open the XCode console. The iOS browser "Debug console" that most iOS debugging articles mention is only displayed in the standalone Safari browser, not in the WebView (where PhoneGap HTML lives).
  • There seem to be times when console.log does not log the output (perhaps during loading?) - in that case, alert() always seems to work.
  • If you log a JS object using console.log, it will just print "Object" by default. You must JSON stringify it to be useful.
  • You can also use debug.phonegap.com (hosted weinre) to view the DOM and JS console logs as well.
  • The WebView browser silently fails on JS errors - it stops running the JS code and does not report the error. To see the error, you must wrap the offending code in a try/catch block.

Given all of those learnings, here is my log() wrapper function that I use across my webapp:

    function log(something) { 
        if (window.console){ 
          if (something instanceof Date) { 
            something = something.toDateString(); 
          } 
          if (isIOS() || isAndroid()) { 
            if (typeof something == 'object') { 
              something = JSON.stringify(something); 
            } 
            console.log(something); 
          } else { 
            console.log(something); 
          } 
        } 
    } 

And I wrap various code blocks in try/catch, like the callback function for AJAX requests:

    try { 
      onSuccess(processJSON(responseJSON)); 
    } catch(e) { 
      log(e); 
    } 

I posted my observations in the PhoneGap group and the developers there made several recommendations: 1) use Ripple, a Chrome extension for mobile emulation 2) monkey-patch JS functions to always try-catch, as done in this library. I've taken a break from iOS debugging for a few days, but I'll probably revisit debugging soon and try out their ideas.

Friday, October 21, 2011

Code Quality Tools

Now that my EatDifferent application code is getting cleaner, I wanted to make it even cleaner by using automated code quality tools.

First, I ran jshint over all my *.js files and fixed a bunch of little issues (like using "===" instead of "==" in many places). Here's how I setup my Makefile to download jshint and run it:

Then I ran reindent.py script to fix the indenting on all my *.py files. I had been using 2-space indents, as that's we used at Google and also what I use in JavaScript, but I was convinced to go with the PEP8 standard, 4 spaces.

Then I downloaded SublimeLinter, a plugin for Sublime Text that automatically checks your code as you write it, using jshint for JS and pyflakes for Python. I configured that to ignore a few PEP8 warnings in the settings JSON ("pep8_ignore": [ "E501", "E221", "E203"]). I'm quite liking SublimeLinter - its lint tools actually catch a few things that could result in real bugs and its nice to be able to correct my code as soon as I type it.

PhoneGap Loading Performance in iOS

As I wrote about earlier, I've been working on the performance of my PhoneGap app, implementing many of the suggestions from this article.

One of the suggestions that article makes is to switch frameworks from something heavy like jQuery (which includes a lot of extra code that may not be necessary on mobile browsers) to something lighter like Zepto or XUI.

I currently use jQuery in my app -- not because I use jQuery a lot in my own code, but because I use a handful of third-party-written jQuery plugins. I could easily port over my own code to a new framework but I don't know how easy it would be to port over someone else's.

So, before I looked into porting from jQuery, I wanted to figure out exactly what effect jQuery had on performance. I'm most concerned with user-facing loading latency versus already-running performance as that's where I perceive the greatest latency in my app, so I decided to see how long it took for the browser to load my HTML, CSS, and JS.

First I wrote a basic function to record times of events and I used it to record events after the CSS, HTML, jQuery script tag, other script tag, document ready, and execution of my setup function This is a stripped down version of what it looked like:

Then I recorded the times in Chrome, iPhone Simulator, and actual iPhone (on the same WIFI network), and graphed them in a spreadsheet. Here's a chart of the results:

As you can see from the chart, the iPhone browser is noticeably slower than Chrome and the simulator, and the jQuery script tag (90 KB) takes 35% of the loading time - same as the custom script tag (220KB). But it's only 300ms, which isn't as slow as I expected and for now, it doesn't seem worth it for me to try to port away from jQuery.

Update (11/20/2011): I have since ported away from jQuery and written that up in this post..

Update (12/13/2011): It's been pointed out that when it comes to measuring the "loading times" for resources, this is not the most precise technique, as it it doesn't reveal the difference between the download time, the parsing time, and the execution time. (In the case of jQuery, I believe much of the time I recorded came from the execution.) For that breakdown, I've been recommended weinre.

Saturday, October 15, 2011

JS & CSS Compiling, Compression & Cache-Busting

Everytime I deploy a new version of the CSS and JavaScript for EatDifferent to production, I run it through a series of steps to ensure code quality and performance:

  • Code quality: I use JSHint to check for JavaScript code quality issues. Sometimes it's a matter of style, but other times it actually finds issues that can become runtime bugs.
  • Concatenation: I use cat to combine my JS files and CSS files into one file each, so that the browser can issue less HTTP requests when loading the page.
  • Compression: I use Closure Compiler to minify my JS and YUI Compressor to minify my CSS, so that those HTTP requests are smaller.
  • Cache bust: I append the current timestamp as a query parameter to the JS and CSS in my base template HTML. I serve the files as static files off App Engine which would normally result in browsers caching them forever, but by appending new query parameters for each deploy, I force the browsers to re-download them only when they've changed.

I do all of this in a Makefile, including downloading the necessary tools. You can see the relevant bits in this gist:

Thursday, October 13, 2011

Modularizing My JavaScript

I generally try not to get too distracted by the code quality in EatDifferent and focus on user-facing quality instead, but after a while, it hurts my head knowing that my code is messy -- and makes me not want to mess with the code further. So, I spent yesterday spring cleaning my JavaScript.

One of the big improvements was to take my smattering of global functions and put them in a namespace. There are a lot of ways to namespace in JS, but I opted for the module pattern described in this article (#3).

Here's the basic template for each module's JavaScript file -- notice how this technique lets me create functions in each module that are only used inside the module, and aren't exposed outside of it.

    var ED = ED || {};

    ED.util = (function() {

      function doSomethingPrivate() {
      }

      function doSomething() {
        doSomethingPrivate();
      }

      return {
        doSomething: doSomething
      }
    })();

I ended up with 6 JS modules used across the web and mobile (PhoneGap) version of the app.

ED.util Utility functions, app-independent
ED.data Global data constant definitions
ED.models Classes representing data from the server DB
ED.shared App functionality shared by web and mobile
ED.web Web specific functionality
ED.mobile Mobile specific functionality

I could have put the first 4 of these in "shared" but I like the conceptual division, and I'm concatenating them together before serving them to the user, so it doesn't hurt to have multiple files.

Now that my code is cleaner and more manageable, I can more confidently iterate on user-facing features!

Tuesday, October 11, 2011

Grammatical Personalization in JS

In EatDifferent, I have various places where I describe something about a user. For example:

  • "Pamela Fox filled out her logs 3 days in a row.
  • "In the last week, you haven't logged any measurements."

I wanted to be able to construct those sentences in JavaScript with some sort of mini templating language. I didn't find any existing libraries for it, so I wrote my own. Now to generate the above strings, I can write stuff like:

personalize('{{ They|name }}' filled out {{ their }} logs 3 days in a row.', 
   {gender: 'male', person: 'third', name: 'Pamela Fox'});
personalize('In the last week, {{ they|name }} {{ have|not }} logged any measurements.',
    {gender: 'male', person: 'first'});

You can see the code for the library in this gist and check out a live demo on jsfiddle.