Packaging for CommonJS, AMD & Others

While JS is not a compiled language, my personal preference is to use a build process to construct even fairly simple libraries. While there are some exceptions, the following illustrates the general directory structure of most of my JS libraries:

dist/
  commonjs/fong.js
  amd/fong.js
  glob/fong.js

src/
  fong.js

test/

README.md
Makefile
package.json

The entire dist/ folder is generated through a build process, and should not be edited by hand. Rather files in the src/ directory are those which should be updated by package authors. While using this approach does create some confusion on which file(s) a potential contributor should edit if they wish to fork the library (definitely a downside of this approach) hopefully this documentation should help explain why the approach is used.

Source Files are Package Agnostic

Using Interleave we can write our source files in a completely package agnositic way. For example, let’s assume that our fong library (as per the movie A Knight’s Tale), is going to require underscore to operate correctly.

To achieve this we supply a simple dep or req comment:

// req: underscore as _

Interally, Interleave uses the findme module requirement syntax which is it then translates into module specific outputs for the various distrubutions. Let’s carry on - time to make fong actually do something.

Our fong library is very simple, and will simply call a kick method on any valid targets in a crowd (array) of people. Here’s the code for the entire source file:

// req: underscore as _
function fong(crowd) {
	// find valid, fongable targets
	var targets = _.filter(crowd, function(target) {
		return (target.name === 'Chaucer' || target.annoying) &&
			typeof target.kick == 'function';
	});

	// kick each of the valid targets
	_.invoke(targets, 'kick');

	// return the kicked targets
	return targets;
}

While this is an extremely trival and contrived example it helps to demonstrate a few key points:

  • There are no CommonJS require statements in the library to include underscore.
  • There are no export mechanisms (globbing, module.exports or AMD define) to expose the functionality of this module.
  • The code is not wrapped in a closure, thus you might think the generated code would horribly pollute the global scope (in a more complicated example).

Building our Distribution Files

Building our distribution files is very simple if you have Interleave installed, and if not you can install it by installing it using NPM:

(sudo) npm install interleave -g

Once you have Interleave installed, simply jump into the examples/fong directory and run the following command in your console:

interleave build src/*.js --wrap

The –wrap option tells Interleave that we want it to provide platform specific variants of our input source file. The resulting files from running this process is shown below and can be found in the repository in the examples/fong/dist folder.

CommonJS (Node)

var _ = require('underscore');

function fong(crowd) {
	// find valid, fongable targets
	var targets = _.filter(crowd, function(target) {
		return (target.name === 'Chaucer' || target.annoying) &&
			typeof target.kick == 'function';
	});

	// kick each of the valid targets
	_.invoke(targets, 'kick');

	// return the kicked targets
	return targets;
}

if (typeof fong != 'undefined') {
    module.exports = fong;
}

AMD

define('fong', ['underscore'], function(_) {
    function fong(crowd) {
    	// find valid, fongable targets
    	var targets = _.filter(crowd, function(target) {
    		return (target.name === 'Chaucer' || target.annoying) &&
    			typeof target.kick == 'function';
    	});
    
    	// kick each of the valid targets
    	_.invoke(targets, 'kick');
    
    	// return the kicked targets
    	return targets;
    }
    
    return typeof fong != 'undefined' ? fong : undefined;
});

Globbing

// req: underscore as _
(function(glob) {
    function fong(crowd) {
    	// find valid, fongable targets
    	var targets = _.filter(crowd, function(target) {
    		return (target.name === 'Chaucer' || target.annoying) &&
    			typeof target.kick == 'function';
    	});
    
    	// kick each of the valid targets
    	_.invoke(targets, 'kick');
    
    	// return the kicked targets
    	return targets;
    }
    
    if (typeof fong != 'undefined') {
        glob.fong = fong;
    }
}(this));

Feedback

Feedback related to this page would definitely be appreciated and can be captured at the following url:

https://github.com/DamonOehlman/reusable-javascript/issues/1

Table Of Contents

Previous topic

What is Reusable JS?

Next topic

Versioning

This Page

Fork me on GitHub