Getting started with front-end automation: An intro to npm
At PreviousNext this past year, we have started using a style-guide-centric approach that matches our Agile development practices. We write our styles and markup as components and then use automation to build, test, and add them to a style guide.
Since we are a Drupal shop, you might expect that all our automation is done with PHP, but we’ve found, on the front-end side, the best tools are written in Node.js. Grunt, Gulp, Yeoman, Bower, Eslint and more are all written in Node.js and installed and distributed with its package manager, npm.
For those new to package managers, a package manager is just a way to install software written in a particular language or operating system. For example, Ruby software is often installed with the Gem package manager, Mac OS X command line utilities with the HomeBrew package manager, and Node.js software with npm. Incidentally, npm stands for the Node Package Manager.
Installing all those front-end tools is easy with npm. Indeed, each of those tools has installation instructions that say “install with npm install -g grunt-cli”. However, it’s hard to find good information on using npm to effectively manage your project’s tools. Getting to know npm and its commands is essential to ensure your entire team is using the same versions of your tools. Even minor differences in tool versions can cause hard-to-fix bugs and frustration.
In fact, once you know how to use npm, you’ll realize that using npm install -g grunt-cli is generally a bad idea. (Hint: it’s the -g that will cause you problems.)
Installing Node.js and npm
In order to use npm to install kss-node (the style guide tool we use) and other useful tools, you’re first going to need Node.js and its npm command installed on your computer.
On Mac OS X, I recommend installing Homebrew, a package manager for installing software on the Mac. And then using brew install node to install Node.js.
- Install Homebrew with: ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" (As recommended on the homebrew web site.)
- Install Node.js with: brew install node
Yes, we just used a package manager to install a package manager. Isn’t inception fun?
You can find out how to install Node.js on other operating systems on the Node.js website.
You can test your installation by typing npm list -g --depth 0. That will show you a list of what node.js software you have installed globally. Right now that list should only include npm.
Installing globally vs. locally
When you first start using npm, one of the more confusing aspects of it is the concept of installing something locally versus installing something globally.
The normal applications on your computer (Microsoft Word, Google Chrome) are installed globally. Meaning that if you you open a .doc file, it will open the one version of Microsoft Word on your computer. If you try to open a Word doc you wrote in 1985, it won't work because the version installed on your computer is too new to understand such an old file.
Node.js software gets around this problem by allowing you to install something "locally", meaning that the software will be installed inside a particular folder for use in that folder only.
This allows you to install a new version of software in a new project's folder while you continue to use an older version in another project's folder. For example, Sass 3.2 files don't always compile properly with Sass 3.4, so it would be extremely helpful to have Sass 3.2 installed locally in ~/Sites/myOldClient, while having Sass 3.4 (and its wonderful new features) installed locally in ~/Sites/myNewClient.
Because of how useful it is to install something locally, that is npm's default mode. npm install eslint will install the eslint program into a node_modules folder in the current directory.
Go to a new project's folder.
cd myNewProject
Install eslint into that project.
npm install eslint
Run eslint for that project.
./node_modules/.bin/eslint path-to-my-js-file-to-lint.js
Or use npm's bin command:
$(npm bin)/eslint path-to-my-js-file-to-lint.js
Ah… you may have noticed why some people want to install eslint globally. ./node_modules/.bin/eslint is kinda awkward, and $(npm bin) is awkward to remember and type. If you just use npm install -g eslint, npm will install the eslint software globally and you will only have to type the simpler eslint path-to-my-js-file.js.
But, really, don't use the -g option. Here's how (courtesy of StackExchange) you get the simplicity of not typing ./node_modules/.bin/ with the advantages of installing things locally:
Edit your ~/.bashrc (or your ~/.bash_profile file if you don't have a .bashrc one) and add this line to the end:
alias npm-exec='PATH=$(npm bin):$PATH'
That's it! After you open a new terminal window (or re-trigger your bash set up), you can:
Install eslint locally
cd myNewProject npm install eslint
From the project root or any sub-folder...
cd some/sub/folder npm-exec eslint file.js
Check which version of eslint is being used with:
npm-exec which eslint
The “which” command will show you the installation location of the specified command:
myNewProject/node_modules/.bin/eslint
As you can see, every time you prefix your command with npm-exec, the software used will be the one in myNewProject/node_modules/.bin. Sweet.
And it looks like npm will eventually get an "exec" command that will work the same way as this bash alias does.
Installing a bunch of node modules in a project
If you are going to install more than one node module in a project, you should get into the habit of creating a package.json file per project. The package.json file is a way to keep track of all the software you install for a project and makes it really easy to re-install them.
For example, you can move the package.json to a new folder and then type npm install and npm will read your package.json and install all of the node modules in the new location.
If you aren't copying a package.json file from somewhere else, you can make your own with:
npm init
npm will prompt you for some basic information like your project’s name. Then you can save the names of your node modules when you install them with:
npm install --save handlebars
There's one more thing you'll need to understand about installing packages with npm: the difference between your application's dependencies and its development dependencies.
For example, your application may require Angular.js to run and may require CSS to run, but it doesn't require the Sass preprocessor. Once the CSS is generated by Sass during development, Sass itself is no longer needed to run the application. So Sass is a development dependency of your application, while Angular is a regular dependency.
You install a normal application dependency with:
npm install --save angular
And a development dependency with --save-dev:
npm install --save-dev node-sass
If you look inside your package.json file, you'll see a 'dependencies'
and a 'devDependencies'
section listing the software you installed with --save and --save-dev.
Sharing a project
This last tidbit isn't necessary if you are the only developer on a project, but it’s quick to learn and you'll seem like an npm expert if you remember it.
If you are sharing a project with Git or some other version control system, the package.json will ensure that everyone using the project will have the minimal software requirements (e.g. kss-node 2.0 or later.) However, using a package.json doesn't ensure that everyone is using the exact same versions which can lead to inconsistencies or bugs. You can fix this problem by ensuring your project has an npm-shrinkwrap.json file, which, fortunately, you can easily generate with npm shrinkwrap --dev; then just add that npm-shrinkwrap.json file to Git. When another person checks out the project and runs npm install, npm will read the lengthy npm-shrinkwrap.json file and install the exact same versions of all the dependencies.
npm commands: a recap
npm list -g --depth 0
- Shows you the npm software installed globally (hopefully just npm itself.)
npm init
- Creates a package.json file to keep track of your dependencies.
npm install --save angular
- Installs a software dependency and saves its name in package.json.
npm install --save-dev node-sass
- Installs a development dependency and saves its name in package.json.
npm shrinkwrap --dev git add npm-shrinkwrap.json
- Ensures anyone using this project gets the exact versions of your dependencies.
npm install
- Installs all the dependencies after receiving a project with a package.json and optional npm-shrinkwrap.json.
npm-exec gulp
- This bash alias ensures the correct executable is used no matter which project folder you are in.
By understanding the above commands, you’ll be able to easily manage the front-end toolsets of each of your projects.