NPM, Bower, and Grunt. Oh My!
If you're following along from my last post on Homebrew you should have Homebrew installed and have used it to install Node.JS on your computer. I'll be honest, when I first heard of software projects that were using Node.JS like Ghost (the blogging platform that I use here) I thought "Why in the world would anyone want to develop their software using something that wasn't compatible with most shared hosting providers in LAMP environments? Isn't PHP good enough?" I can't answer for you whether one is better than the other for your needs however one thing that I've come to realize is that the power in Node is the fact that you're writing Javascript, a language built for the web. If you're developing an application that will ultimately exist as a web application, it starts to make some sense. Another thing I've found as I've dug in more into this stuff is that you don't have to be married to using Node.JS as the actual language you build your app in to utilize it locally for other things. In particular there's a powerful tool baked in called NPM, a package manager for javascript packages.
Now you might be thinking "What the hell Tim, you already had me install a package manager last time!" I know, and I'll go ahead and warn you that we're going to use 2 more before this blog post is over. Essentially you'll find some packages are only available in specific tools and environments so a combination approach is better (although I know some that try to use NPM for everything when possible). NPM is Node's Package Manager and you can read more about it at http://npmjs.com/ along with looking at all the packages that are available. Anyone can build a package and publish it to NPM and if you've ever been looking at a project on GitHub and noticed a package.json file that indicates it's an NPM install. Let's take a look at the json file to see what's in there, this one is from a web framework called Express which is great for building the networking and API bits of a web application:
{ "name": "express", "description": "Fast, unopinionated, minimalist web framework", "version": "4.10.7", "author": { "name": "TJ Holowaychuk", "email": "tj@vision-media.ca" }, "contributors": [ { "name": "Aaron Heckmann", "email": "aaron.heckmann+github@gmail.com" }, { "name": "Ciaran Jessup", "email": "ciaranj@gmail.com" }, { "name": "Douglas Christopher Wilson", "email": "doug@somethingdoug.com" }, { "name": "Guillermo Rauch", "email": "rauchg@gmail.com" }, { "name": "Jonathan Ong", "email": "me@jongleberry.com" }, { "name": "Roman Shtylman", "email": "shtylman+expressjs@gmail.com" }, { "name": "Young Jae Sim", "email": "hanul@hanul.me" } ], "license": "MIT", "repository": { "type": "git", "url": "https://github.com/strongloop/express" }, "homepage": "http://expressjs.com/", "keywords": [ "express", "framework", "sinatra", "web", "rest", "restful", "router", "app", "api" ], "dependencies": { "accepts": "~1.1.4", "content-disposition": "0.5.0", "cookie-signature": "1.0.5", "debug": "~2.1.1", "depd": "~1.0.0", "escape-html": "1.0.1", "etag": "~1.5.1", "finalhandler": "0.3.3", "fresh": "0.2.4", "media-typer": "0.3.0", "methods": "1.1.1", "on-finished": "~2.2.0", "parseurl": "~1.3.0", "path-to-regexp": "0.1.3", "proxy-addr": "~1.0.4", "qs": "2.3.3", "range-parser": "~1.0.2", "send": "0.10.1", "serve-static": "~1.7.2", "type-is": "~1.5.5", "vary": "~1.0.0", "cookie": "0.1.2", "merge-descriptors": "0.0.2", "utils-merge": "1.0.0" }, "devDependencies": { "after": "0.8.1", "istanbul": "0.3.5", "mocha": "~2.0.0", "should": "~4.3.1", "supertest": "~0.15.0", "ejs": "~1.0.0", "marked": "0.3.2", "hjs": "~0.0.6", "body-parser": "~1.9.3", "connect-redis": "~2.1.0", "cookie-parser": "~1.3.3", "express-session": "~1.9.2", "jade": "~1.7.0", "method-override": "~2.3.0", "morgan": "~1.5.0", "multiparty": "~4.0.0", "vhost": "~3.0.0" }, "engines": { "node": ">= 0.10.0" }, "files": [ "LICENSE", "History.md", "Readme.md", "index.js", "lib/" ], "scripts": { "test": "mocha --require test/support/env --reporter spec --bail --check-leaks test/ test/acceptance/", "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --require test/support/env --reporter dot --check-leaks test/ test/acceptance/", "test-tap": "mocha --require test/support/env --reporter tap --check-leaks test/ test/acceptance/", "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --require test/support/env --reporter spec --check-leaks test/ test/acceptance/" }, "gitHead": "ff5e96c88b23ebf0bb9bf99f9195b5b40215502f", "bugs": { "url": "https://github.com/strongloop/express/issues" }, "_id": "express@4.10.7", "_shasum": "0652f8cd5d0e2949d77b7dea7c5208161ec81ac6", "_from": "express@*", "_npmVersion": "1.4.28", "_npmUser": { "name": "dougwilson", "email": "doug@somethingdoug.com" }, "maintainers": [ { "name": "tjholowaychuk", "email": "tj@vision-media.ca" }, { "name": "jongleberry", "email": "jonathanrichardong@gmail.com" }, { "name": "shtylman", "email": "shtylman@gmail.com" }, { "name": "dougwilson", "email": "doug@somethingdoug.com" }, { "name": "aredridel", "email": "aredridel@nbtsc.org" }, { "name": "strongloop", "email": "callback@strongloop.com" }, { "name": "rfeng", "email": "enjoyjava@gmail.com" } ], "dist": { "shasum": "0652f8cd5d0e2949d77b7dea7c5208161ec81ac6", "tarball": "http://registry.npmjs.org/express/-/express-4.10.7.tgz" }, "directories": {}, "_resolved": "https://registry.npmjs.org/express/-/express-4.10.7.tgz" }
There's a lot going on there but hopefully it's pretty easy to understand. The json file is providing metadata about the project, the name, description, who developed it or contributed to it, the Github repo it can be downloaded from, etc. It also provides a list of dependencies which NPM can install for you at the same time it installs this application. So instead of you having to go run around and grab all the various requirements and piece it together with fingers crossed that you got everything, you can just open Terminal, create a folder for the project, and then within that folder type npm install express
and Node will go to work getting it all setup for you.
You can do the same by grabbing a copy of whatever project you're interested in from Github and cloning it to your computer, and if it's got a package.json file within it you navigate to that folder and type npm install
and it will read through the file and make sure all dependencies are installed and available. Occasionally you made need to have access globally to a piece of software so that you can use it in any folder, in that case you'll use the -g command. A great project I've been playing with called Sails.JS is installed by typing npm install -g sails
and once that's installed I can fire up a new Sails project anywhere by typing sails new nameofproject
and it will create the folder based on the name of your project and install everything to get it bootstrapped.
Our third and final (I think...) package manager I want to mention is Bower. If Homebrew is for your low-level system installations and NPM is for particular applications and frameworks, think of Bower as the components you may want to install within the project you're working on (it won't always hold up but it's a close aproximation of the way people use them). In order to use the bower command we have to have it installed on our system globally so we'll install it using non other than our good friend NPM.
npm install -g bower
Now within any project we're working on say you want to use the Font Awesome icon library (which lives up to it's name in my opinion). Instead of downloading the files manually and finding the right spot to keep them you can simply type bower install fontawesome
within your project directory. Bower will grab the latest copy of the files and drop them into a folder titled bower_components. This helps you keep all your third-party components organized within your application.
One last tool I want to look at very briefly (it's not a package manager! I swear!) is a tool called Grunt. Grunt is a powerful tool that can run a series of tasks on your project either on-demand with various commands or on the fly by watching the contents of certain folders. Tasks are written in a Gruntfile. Before you can use it we need to have the grunt command globally available on our system. Hmmm, how might we do that?
npm install -g grunt-cli
Bingo. So let's take our previous example of wanting to add Font Awesome to our project. We installed it with Bower which saved us from having to find the site, download the files, and copy them to a directory. Wouldn't it be awesome if we didn't have to go into our project and add references to those files either? Grunt can watch our bower_components directory for any changes and add the references to the files automatically to locations we want by putting comments in our files. It's a plugin (there's a Grunt plugin for everything) called grunt-wiredep and you can read on that GitHub link about how to install and use it. Another great Grunt task is running a local server for your files. Grunt will package up your application and serve it on the fly to the port of your choice so you can keep your browser open to http://localhost:3000 or something like that and view your app as you develop it. That plugin is called grunt-serve. You can even have it do live-reloading anytime you save a file! All of these plugins get installed with NPM and you add plugins as you need them.
Putting it all together
So, that's a lot of stuff I probably could have broken up into several posts but they all kind of relate to each other in terms of getting your development environment up and running. If you want to play around with something I'd encourage you to clone MeanJS, a project that incorporates MongoDB, Node, AngularJS, and a lot more that I plan to work with in the next few posts, and then go into there and do an npm install
and once it's done type grunt
and open up your browser at http://localhost:3000. Make sure you have the other global installs done like MongoDB and Express, it's a great framework for starting to play with this stuff and a good example of using some of what I've talked about so far and what we'll continue with in the next few posts.