In this lesson we'll build a simple npm package. The package installs the greet
command:
$ greet howard
hello howard
If greet
had had a few more martinis then it should:
$ greet howard --drunk
hello howard, you look sexy today
By building this simple package, we'll become familiar with the core tools used in a nodejs development environment.
- How to create an npm package
- CommonJS module system
Let's use the npm init
command to start a new npm project in the greet
directory:
$ mkdir greet && cd greet
$ npm init
name: (greet)
version: (0.0.0)
description: A simple and naive greeter
entry point: (index.js)
test command:
git repository:
keywords: hello-world
author: Howard Yeh
license: (ISC) MIT
Is this ok? (yes) yes
It creates the file package.json
, which describes the project. Let's take a look:
$ cat package.json
{
"name": "greet",
"version": "0.0.0",
"description": "echo \"Error: no test specified\" && exit 1",
"main": "index.js",
"scripts": {
"test": "mocha"
},
"keywords": [
"hello-world"
],
"author": "Howard Yeh",
"license": "MIT"
}
Let's package a very simple greet
function as an node module.
function greet(name) {
return "hello, " + name;
}
In package.json
, there's the main
value:
{
"name": "greet",
...
"main": "index.js"
}
This means that index.js
is the file that would be loaded when the module is required.
Create index.js
. Just make it an empty file for now.
$ touch index.js
We can require this file directly (it exports an empty object by default):
$ node -e 'console.log(require("./index.js"))'
{}
Or we can require it without the .js
extension:
$ node -e 'console.log(require("./index"))'
{}
Or we can require the project directory (which requires the main
value of package.json
:
$ node -e 'console.log(require("./"))'
{}
All of the above loads index.js
as a module.
Instead of loading the module by path, we want to be able to load the module by name:
// require by module name
var greet = require("greet");
// is the the same as requiring
var greet = require("/path_to_greet_module/index.js");
Let's try to require the greet
module:
$ node -e 'console.log(require("greet"))'
module.js:340
throw err;
^
Error: Cannot find module 'greet'
We get this error because the module isn't installed. For development purposes, we can use the npm link
command to install the current project directory as package:
Hint: If the global node_modules path is not writable by user, you need to run sudo npm link
$ npm link
~/.nvm/v0.10.26/lib/node_modules/greet -> ~/greet
The installed greet
module is a symlink to the project directory. Let's require our module again:
$ node -e 'console.log(require("greet"))'
{}
It works.
To uninstall the linked package:
$ npm unlink -g greet
unbuild greet@0.0.0
Question: npm link
installs a package by creating a symbolic link. What's a symbolic link?
Question: npm link
installs the current directory as a global package. What's the difference between installing a package globally and installing it locally? Read npm 1.0: Global vs Local installation
Right now, our greet
module is empty. It's just the empty object:
$ node
> greet = require("greet")
{}
NodeJS uses the CommonJS module spec. Basically, whatever value you assign to module.exports
is the value returned by require
. See documentation on module for more details.
Implement: index.js should export the greet function
Hint: In index.js
, complete the following:
// file: index.js
module.exports = ...
Example:
$ node
> greet = require("greet")
[Function: greet]
> greet("howard")
'hello, howard'
npm link
is good for developing the package locally. npm pack is used to create a user installable package.
$ npm pack
greet-0.0.0.tgz
Running the packing command creates a compressed tarball (.tgz). We can look inside to see what it contains:
$ tar -ztf greet-0.0.0.tgz
package/package.json
package/index.js
You can install the tar file:
$ npm install greet-0.0.0.tgz
npm install greet-0.0.0.tgz
npm WARN package.json greet@0.0.0 No repository field.
npm WARN package.json greet@0.0.0 No README data
greet@0.0.0 node_modules/greet
Uninstall it:
$ npm uninstall greet
npm uninstall greet
unbuild greet@0.0.0
We want to be able to use the greet module as a command. Add the bin
field to package.json
(detailed doc):
// in package.json
"bin" : { "greet" : "./bin/greet.js" }
This specifies that when the package is installed, the script ./bin/greet.js
should be installed as the executable greet
.
Now let's create the executable script at bin/greet.js
:
#!/usr/bin/env node
console.log("Hello World");
The first line is a shebang directive. It's a way for the computer to know how to run the script as a program.
(Why do we use /usr/bin/env
instead of the node command? Because the node
executable can be at different places on different systems, and the env
uses the environment's PATH
to figure out which node
program to run.)
Change the file permission as an executable file:
$ chmod a+x bin/greet.js
Now we can execute the script:
$ ./bin/greet.js
Hello World
Let's run npm link
again:
$ npm link
~/.nvm/v0.10.26/bin/greet -> ~/greet/bin/greet.js
~/.nvm/v0.10.26/lib/node_modules/greet -> ~/greet
Notice how ~/.nvm/v0.10.26/bin/greet
is a link to our script. Now we can run the greet
command:
$ greet
Hello World
Implement modify bin/greet.js
so greet howard
uses the greet
function exported by index.js
HINT: Use require
with a relative path. See File Module
HINT: process.argv
is an array that contains the command arguments.
Example:
$ greet howard
hello, howard
Let's change the greet
function to accept an extra argument:
function greet(name,drunk) {
if(drunk) {
return "hello " + name + ", you look sexy today";
} else {
return "hello, " + name;
}
}
Implement: Use the minimist package to parse command arguments, and accept the --drunk
option.
Example:
$ greet howard --drunk
hello howard, you look sexy today
HINT: npm install minimist --save
to add the package as dependency.
HINT: var parseArgs = require('minimist')
is a function.
If you are not familiar with how require
find the right file to load, read Loading from node_modules Folders.
Create a repository called fork2-node-greet
on Github, and push your code.