Permalink
Browse files

[api] Write bundler, integrate with app.js

  • Loading branch information...
1 parent b9e7f91 commit 737917f3c14a93b53b6298ecc6b91374d76fc93f @jfhbrook committed Apr 11, 2012
Showing with 145 additions and 13 deletions.
  1. +32 −12 app.js
  2. +107 −0 bundler.js
  3. +6 −1 package.json
View
44 app.js
@@ -1,36 +1,56 @@
-var path = require('path');
+var path = require('path'),
+ utile = require('utile');
+
+utile.inspect = require('util').inspect;
var flatiron = require('flatiron'),
app = flatiron.app;
-// Set up app.config to use ./config.json to get and set configuration settings.
+// This is a new library used for running browserify bundle jobs for us.
+var bundler = require('./bundler');
+
app.config.file({ file: path.join(__dirname, 'config.json') });
+// Note that 'bundler' is written to work as a flatiron plugin.
app.use(flatiron.plugins.http);
+app.use(bundler);
-// This router syntax allows you to define multiple handlers for one path based
-// on http method.
app.router.path('/', function () {
-
- // This is the same functionality as previously.
this.get(function () {
this.res.writeHead(200, { 'content-type': 'text/plain' });
- this.res.end('hello!');
+ this.res.write('Welcome to the Browserify CDN! You probably want to post. Ex:\n\n');
+ this.res.end(' curl -X POST -d \'var traverse = require("traverse");\' address:3600\n');
});
- // Now, when you post a body to the server, it will reply with a JSON
- // representation of the same body.
this.post(function () {
- this.res.json(200, this.req.body);
+
+ // If the request body doesn't have the property we expect, it's assumed
+ // to be raw javascript. Note that the raw unparsed body is buffered into
+ // req.chunks (as a Buffer).
+ var req = this.req,
+ res = this.res,
+ js = req.body.js ? req.body.js : req.chunks.toString();
+
+ // 'app.bundler' was created when we attached 'bundler'.
+ app.bundler.bundle(js, function (err, data) {
+ if (err) {
+ return res.json(500, {
+ success: false,
+ reason: err.message || 'unknown'
+ });
+ }
+ res.writeHead(200, { 'content-type': 'text/javascript' });
+ res.end(data.bundle);
+ });
});
});
-// Now we're using app.config to set the port, with a default of 8080.
app.start(app.config.get('port') || 8080, function (err) {
if (err) {
throw err;
}
var addr = app.server.address();
- app.log.info('Listening on http://' + addr.address + ':' + addr.port);
+
+ app.log.info('Browserify-CDN Listening on http://' + addr.address + ':' + addr.port);
});
View
@@ -0,0 +1,107 @@
+var EventEmitter2 = require('eventemitter2').EventEmitter2,
+ browserify = require('browserify'),
+ detective = require('detective'),
+ npm = require('npm'),
+ crypto = require('crypto'),
+ util = require('utile');
+
+// I wrote the Bundler as a constructor with prototype methods.
+// I find that it's a good fit for stateful problems.
+var Bundler = module.exports = function (opts) {
+
+ // This lets you create a new Bundler without the 'new' keyword.
+ if (!(this instanceof Bundler)) {
+ return new Bundler;
+ }
+
+ var bundler = this;
+
+ // Set the bundler's persistent options here. Also handle defaults.
+ bundler.options = opts || {};
+ bundler.options.npm = bundler.options.npm || {};
+
+ // Overrides falsy values such as `undefined`
+ if (bundler.options.cache !== false) {
+ bundler.options.cache = true;
+ }
+
+ // Bundler inherits from an EE2 with wildcards and the :: delimiter.
+ EventEmitter2.call(this, {
+ wildcard: true,
+ delimiter: '::',
+ maxListeners: 0
+ })
+
+ // Bundler requires a loaded npm in order to work properly.
+ // The rest of the code in the constructor sets this up and emits events
+ // to signal when it's ready.
+ bundler.ready = false;
+
+ npm.load({}, function (err) {
+ if (err) {
+ // What would be really cool is if I checked to see if there was an error
+ // event listener or not.
+ bundler.emit('error', err);
+ }
+
+ bundler.ready = true;
+ bundler.emit('bundler::ready');
+ });
+};
+
+util.inherits(Bundler, EventEmitter2);
+
+// The 'bundle' method is what actually attempts to bundle your project.
+Bundler.prototype.bundle = function (src, cb) {
+ var bundler = this,
+ modules = detective(src);
+
+ // We used 'detective' to get a list of needed modules, so that we can make
+ // sure they're installed before trying to browserify.
+ npm.commands.install(modules, function (err) {
+ if (err) {
+ cb(err);
+ }
+
+ var bundle;
+
+ // Attempt to browserify the passed-in javascript source
+ try {
+ bundle = browserify(this.options)
+ .addEntry('index.js', { body: src || '' })
+ .bundle();
+
+ // Hit the callback with our complete bundle object.
+ cb(null, {
+ src: src,
+ md5: crypto.createHash('md5').update(src).digest('base64'),
+ bundle: bundle
+ });
+ }
+ catch (err) {
+ cb(err);
+ }
+ });
+};
+
+// Bundler can be used as a broadway plugin.
+Bundler.attach = function (opts) {
+ this.bundler = new Bundler(opts);
+}
+
+// The major win we get from making Bundler attachable is that we can integrate
+// its initialization step with our app.
+Bundler.init = function (done) {
+ var npm = 'npm'.red.inverse;
+
+ if (this.bundler.ready) {
+ console.log('init: %s is ready.', npm);
+ return done();
+ } else {
+ console.log('init: Waiting for %s.', npm);
+ this.bundler.once('bundler::ready', function () {
+ console.log('init: %s is ready.', npm);
+ return done();
+ });
+ }
+}
View
@@ -6,6 +6,11 @@
},
"dependencies": {
"flatiron": "0.1.16",
- "union": "0.3.0"
+ "union": "0.3.0",
+ "utile": "0.0.10",
+ "eventemitter2": "0.4.x",
+ "browserify": "1.10.6",
+ "detective": "0.1.x",
+ "npm": "1.1.x"
}
}

0 comments on commit 737917f

Please sign in to comment.