Index ¦ Archives  ¦ Atom  ¦ RSS

Better Pebble Javascript

In one of my apps, I was including a few libraries, and the resulting code was getting rather large and hard to manage. I also couldn't get it to work when the files were separate, so I was working with them all in the same file. Having some experience with webapps, I knew exactly what to do: uglify my code.

UglifyJS, that is. Specifically, UglifyJS2. I modified my wscript to combine all my javascript files into a single one before creating the pebble bundle using UglifyJS2. Actually, once you have UglifyJS2 installed, the wscript change is pretty simple.

This simple function will compile your javascript into a tiny file:

def compile_js(ctx, in_js_files):
    # Concatenate all JS files into pebble-js-app.js
    in_js_nodes = ctx.path.ant_glob(in_js_files, excl='src/js/pebble-js-app.js')
    out_js_node = ctx.path.make_node('src/js/pebble-js-app.js')
    # Cleanup js and comments
    uglifyjs = 'uglifyjs'
    comp_opts = ','.join([
        'drop_console', 'pure_getters','warnings','cascade','join_vars',
        'dead_code','sequences','properties','loops','unused',
        "pure_funcs=['DMath.sin','DMath.cos']",
    ])
    defines = ""
    options = '--screw-ie8 -c %s --lint -m toplevel %s' % (comp_opts, defines)
    inputs = ' '.join(node.relpath() for node in in_js_nodes)
    ret = ctx.exec_command('%s %s %s > %s' % (uglifyjs, options, inputs, out_js_node.relpath()))
    if ret != 0:
      ctx.fatal('uglifyjs failed')

    return out_js_node

To use it, replace the last two lines in your normal wscript file:

ctx.pbl_bundle(elf='pebble-app.elf',
               js=ctx.path.ant_glob('src/js/**/*.js'))

With these:

js_node = compile_js(ctx, 'src/js/**/*.js')
ctx.pbl_bundle(elf='pebble-app.elf', js=js_node)

This will provide your users a slightly better experience; uglifyjs removes any unnecessary code (like console logging via the drop_console option) speeding up your code and since most javascript engines store a copy of a function's source in memory, your javascript will be that much smaller and faster.

For my particular case, I had a library that had lots of comments, so my final js file went from 19K to less than 8K, a saving of almost 60%! While this isn't a major performance change, I admit, it is one of many steps to giving your users a great experience.

An Exaltation of the User Experience

Remember, while the javascript doesn't run on the Pebble, it does run on devices that actually have worse battery lives. I have to charge my phone daily, but my Pebble can go at least 5 days without a charge. If a Pebble app decides to start pinging the Internet constantly, my phone will die way before my Pebble.

Extra Options with UglifyJS2

In my code block above, you may have noticed a few different things, like the uglifyjs variable and the empty defines variable. I don't actually use that block exactly, I have some customizations.

uglifyjs

I actually don't have uglifyjs2 installed to my system, I have it checked out along with its dependencies into a nodejs-src folder. That's amdefine, async, minimist, node-optimist, node-wordwrap, source-map, and UglifyJS2, each with a symlink from a nodejs folder to the various source files/folders:

 amdefine.js -> ../nodejs-src/amdefine/amdefine.js
 async.js -> ../nodejs-src/async/lib/async.js
 minimist.js -> ../nodejs-src/minimist/index.js
 optimist.js -> ../nodejs-src/node-optimist/index.js
 source-map -> ../nodejs-src/source-map/lib/source-map
 source-map.js -> ../nodejs-src/source-map/lib/source-map.js
 wordwrap.js -> ../nodejs-src/node-wordwrap/index.js

Then I have uglifyjs set to:

 NODE_PATH=/path/to/pebble/app/nodejs/ ./nodejs-src/UglifyJS2/bin/uglifyjs

I copied this structure from a friend in college (initially with Python and using PYTHONPATH), and I think it works pretty well.

UglifyJS2 Options

There are quite a few options possible, I only set a few. For creating a pbw to upload to the app store, I stick with the ones I wrote above, but for development sometimes I add -b to beautify the output, which just adds whitespace by default. I can also normally don't remove console logging, but that's a compressor options.

Compressor Options

comp_opts is my current preference for compressor options, but they're not set in stone. You can add/remove anything from that list according to the readme of UglifyJS2.

Defines

Defines are great, but the one thing I like the most is the ability to pull another file into javascript. I can, since wscript is python, grab something from appinfo.json, or I can read authentication tokens into variables. I do the latter with my app's SetPebble token, which I have in .gitignore since I don't want to accidentally ship that with any open source apps. You can define a DEBUG variable as true or false to conditionally compile, much like the UglifyJS2 docs mention.

Here's my actual defines variable:

defines = "--define SET_PEBBLE_TOKEN='%s'" % (
    ctx.path.find_node('setpebble.token').read().strip())

Conclusion

With UglifyJS2 in your build process, you get plenty of benefits, including a slightly better user experence to give you an edge over the next guy writing a competing app. Also, just in case it seems otherwise, I have no affiliation with UgilfyJS2. I just found it when I was doing research for a previous job and liked how well it did its job.

© Fahrzin Hemmati. Built using Pelican. Theme by Giulio Fidente on github.