Managing JavaScript dependencies in the browser is hard. Library scripts typically create global variables and functions. Other scripts now depend on those global objects to do their work. This works but in order to load all required scripts we have to add <script> elements to our HTML, making sure to add them in the right order, and basically know what each exposes.
The problem
Consider the following client side code:
1: // Print a message
2: utils.print("Hello");
This depends on another piece of script below:
1: // Expose the utility object with it's print function
2: var utils = {
3: print: function(msg){
4: console.log(msg);
5: }
6: };
And for all of that to work we have to load the scripts in the right order using some HTML as below:
1: <!DOCTYPE html>
2: <html>
3: <head lang="en">
4: <meta charset="UTF-8">
5: <title>Browserify demo</title>
6: </head>
7: <body>
8:
9:
10: <script src="utils.js"></script>1:
2: <script src="demo.js"></script>
11:
12: </body>
13: </html>
Not really rocket science here but if we want update utils.print() to call a printIt() function loaded from yet another library we have to go back to our HTML and make sure we load the printIt.js as well. Easy in a small app but this can become hard and error prone with larger applications.
Browserify to the rescue
Using browserify will make managing these dependencies a lot easier. To understand how it works we first must take a quick look at how NodeJS modules work.
With node each module can take a dependency on another module by requiring it using the require() function. And each module can define what it exports to other modules by using module.exports. The NodeJS runtime takes care of loading the files and adding dependencies inside a module will not require a change anywhere else in the program.
This system works really nice but unfortunately the browser doesn’t provide this NodeJS runtime capability. One problem here is that a call to require() is a synchronous call that returns the loaded module while the browser does all of its IO asynchronously. In the browser you can use something like RequireJS to asynchronously load scripts but while this works file this is not very efficient due to its asynchronous nature. As a result people usually use RequireJS during development and then create a bundle with all the code for production.
Browserify on the other hand will allow us to use the synchronous NodeJS approach with script loading in the browser. This is done by packaging up all files required based on the require() calls and creating one file to load at runtime. Converting the example above to use this style requires some small changes in the code.
The demo.js specifies it requires utils.js. The syntax “./utils” means that we should load the file from the same folder.
1: var utils = require("./utils");
2: // Print a message
3: utils.print("Hello");
Next the utils.js specifies what it exports:
1: // Expose the utility object with it's print function
2:
3: var utils = {
4: print: function(msg){
5: console.log(msg);
6: }
7: };
8:
9: module.exports = utils;
Next we need to run browserify to bundle the file for use in the browser. As browserify is a node application we need to install node and then, through the node package manager NPM, install browserify with
1: npm install -g browserify
With browserify installed we can bundle the files into one using:
1: browserify demo.js > bundle.js
This will create a bundle.js with the following content:
1: (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
2: var utils = require("./utils");
3: // Print a message
4: utils.print("Hello");
5:
6: },{"./utils":2}],2:[function(require,module,exports){
7: // Expose the utility object with it's print function
8:
9: var utils = {
10: print: function(msg){
11: console.log(msg);
12: }
13: };
14:
15: module.exports = utils;
16: },{}]},{},[1]);
Not the most readable but then that was not what it was designed to do. Instead we can see all code we need is included. Now by just including this generated file we ready to start our browser application.
Adding the printIt() function
Doing the same change as above is simple and best of all doesn’t require any change to the HTML to load different files. Just update utils.js to require() printIt.js and explicity export the function in printIt.js, rerun browserify and you are all set.
1: function printIt(msg){
2: console.info(msg);
3: }
4:
5: module.exports = printIt;
Note that it’s fine to just export a single function here.
1: // Expose the utility object with it's print function
2: var printIt = require("./printIt");
3:
4: var utils = {
5: print: function(msg){
6: printIt(msg);
7: }
8: };
9:
10: module.exports = utils;
And the result of running browserify is:
1: (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
2: var utils = require("./utils");
3: // Print a message
4: utils.print("Hello");
5:
6: },{"./utils":3}],2:[function(require,module,exports){
7: function printIt(msg){
8: console.info(msg);
9: }
10:
11: module.exports = printIt;
12:
13: },{}],3:[function(require,module,exports){
14: // Expose the utility object with it's print function
15: var printIt = require("./printIt");
16:
17: var utils = {
18: print: function(msg){
19: printIt(msg);
20: }
21: };
22:
23: module.exports = utils;
24: },{"./printIt":2}]},{},[1]);
Again not the most readable code but the printIt() function is now included. Nice and no changes required to the HTML
Proper scoping
As a side benefit browserify also wraps all our JavaScript files in a function ensuring that proper scope for variables is used and we don’t accidently leak variables to the proper scope.
Using browserify works really nice but this way we do have to start it after every time. In the next blog post I will show how to use Gulp or Grunt to automate this making the workflow a lot smoother.
Enjoy!