Browserify 101

 

Open Source | DevOps | Operations | Cloud Automation | DevOps Tools | OpenStack | Browserify

The code for this post can be found here.

Introduction

Browserify is a nifty library that should be buzzed more.
On its front page it claims to `let you use require in the browser by bundling up all your dependencies`.
However I have to disagree. A lot of libraries give you that while Browserify give you node code compatibility to the browser.
Naman Goel explains this important difference very clearly in his blog post: http://blog.namangoel.com/browserify-vs-webpack-js-drama.
While I have seen some cool uses for it in frontend only, in this blog post I will only consider nodejs code as input for Browserify.
Let move on to some examples.



Experience the awesomeness of the Cloudify Blueprint Composer today.  Go


Hello World

If I write the following code for node
`console.log(’hello world’);`
It will run in the browser, but where’s the fun?
I install Browserify with `npm -g install browserify` (use `sudo` if needed) and I run the command:
`browserify -e helloworld.js -o helloworld.browserify.js` I get the following output

As you can see, Browserify adds a very long line of code, then it simply appends my code, and closes with some brackets.
There are 2 things to notice here

  • The definition of require/module and exports at the end of the first line – this what allows code compatibility as is in the browser
  • The number `1` at the bottom – this helps Browserify to link different files together later on. Since I did not `require` anything we didn’t get to see it in action.

Obviously we can think of some limitations right ahead. Although we wrote `console.log` which runs on browser, reading a file will not. We will also see how to overcome some of these difficulties.

What is Browserify?

Now that we know what Browserify does, we can say the following

  • Browserify makes your nodejs code work on the browser
  • It concatenates my files in a smart way. I don’t have to specify all the files to concatenate, nor do I need to specify them in the correct order. I only have to supply it with an entry point. Browserify traverses the dependency tree by finding `require` statements in the code and it figures out the dependencies by itself.
  • It brings a version of require, export and module to the frontend – but the important thing here is that it keeps the code readable! If you ever find the need to debug the code in the frontend, I bet you will find it very convenient to have the code you wrote as is in the a single file.
  • It manipulates the code and actually allows you to write plugins to manipulate it further. For example, you can find the following nifty plugins:
    • ngify – allows you to write simple javascript objects, and by adding some annotations to transform it to an angular component.
    • es6ify – allows you to write ECMAScript 6 code and then transcompiles it to ECMAScript 5.
    • brfs – find `fs.readFileSync` in your code and injects the file’s content instead.

Here is a list of available transformations.

Who is using it?

We use it for our REST API Javascript Client. Other customers include: reactjs, mapbox, elasticsearch and cloudflare.
Travis does not use browserify. We will explore, in just a few moments, how easy it is to write a Travis REST Client with Browserify.

When to use Browserify

So I think I made my opinion quite clear up until now. Browserify is mainly for converting nodejs code to run on browsers. However, even though this is what I think you should do, there are other things you could do. and it will be a shame if we didn’t mention some of those.
Project internal reusable code (should)
A lot of projects have code that is very usable in backend and frontend.
For example – models can be very useful. Thinking about Travis and the `Repository` model, I can think of a line of code such as `new Repository(data).getName()` that I will want to run in backend and frontend.
External api client (should)
As mentioned above we will shortly show how to use it for REST Client. But here is a preview how it will look like when people use it. note they will be able to use it in any framework they wish: angular, jquery, vanilla, and nodejs.

When it is easier to write code in a different way (could/should)
using the transformation feature, you can also use browserify if it is easier to write code differently. For example if there are things easier to write in es6`var log = msg => console.log(msg);`. or if you have javascript code you need to run in angular:

I suggest you be very strict about choosing your library here. If you want to simply transform code rather than have it run on node and browser, there are other, probably better libraries that made that their main goal.
Any chance you get (could)
Some people like to stretch the limits. If you are one of those people, go ahead 🙂
Here is an image about Browserify at ng-conf 2014 given by Ben Clinkinbeard.
You can see the lecture at: http://www.youtube.com/watch?v=NTPutZ99XWY.
Someone even wrote a seed for it.
However, I do recommend you check out other libraries that for me seem more relevant for this purpose.

Writing a Travis Client

I chose one api call in Travis to start implementing. You can read their official documentation at: http://docs.travis-ci.com/api/#branches
First lines of code in node
I wrote the following code:

`Client.js` is the actual client. It gets a configuration object, which I will probably use further down the road.
`Repos.js` is a start at implementing their api with `/repos` in the url.
The only odd choice I had here is using `superagent` to generate the `http` requests. However there’s a reason for that, superagent supports both nodejs and browser.. read more about it at: https://github.com/visionmedia/superagent
`superagent`’s interface for a `get` request is `superagent.get(url).end(callback)`. It uses a promise like structure but with `end` rather than `then`.. we can work with this right? yeah we can!
At Cloudify we chose to use node’s `request` module and its browser supportive cousin `request-browser`. You can choose your own as long as it has browser and nodejs support.
Porting the code to all frameworks
The next step is to wrap the code above to support different frameworks. To keep our source organized we will keep a convention to use the framework’s name in the filename – with exception of nodejs, for it we will not use any special file marking. The logic is that for node we are not doing anything special, and so no need to indicate it in any way. You can choose your own logic regarding code organization.
. So we named our wrappers accordingly:

  • `TravisClient.jquery.js` – for jquery
  • `TravisClient.vanilla.js` – for clean Javascript without any framework.
  • `TravisClient.angular.js` – for angularjs
  • `TravisClient.js` – for node


