Let's Make a Framework: npm
Let’s Make a Framework is an ongoing series about building a JavaScript framework from the ground up.
These articles are tagged with lmaf. The project we’re creating is called Turing. Documentation is available at turingjs.com.
There’s no reason why you can’t distribute your client-side JavaScript using npm! In this tutorial I’ll look at adapting parts of Turing to work using the popular package manager.
Installation
Distributing packages with npm is extremely easy. This is partly due to the fact that npm itself is very simple, but it’s also because npm has great documentation.
If you don’t already have npm, the basic installation is just:
curl http://npmjs.org/install.sh | sh
There’s more information in the npm readme.
Registry Account Creation
A registry user account is required to distribute npm packages. Use man npm-adduser to read how this works. If you’ve forgotten your password, npm now has a password reset page at admin.npmjs.org.
package.json
Once you’ve got an account, you’ll need to create a package.json file. You don’t really need a tool to do this; it’s pretty trivial as long as you write well-formed JSON. Like most people I usually forget what various supported options are, so I usually check man npm-json to make sure I’ve got everything I want set up.
When working on most Node projects, I’ll have a dependencies property, but this project doesn’t really have any. We do have some development dependencies though, so these can be put into the devDependencies property. This is preferable to dependencies because people who just want to use your module don’t usually want to build or test it.
This is what the Turing package.json file looks like:
{ "name": "turing"
, "description": "A library for enumeration, functional programming, promises, and more"
, "version": "0.0.73"
, "url": "http://turingjs.com/"
, "author": "Alex R. Young <alex@helicoid.net>"
, "engines": ["node >= 0.4.0"]
, "main": "./lib/index.js"
, "devDependencies": {
"dox": "latest"
, "jake": "latest"
}
, "repository": {
"type" : "git"
, "url" : "https://github.com/alexyoung/turing.js.git"
}
}
Adapting Turing’s Modules
In the browser Turing uses a global to stitch various modules together. This was done so it can be safely split into self-contained modules. However, this pattern makes adapting it to a CommonJS module slightly awkward.
As an example, consider Turing’s enumerable module and turing.init. The init method is from the Core module, and allows us to extend the behaviour of turing(). That means we get jQuery-like polymorphic initialization:
turing([1, 2, 3]).map(function(i) { return i * 10; }).values();
Great! But that means the enumerable looks for turing.init. We can’t share globals in the same way using CommonJS modules. That means the anonymous function wrapper isn’t quite enough.
This old pattern worked well in the browser, but doesn’t suit CommonJS without modification:
(function() {
turing.myModule = {
// Methods
};
})();
What we need is a way of passing turing after it’s instantiated in turing.core.js. That gives rise to this pattern:
(function() {
function myModule(global) {
global.myModule = {
// Methods
};
}
if (typeof module !== 'undefined') {
// For Node
module.exports = function(t) {
return EnumerableModule(t);
}
} else {
EnumerableModule(turing);
}
})();
Now, in the lib/index.js mentioned by the example package.json:
var turing = require(__dirname + '/../turing.core.js').turing;
require(__dirname + '/../turing.promise.js')(turing);
require(__dirname + '/../turing.enumerable.js')(turing);
module.exports = turing;
Now Turing can be loaded using Node:
var t = require('turing');
t([1, 2, 3]).map(function(i) { return i * 10; }).values();
Further iterations could make it load certain modules on demand. Ideally people should be able to do this:
var lessCommonModule = require('turing').lessCommonModule;
lessCommonModule.doSomething();
Testing
I actually wrote a small unit test for this in test/index.test.js:
var assert = require('assert')
, turing = require(__dirname + '/../lib/index.js');
assert.ok(turing.Promise);
var sum = 0;
turing.enumerable.each([1, 2, 3], function(i) {
sum += i;
});
assert.equal(6, sum);
It just makes sure everything gets loaded as expected.
Another way to test your npm modules is to use npm link. This links your package system-wide, so it behaves as if it’s been installed. This is useful for loading your modules in the Node REPL, or testing binary scripts that you might distribute alongside your package.
Publishing
Once you’re done with all that, run npm publish and your code will be available for distribution through npm.
Conclusion
Check all of this out in commit a54f32. And, if you’ve ever wanted to create a project website using GitHub, I’ve already got you covered in Part 48: Project Websites.