manage frontend dependencies with npm and browserify
This post is part of the series "the road to Visual Studio free frontend development":
- Frontend life with Visual Studio
- Frontend life without Visual Studio
- Frontend life is easy with Grunt
- Manage frontend dependencies with npm and browserify
You can browse the code of this post on github.
All variables should be declared before used. JavaScript does not require this, but doing so makes the program easier to read and makes it easier to detect undeclared variables that may become implied globals. Implied global variables should never be used. Use of global variables should be minimized.
As Duglas Crockford suggests the global variable default behaviour in javascript is evil, so it's safer to avoid it.
module pattern
Module pattern in javascript is the way you make your javascript life easy. Previously I spoke about the dependencies management and I mentioned RequireJs as the most obvious tool to choose. requirejs is a library that follows AMD specifications in order to manage modules in javascript.
To make a long story short, you can create your modules supporting AMD by wrapping your code within a clojure:
//file app.js
define(function (require) {
var logger = require('./log');
logger.write('hello world');
});
//file log.js
define(function () {
return {
write: function (message) {
console.log(message);
}
};
});
It sounds simple, but RequireJs comes with some drawbacks: it's not so comfortable to remember wrapping every file. I know: it's only a step of your development process to remember, but we can do better. My suggestion is to choose RequireJs only if you cannot use a task manager (like Grunt), because for RequireJs a task manager isn't mandatory (I think this is its best feature).
Fortunately I have a task manager, so I go on choosing a different dependency manager.
CommonJs
CommonJs is a different way to manage dependencies in JavaScript: it was created to support JavaScript on the server side (Node.js), thanks to some libraries now you can benefit from commonjs on the client side too.
CommonJs ensures that each module hides every variable to other modules by default: in order to expose variables outside, you have to export them explicitly.
Browserify
The library I'm choosing to manage dependencies is Browserify: it's simple and brilliant because you can write your code that requires dependencies just as well as you do in Node.js. At the build time, Browserify starts from the javascript entry point and recursively bundles up all your required modules in a single output file. The great thing is that Browserify detects all dependencies of your project for you: make available modules to Browserify (we speak about this detail in a while) and require them is the only things up to you.
For example, look at the script
html section of my website. Before using Browserify, it was:
<script src="vendor/html5.js" type="text/javascript"></script>
<script src="vendor/jquery.js" type="text/javascript"></script>
<script src="vendor/jquery.html5loader/animations/jquery.html5Loader.line.js" type="text/javascript"></script>
<script src="vendor/jquery.html5loader/jquery.html5Loader.js" type="text/javascript"></script>
<script src="scripts/loader.js" type="text/javascript"></script>
<script src="vendor/underscore.js" type="text/javascript"></script>
<script src="vendor/jquery.awesomeCloud.js" type="text/javascript"></script>
<script src="vendor/jquery.knob.js" type="text/javascript"></script>
<script src="vendor/jquery.scrollTo.js" type="text/javascript"></script>
<script src="vendor/waypoints.js" type="text/javascript"></script>
<script src="scripts/default.js" type="text/javascript"></script>
<script src="scripts/ga.js" type="text/javascript"></script>
but after using Browserify, I need only to declare the reference to the entry point of my app:
<script src="app.js" type="text/javascript"></script>
it's awesome! Tell me how to do that!
Let's start downloading the grunt-browserify
plugin:
npm install grunt-browserify --save-dev
now I can modify the grunt file removing the uglify section (this job is now provided by Browserify) and add a new Browserify section to the configuration:
grunt.initConfig({
//previous declarations...
browserify: {
main: {
options: {
bundleOptions:{
debug:true,
},
},
src : ['client/app.js'],
dest: 'publish/app.js'
}
},
//next declarations...
));
//previous dependencies loaded ...
grunt.loadNpmTasks('grunt-browserify');
grunt.registerTask('default', ['clean','copy', 'browserify']);
This configuration tells browserify to bundle all dependencies in a single file, and it provides the source map (thanks to the debug:true
option) so that during the debugging you inspect your application JavaScript files as splitted.
The configuration is done, now I can change my app code in the Node.js way:
//app.js
module.exports = appFactory();
function appFactory() {
"use strict";
var that = {};
var $ = require('jquery');
//...
that.start = function() {
$('html').animate({ scrollTop: 0 }, 1);
//...
};
//...
return that;
}
To be honest, I have simplified the grunt configuration so that it's focused only on the important work done by grunt. If you take a look at the real code, there are some interesting options for creating aliases and to make available to Browserify the not CommonJs compatible libraries (the option is called shim); for futher details look at Browserify and the grunt-browserify documentation.
What if I don't use grunt? Don't panic! Rob shows shows you how to use browserify in the standalone way.
A little interesting note before move on: you can require and use core (server) node modules in your client code; browse the node api for more details and take a look at this example where Rob shows how to take advantage of node api in order to render an html template without using any framework.
package manager
Ok, in both the Grunt configuration and javascript/html code there are no traces of the location of the dependencies.
How and where I do have to place my dependencies?
Here is where a (client) package manager comes in.
Remember what we said some posts ago? The most widely used client package manager is Bower, but it will not be my choice (in this context): the reason is, I already have a package manager for the dependencies.
Can I use npm for the client side dependencies as well? Definitely yes! The answer to the question "Why don't we use Bower?" is simple: one tool is better than two!
Sounds strange but most of the widespread client libraries are available in Npm.
Npm comes with another great advantage when used with Browserify: CommonJs by default uses Npm as package manager, so when you need to require a dependency, the dependency resolver looks for a module located in the same directory where Npm put it (the default is under the node_modules
directory in the root of the project). Let's go to download our client dependencies like this:
npm install jquery --save
npm install underscore --save
Now, If you check the package.json
file, you can see how devDependencies
section and the dependencies
section have changed as well:
{
"name": "gianluca.carucci.org",
"author": "Gianluca Carucci",
"devDependencies": {
"grunt": "~0.4.2",
"grunt-contrib-uglify": "~0.4.0",
"grunt-contrib-clean": "~0.5.0",
"grunt-contrib-copy": "~0.5.0",
"grunt-browserify": "~2.0.1"
},
"dependencies": {
"underscore": "~1.6.0",
"jquery": "~2.1.0"
}
}
This is how you can manage modules using Npm. If you need to require local modules from Browserify instead of default Npm modules, you have to require them using a relative path:
var mymodule = require('./../mymodule');
Browserify I love you
The only drawback of using Browserify is that you need a task manager to bundle up your JavaScript; if you have it, your life goes simple, if don't, your should choose is RequireJs. I found Browserify useful for these reasons:
- Client side development experience is (quite) the same as the server side one (so turn developers between the server side and client side are simplee)
- You deliver only one JavaScript file for the application
- It is advantageous to use the source map
- It is advantageous to use the Node.js server api
That's all for now. I have one more problem to solve: how can I avoid running Grunt manually when a source code file changes? This is the topic of the next post.