As you can see, there’s hardly any work done.
Using Gruntfile to ease on conversion
We use grunt. gulp is also supported.
https://github.com/jmreidy/grunt-browserify.
Here is how it looks.
We keep our convention here as well. A bit tiresome, but I think it’s worth it.
Once again, nodejs does not require any special treatment.
Browserify will output 3 files to dist folder: vanilla, jquery and angular. You can keep minimizing the code and uglifying it as you wish. Simply use the plugins you know and love right now on the files you will get in the dist folder.
Lets use it!!!
Using the code will require 3 different HTML files to simulate each framework.
 

  • Vanilla:
     
  • JQuery:
     
  • Angularjs:
    Almost no code, and everything seems to work 🙂
    So far we barely wrote any code to help make this happen.
    We wrote more test code and configurations than anything else. This is awesome!!!
    But wait! Angular does not work! Two-way binding will not work.
    This is a problem we need to solve and thanks to our conventions we know exactly how.
    We need to modify `TravisClient.angular.js`.
     
  • A small fix for better angular experienceI did not do this for this demo, but here is the change we did in our REST API client where we used `request` module. It is very similar to the change needed for `superagent`. Simply wrap the callback invocation with `$timeout`.
    Why `$timeout` and not `$rootScope.$apply`?
    We chose `$timeout` because we will get an error if `$apply` is already in progress. Since we are async anyway, using `$timeout` is legitimate, plus it can handle the scenario if `$apply` or `$digest`is in progress without errors.
    Here is how it looks in our code.

    You might want to try and venture using `lodash`’s `_.wrap`. In ECMAScript 6 – this will be the perfect scenario to use `proxy`. Alas, we can’t use it yet as this feature does not translate to ECMAScript 5. See comment at: https://babeljs.io/docs/learn-es6/#proxies for more details

    Writing the tests

    Now we need to write tests for 4 different environments. How are we going to do that?
    Exactly the same way!!!
    Using libraries that can run both in frontend and backend
    Just like we chose `superagent`, we will need to choose libraries that run both in frontend and backend.
    I used `expect.js` , `mocha` and `jasmine 2.0` to get same API across all frameworks.
    Currently `jasmine_node` is still on `jasmine 1`. Whenever they change to 2.0 I might change my stack.
    Thankfully `mocha` and `jasmine 2` both use `done` for async tests.. otherwise this would not have been easy..
    Running the code in browser will require `karma` and in node we will use `mochaTest grunt plugin`.
    When do we use Browserify?
    We will use browsierify in the karma config file.
    Browserify has a preprocessor for karma that does the job.
    Read about it at: https://github.com/nikku/karma-browserify
    Organizing the files
    Once again we generate files per framework.

     
    A bit verbose, but worthwhile.
    As you can see each framework has a different entry point to the tests.
    The actual tests are written in `clientTest` folder. That could will run the same in every framework – just like the client itself.
    We chose to separate `initClient.js` to a different file since we are thinking ahead just like we added the `config` file in the constructor.
    If there is a test that wants to initialize the client with different `config` settings, the test will simply need to override `initClient.js` file. While the rest stays the same.
    Writing the wrappers
    The goal of each wrapper in our case is to put the client on a global scope. And so, it looks like this:

    Writing the actual test
    The test is pretty standard..

    Writing the grunt configuration
    The grunt configuration is pretty standard – note we do not browserify the files here..

    Karma configuration + Browserifying the test files
    This is the important step. Let’s see just one of the karma configurations.

    A word about Webpack and friends

    There are a lot of libraries recently that add `require` to frontend or such. This trend even increases as ECMAScript 6 approaches with a built in support for modules.
    I am sure you can use each of them to get the same results we got here.
    Personally, and this is my personal, objective view of things, it is better to use a library for the main purpose it declares.
    use grunt for build, yeoman for project and file generations. npm for backend installations and bower for frontend.
    Writing this, I know that in less than two years time I will probably hold a totally different opinion, as the entire community will probably shift, and that’s fine two years from now. I don’t support jumping ahead of the community. I am not using ECMAScript 6 even though there are transcompilers already and such.
    But that’s just my personal opinion.
    Browserify does the job well and without any effort.
    It took us less than a week to learn, implement and test the entire Cloudify REST API.
    Which is the same amount of time it would have taken us to do the same just for nodejs. So time added by Browserify is negligible.
    The amount of code we had to add in order to get support for each framework is laughable.
    If I wanted to require files at runtime in frontend, I would use `require.js` but that is not what I was set on doing.

 

comments

    Leave a Reply

    Your email address will not be published. Required fields are marked *

    Back to top