Browserify

By nathanharper

Sunday, Feb 15, 2015

When I joined InterExchange back in October, App was on the brink of a radical change — dirkkelly had recently set up the separate Styleguide project to manage our assets, leveraging the Facebook’s React framework to power our new placement system. The placement system, was a huge success, but when the dust settled, it was clear we had a serious mess on our hands.

Simple bugs that would have been quickly caught and squashed in our Ruby codebase were taking inordinately long amounts of time to track down in our JavaScript, or even finding their way into production. Neither of us had much experience maintaining a JavaScript application of considerable complexity, and it showed. It was clear that if we wanted to keep our code maintainable, We were going to have to do some research. The two most obvious victories, we decided, would be:

  • Testing
  • Code Linting

Why didn’t you have those things to begin with?

Ironically, we did! They just didn’t work. Although we never used either, both JSHint and Facebook’s Jest were installed in the project, for linting and testing respectively.

Why didn’t they work?

To understand this, it’s necessary to understand how Styleguide was originally built. We use Gulp for our project builds, and originally we had a bunch of JavaScript snippets spread out among many files, which Gulp would concatenate into one big JavaScript file, minimize, and dump into our build directory. For third-party packages, we installed browser-compatible versions of them with Bower and concatenated them all into their own file.

Why this was an unmaintainable mess

This approach made the code virtually impossible to test or lint. The key to having good tests is being able to isolate the chunk of code being tested, and this is particularly difficult when said chunk is referencing countless objects declared in some distant file or third-party dependency, with no indication of where it came from. JSHint, when we dared to run it, would spit out lines like this until it gave up and died:

line 69  col 22   'Spinner' is not defined.
  line 71  col 22   'React' is not defined.
  line 72  col 17   'React' is not defined.
  line 73  col 19   'React' is not defined.
  line 74  col 21   'Pagination' is not defined.
  line 74  col 103  'actions' is not defined.
  line 74  col 103  Too many errors. (66% scanned)

This was a pain for making the code work in a browser as well; the JavaScript files needed to be concatenated by Gulp in a precise order for all the dependencies to resolve.

Modules

The solution to our problem was clear: we needed a way to encapsulate all the parts of our code into fully defined modules with explicit dependency resolution. This way, any individual chunk of code we decided to test could know where to find its dependencies, and essentially be run as its own mini application.

Just to make life difficult, there are several different approaches to making modular code in JavaScript. The two most common are called CommonJS and AMD, and we went with the former.

To offer the most basic explanation of what comprises a CommonJS module, they explicitly require foreign dependencies, and export their values. A simple module might look like this:

var someOtherModule = require('./some_other_module');

var myFunction = function() {
  console.log('Welcome to my module!');
};

/**
 * module.exports defines the object that is exposed
 * when you require this module from some other place.
 **/
module.exports = {
  myFunction: myFunction,
  someOtherModule: someOtherModule
};

What about the browser?

Jest runs its tests with an embedded DOM inside NodeJS, which understands CommonJS just fine. But what about the browser? require is not defined in this context, so the CommonJS modules will not work.

Browserify

Enter Browserify. Browserify packages modular JavaScript code for the browser. Browserify is amazing. (Its cousin Webpack is possibly even more amazing, but I’ll get into that in another post!)

The way that Browserify works is that you give it the entry-point of your application as a single file path, and by recursively reading all the required modules, it builds a single bundled JavaScript file for you. No more dumb file concatenation!

gulp.task('javascript-components', function () {
  return browserify({
    fullPaths: false,
    entries: './src/js/main.js',
    dest: './build',
    outputName: 'interexchange-components.min.js',
    debug: true
  })
  .plugin('minifyify', {
    map: '/maps/interexchange-components.min.js.map',
    output: 'build/maps/interexchange-components.min.js.map'
  })
  .bundle()
  .on('error', function (err) {
    console.log(err);
    this.emit('end');
  })
  .pipe(source('interexchange-components.min.js'))
  .pipe(gulp.dest('build/js'));
});

What about external (third party) dependencies?

Browserify has several methods of specifying external dependencies, a technique referred to as “shimming”. For our purposes, I wanted to specify certain libraries that would not be included in the package, since they were being loaded globally from other script tags, but I still wanted to be able to require them from our modules so that Jest and JSHint would not get confused by the unspecified dependency. To achieve this, I used the browserify-shim plugin, and it was as easy as adding this configuration to our package.json file:

"browserify": {
  "transform": [
    "browserify-shim"
  ]
},
"browserify-shim": {
  "react/addons": "global:React",
  "reflux": "global:Reflux",
  "react-bootstrap": "global:ReactBootstrap",
  "react-radio-group": "global:RadioGroup",
  "intercom.io": "global:Intercom",
  "moment": "global:moment",
  "jquery": "global:$"
}

The result of this is that we could now write assignments like var $ = require('jquery'); in our code, and have it resolve to the jQuery package installed with NPM during tests, but Browserify will know it’s a shim for a global jQuery object, and remove the require call from the generated bundle.

The End

Well, it was a long journey, and I learned a lot about the NodeJS ecosystem and gained a lot of self-knowledge in the process. And best of all, our tests run!

> StyleGuide@0.0.1 test /Users/nathanharper/src/interexchange/styleguide
> jest

Found 8 matching tests...
 PASS  __tests__/AjaxCheckBoxFilter-test.js (0.797s)
 PASS  __tests__/Pagination-test.js (0.825s)
 PASS  __tests__/SearchFilter-test.js (0.175s)
 PASS  __tests__/AjaxSearchForm-test.js (1.121s)
 PASS  __tests__/JobOfferParticipantAgreementStore-test.js (1.289s)
 PASS  __tests__/AjaxBooleanFilter-test.js (1.413s)
 PASS  __tests__/AjaxSearchFilter-test.js (1.569s)
 PASS  __tests__/AjaxDateRangeFilter-test.js (1.731s)
8 tests passed (8 total)
Run time: 10.69s

Nothing beats the deep, tranquil sleep that comes from knowing all your tests are green.

Tune in next time, when I’ll be going into more depth on Jest, JSHint, and React. And coming up soon, I’ll be blogging my experience at React Week!