Adventures in Small Libraries

11/18/14

I’ve been doing some work recently taking client side components that need to be shared across projects and moving them from an app into their own git repositories as libraries. I wanted to share a little bit about the experience so far, and the tools I’ve been using to get the job done. It’s an interesting process and has, luckily, been going pretty smoothly.

Components in this case can mean either a pure JavaScript module or a combination of JS, HTML, and CSS that make up a functioning widget.

Determining the value of separating

One of the first questions I have to ask is whether or not a given component is sufficiently large, complex, or desiring of consistency to warrant being in its own repository. Each of the conditions seem to be equally valid, but weighted differently depending on the scenario. For most cases where I debated this question, the answer was yes-- as some maintenance now, despite the headache or busy work involved, will be a big win for maintenance in the future.

Any module that does a significant amount of its own work is a good candidate. A few examples would include a module that requires several options on instantiation, a module which controls several visual interactions, a module that has its own method of fetching and/or formatting data, and a module which has its own templates or method of generating markup.

There have also been some scenarios where separating script is either overkill or just doesn't make sense. I've been careful to identify and avoid these. For starters, anything that has deeply embedded app specific logic or naming schemes can be ruled out. Any script that is initialization and configuration focused which will likely change from project to project can also be ruled out. I've avoided code interacting with state mechanisms. State mechanisms, in this case, include things like managing cookies or interacting with web storage (the exception of course being if that is the module's sole purpose). I've also ruled out code defining analytics tracking as its a dependency that often requires updates or adjustments on a per app basis. Analytics may also not be present at all, so keeping it separate or separating out any explicit tracking code in favor of firing appropriate events has worked well.

Handling Dependencies

After figuring out if something should be separated, I next determine how the dependencies (if any) should be managed. How will those dependencies be included in both the new repository and the app I'm separating the component from? In some cases, listing the dependency and making sure it's included in the right place is enough. In the case where I’ve got JS, HTML, and CSS, I’ve started creating a standalone version so it can be visually tested in addition to unit tests.

As for a version that can be included, I provide two builds-- one with and one without the dependencies bundled. The one without dependencies is intended for use in the apps it will be shared across. The one with dependencies bundled is for use in standalone testing.

Tools

The tools I’m using all fit nicely together and are now my default for creating a new client side library.

  • - Node/NPM: The package manager I use as the foundation to provide tools for building and client side package management.
  • - Bower: The package manager I use for client side JS dependencies, and as the package manager I use to publish or to pull directly from a git url into an app. I typically have this installed globally.
  • - Gulp.js: I use Gulp for the build processes, notably less compilation, JS concatenation and minification, and versioning.
  • - Karma, PhantomJS, and Jasmine for unit testing.

Build

The end result requires only a small set of commands to manage dependencies, build processes, and tests.

      
  // install node dependencies, most notably, gulp and gulp plugins
  npm install

  // install client dependencies
  bower install

  // (default task) cleans and then builds standalone and distributable versions
  gulp

  // run unit tests against build versions in phantomJS with singleRun=true 
  // Pro Tip: don’t forget the Nyan Reporter
  karma start ./path/to/karma.conf.js

  // (patch/minor/major) bump package.json and bower.json versions
  gulp bump-{type}

  // tag the bumped version in git
  git tag v0.0.1
      
    

I’ve considered using gulp-git to reduce versioning by one step but I prefer to do that step by hand right now.

Here are examples package.json, gulpfile.js, and karma.conf.js, which include many of the basics I use for the process.

A few takeaways I have so far:

Write a solid README

If possible, I try to do this before I do anything else. I find writing out sections about install, build, and including a new library helps plan out the process, needed changes, and any other details I may not have considered already.

Expect breaking changes

In all the cases where I’ve started sharing a module across projects, I’ve almost immediately needed to make updates or adjustments to the module. Sometimes the updates are facilitated by the need for more flexibility, and other times because something was too tightly coupled. These updates often result in breaking changes to one or both of the projects. After the first iteration of breaks, I started expecting and planning for it better and then figuring out ahead of time what is going to need to change. The most important thing now is to keep the projects who use the shared resource up to date with changes, which leads me to my next takeaway.

Version aggressively

Every incremental change receives a version bump and git tag, making it trivial to track and update in each of the projects it is shared in. I have only bumped a minor or major version once my module is either supported by unit tests or a standalone visual test.

Test it, when it’s ready

Taking any number of factors into account, tests may or may not exist for a component before it needs to be shared. In my opinion, this is okay-- as long as I have some confidence in its functionality and someone else has reviewed, or at least glanced at, my code. Once I've stabilized the movement of the module, I test it if I can. I’ve tried a few different testing tools and have come to like the group I listed above. Having more confidence in a piece of code’s correctness and performance, no matter how small, is always a win. That being said, some functionality is just hard to test and for those instances I just make sure to visually test as best I can.

Look at other projects for good examples

I'm always trying to learn more about open source projects. Whether I'm using a project or just interested in something new, taking a close look at other people's work has been an invaluable resource.

I hope you enjoyed the documentation of my experience. Any feedback, suggestions, and/or new ideas are welcome!

comments powered by Disqus