combohandler
Advanced tools
Comparing version 0.1.2 to 0.1.3
37
app.js
@@ -1,2 +0,35 @@ | ||
var server = require('./lib/server'); | ||
module.exports = server(require('./config')); | ||
#!/usr/bin/env node | ||
/** | ||
Cluster initialization file for the site. See the Cluster docs at | ||
<http://learnboost.github.com/cluster/> for details about Cluster. | ||
**/ | ||
var cluster = require('cluster'), | ||
server = require('./lib/server'); | ||
cluster(server(require('./config'))) | ||
.set('title', 'combohandler') | ||
.in('development') | ||
.set('workers', 1) | ||
.use(cluster.pidfiles()) | ||
.use(cluster.cli()) | ||
.use(cluster.logger('logs', 'debug')) | ||
.use(cluster.reload([ | ||
'config.js', | ||
'index.js', | ||
'lib', | ||
], { | ||
extensions: ['.js'], | ||
interval : 1000, | ||
signal : 'SIGQUIT' | ||
})) | ||
.use(cluster.debug()) | ||
.listen(8000) | ||
.in('production') | ||
.use(cluster.pidfiles()) | ||
.use(cluster.cli()) | ||
.use(cluster.logger('logs')) | ||
.listen(8000); |
Combo Handler History | ||
===================== | ||
0.1.3 (2011-10-31) | ||
------------------ | ||
* Use Cluster instead of Spark2. | ||
0.1.2 (2011-07-11) | ||
@@ -5,0 +11,0 @@ ------------------ |
119
index.js
@@ -1,118 +0,1 @@ | ||
var fs = require('fs'), | ||
path = require('path'), | ||
sys = require('sys'), | ||
// Default set of MIME types supported by the combo handler. Attempts to | ||
// combine one or more files with an extension not in this mapping (or not | ||
// in a custom mapping) will result in a 400 response. | ||
MIME_TYPES = exports.MIME_TYPES = { | ||
'.css' : 'text/css', | ||
'.js' : 'application/javascript', | ||
'.json': 'application/json', | ||
'.txt' : 'text/plain', | ||
'.xml' : 'application/xml' | ||
}; | ||
// -- Exported Methods --------------------------------------------------------- | ||
exports.combine = function (config) { | ||
var mimeTypes = config.mimeTypes || MIME_TYPES, | ||
// Intentionally using the sync method because this only runs when the | ||
// middleware is initialized, and we want it to throw if there's an error. | ||
rootPath = fs.realpathSync(config.rootPath); | ||
function getMimeType(filename) { | ||
return mimeTypes[path.extname(filename).toLowerCase()]; | ||
} | ||
return function (req, res, next) { | ||
var body = [], | ||
query = parseQuery(req.url), | ||
pending = query.length, | ||
type = pending && getMimeType(query[0]), | ||
lastModified; | ||
function finish() { | ||
if (lastModified) { | ||
res.header('Last-Modified', lastModified.toUTCString()); | ||
} | ||
res.header('Content-Type', (type || 'text/plain') + ';charset=utf-8'); | ||
res.body = body.join('\n'); | ||
next(); | ||
} | ||
if (!pending) { | ||
// No files requested. | ||
return next(new BadRequest); | ||
} | ||
query.forEach(function (relativePath, i) { | ||
// Skip empty parameters. | ||
if (!relativePath) { | ||
pending -= 1; | ||
return; | ||
} | ||
fs.realpath(path.normalize(path.join(rootPath, relativePath)), function (err, absolutePath) { | ||
// Bubble up an error if the file can't be found or if the request | ||
// attempts to traverse above the root path. | ||
if (err || !absolutePath || absolutePath.indexOf(rootPath) !== 0) { | ||
return next(new BadRequest); | ||
} | ||
fs.stat(absolutePath, function (err, stats) { | ||
if (err || !stats.isFile()) { return next(new BadRequest); } | ||
var mtime = new Date(stats.mtime); | ||
if (!lastModified || mtime > lastModified) { | ||
lastModified = mtime; | ||
} | ||
fs.readFile(absolutePath, 'utf8', function (err, data) { | ||
if (err) { return next(new BadRequest); } | ||
body[i] = data; | ||
pending -= 1; | ||
if (pending === 0) { | ||
finish(); | ||
} | ||
}); // fs.readFile | ||
}); // fs.stat | ||
}); // fs.realpath | ||
}); // forEach | ||
}; | ||
}; | ||
// BadRequest is used for all filesystem-related errors, including when a | ||
// requested file can't be found (a NotFound error wouldn't be appropriate in | ||
// that case since the route itself exists; it's the request that's at fault). | ||
function BadRequest(message) { | ||
this.name = 'BadRequest'; | ||
Error.call(this, message || 'Bad request.'); | ||
Error.captureStackTrace(this, arguments.callee); | ||
} | ||
sys.inherits(BadRequest, Error); | ||
exports.BadRequest = BadRequest; // exported to allow instanceof checks | ||
// -- Private Methods ---------------------------------------------------------- | ||
function decode(string) { | ||
return decodeURIComponent(string).replace(/\+/g, ' '); | ||
} | ||
// Because querystring.parse() is silly and tries to be too clever. | ||
function parseQuery(url) { | ||
var parsed = [], | ||
query = url.split('?')[1]; | ||
if (query) { | ||
query.split('&').forEach(function (item) { | ||
parsed.push(decode(item.split('=')[0])); | ||
}); | ||
} | ||
return parsed; | ||
} | ||
module.exports = require('./lib/combohandler'); |
var connect = require('connect'), | ||
express = require('express'), | ||
combo = require('../index'); | ||
combo = require('./combohandler'); | ||
@@ -5,0 +5,0 @@ module.exports = function (config, baseApp) { |
{ | ||
"name" : "combohandler", | ||
"description": "Simple Yahoo!-style combo handler.", | ||
"version" : "0.1.2", | ||
"keywords" : ["combo", "combine", "cdn", "yui", "yui3", "js", "css", "performance"], | ||
"version" : "0.1.3", | ||
"keywords" : [ | ||
"combo", "combohandler", "combohandle", "combine", "cdn", "yui", "yui3", | ||
"yui 3", "js", "css", "performance" | ||
], | ||
"homepage" : "https://github.com/rgrove/combohandler", | ||
@@ -21,6 +24,7 @@ | ||
"node >=0.4.0", | ||
"npm >=0.3.1" | ||
"npm >=1.0.0" | ||
], | ||
"dependencies": { | ||
"cluster": "~0.7.7", | ||
"connect": ">=1.3.0 <2.0.0", | ||
@@ -35,2 +39,3 @@ "express": ">=2.2.0 <3.0.0" | ||
"files": [ | ||
"lib/combohandler.js", | ||
"lib/server.js", | ||
@@ -37,0 +42,0 @@ "HISTORY.md", |
163
README.md
@@ -8,3 +8,3 @@ Combo Handler | ||
The combo handler is compatible with the [YUI 3][] Loader, so you can use it to | ||
The combo handler is compatible with the [YUI][] Loader, so you can use it to | ||
host YUI, or you can use it with any other JavaScript or CSS if you're willing | ||
@@ -20,3 +20,3 @@ to construct the combo URLs yourself. | ||
[Nginx]: http://nginx.org/ | ||
[YUI 3]: http://developer.yahoo.com/yui/3/ | ||
[YUI]: http://yuilibrary.com/ | ||
@@ -53,10 +53,14 @@ | ||
var combo = require('combohandler'); | ||
app.use(combo.combine({rootPath: '/local/path/to/files'})); | ||
```js | ||
var combo = require('combohandler'); | ||
app.use(combo.combine({rootPath: '/local/path/to/files'})); | ||
``` | ||
Or as route middleware for a specific route: | ||
app.get('/foo', combo.combine({rootPath: '/local/path/to/foo'}), function (req, res) { | ||
res.send(res.body, 200); | ||
}); | ||
```js | ||
app.get('/foo', combo.combine({rootPath: '/local/path/to/foo'}), function (req, res) { | ||
res.send(res.body, 200); | ||
}); | ||
``` | ||
@@ -81,42 +85,43 @@ In either case, the middleware will perform combo handling for files under the | ||
var combo = require('combohandler'), | ||
express = require('express'), | ||
```js | ||
var combo = require('combohandler'), | ||
express = require('express'), | ||
app = express.createServer(); | ||
app = express.createServer(); | ||
app.configure(function () { | ||
app.use(express.conditionalGet()); | ||
app.use(express.errorHandler()); | ||
}); | ||
app.configure(function () { | ||
app.use(express.conditionalGet()); | ||
app.use(express.errorHandler()); | ||
}); | ||
// Return a 400 response if the combo handler generates a BadRequest error. | ||
app.error(function (err, req, res, next) { | ||
if (err instanceof combo.BadRequest) { | ||
res.send('Bad request.', {'Content-Type': 'text/plain'}, 400); | ||
} else { | ||
next(); | ||
} | ||
}); | ||
// Return a 400 response if the combo handler generates a BadRequest error. | ||
app.error(function (err, req, res, next) { | ||
if (err instanceof combo.BadRequest) { | ||
res.send('Bad request.', {'Content-Type': 'text/plain'}, 400); | ||
} else { | ||
next(); | ||
} | ||
}); | ||
// Given a root path that points to a YUI 2 root folder, this route will | ||
// handle URLs like: | ||
// | ||
// http://example.com/yui2?build/yahoo/yahoo-min.js&build/yuiloader/yuiloader-min.js | ||
// | ||
app.get('/yui2', combo.combine({rootPath: '/local/path/to/yui2'}), function (req, res) { | ||
res.send(res.body, 200); | ||
}); | ||
// Given a root path that points to a YUI 2 root folder, this route will | ||
// handle URLs like: | ||
// | ||
// http://example.com/yui2?build/yahoo/yahoo-min.js&build/yuiloader/yuiloader-min.js | ||
// | ||
app.get('/yui2', combo.combine({rootPath: '/local/path/to/yui2'}), function (req, res) { | ||
res.send(res.body, 200); | ||
}); | ||
// Given a root path that points to a YUI 3 root folder, this route will | ||
// handle URLs like: | ||
// | ||
// http://example.com/yui3?build/yui/yui-min.js&build/loader/loader-min.js | ||
// | ||
app.get('/yui3', combo.combine({rootPath: '/local/path/to/yui3'}), function (req, res) { | ||
res.send(res.body, 200); | ||
}); | ||
// Given a root path that points to a YUI 3 root folder, this route will | ||
// handle URLs like: | ||
// | ||
// http://example.com/yui3?build/yui/yui-min.js&build/loader/loader-min.js | ||
// | ||
app.get('/yui3', combo.combine({rootPath: '/local/path/to/yui3'}), function (req, res) { | ||
res.send(res.body, 200); | ||
}); | ||
app.listen(3000); | ||
app.listen(3000); | ||
``` | ||
### Creating a server | ||
@@ -129,15 +134,16 @@ | ||
var comboServer = require('combohandler/lib/server'), | ||
app; | ||
```js | ||
var comboServer = require('combohandler/lib/server'), | ||
app; | ||
app = comboServer({ | ||
roots: { | ||
'/yui2': '/local/path/to/yui2', | ||
'/yui3': '/local/path/to/yui3' | ||
} | ||
}); | ||
app = comboServer({ | ||
roots: { | ||
'/yui2': '/local/path/to/yui2', | ||
'/yui3': '/local/path/to/yui3' | ||
} | ||
}); | ||
app.listen(3000); | ||
app.listen(3000); | ||
``` | ||
### Augmenting an existing server | ||
@@ -149,31 +155,34 @@ | ||
var comboServer = require('combohandler/lib/server'); | ||
```js | ||
var comboServer = require('combohandler/lib/server'); | ||
comboServer({ | ||
roots: { | ||
'/yui2': '/local/path/to/yui2', | ||
'/yui3': '/local/path/to/yui3' | ||
} | ||
}, myApp); // Assuming `myApp` is a pre-existing Express server instance. | ||
comboServer({ | ||
roots: { | ||
'/yui2': '/local/path/to/yui2', | ||
'/yui3': '/local/path/to/yui3' | ||
} | ||
}, myApp); // Assuming `myApp` is a pre-existing Express server instance. | ||
``` | ||
### Running the included standalone server | ||
If you clone (or download) the GitHub repo, you can rename `config.sample.js` to | ||
`config.js`, edit it to your liking, and then simply use [Spark][] or [Spark2][] | ||
to run `app.js` as a standalone server. | ||
If you clone or download the GitHub repo, you can rename `config.sample.js` to | ||
`config.js`, edit it to your liking, and then simply run `app.js` to start a | ||
standalone server in development mode. | ||
npm install -g spark2 | ||
git clone git://github.com/rgrove/combohandler.git | ||
cd combohandler | ||
spark2 -v | ||
mv config.sample.js config.js | ||
./app.js | ||
[Spark]: https://github.com/senchalabs/spark | ||
[Spark2]: https://github.com/davglass/spark2 | ||
To run the standalone server in production mode, set the `NODE_ENV` variable to | ||
`production` before running it: | ||
NODE_ENV=production ./app.js | ||
Using as a YUI 3 combo handler | ||
------------------------------ | ||
With a tiny bit of configuration, you can tell YUI 3 to use your custom combo | ||
With a tiny bit of configuration, you can tell YUI to use your custom combo | ||
handler instead of the Yahoo! combo handler. Here's a working example that uses | ||
@@ -183,12 +192,14 @@ a live combo handler instance running on fuji.jetpants.com to serve the latest | ||
<script src="http://fuji.jetpants.com/yui/combo/yui3?build/yui/yui-min.js"></script> | ||
<script> | ||
var Y = YUI({ | ||
comboBase: 'http://fuji.jetpants.com/yui/combo/yui3?', | ||
combine : true, | ||
root : 'build/' | ||
}).use('node', function (Y) { | ||
// YUI will now automatically load modules from the custom combo handler. | ||
}); | ||
</script> | ||
```js | ||
<script src="http://fuji.jetpants.com/yui/combo/yui3?build/yui/yui-min.js"></script> | ||
<script> | ||
var Y = YUI({ | ||
comboBase: 'http://fuji.jetpants.com/yui/combo/yui3?', | ||
combine : true, | ||
root : 'build/' | ||
}).use('node', function (Y) { | ||
// YUI will now automatically load modules from the custom combo handler. | ||
}); | ||
</script> | ||
``` | ||
@@ -195,0 +206,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Non-existent author
Supply chain riskThe package was published by an npm account that no longer exists.
Found 1 instance in 1 package
15307
10
177
221
0
3
2
+ Addedcluster@~0.7.7
+ Addedcluster@0.7.7(transitive)
+ Addedd@1.0.2(transitive)
+ Addedduration@0.2.2(transitive)
+ Addedes5-ext@0.10.64(transitive)
+ Addedes6-iterator@2.0.3(transitive)
+ Addedes6-symbol@3.1.4(transitive)
+ Addedesniff@2.0.1(transitive)
+ Addedevent-emitter@0.3.5(transitive)
+ Addedext@1.7.0(transitive)
+ Addedlog@6.3.2(transitive)
+ Addedmkdirp@3.0.1(transitive)
+ Addednext-tick@1.1.0(transitive)
+ Addedsprintf-kit@2.0.2(transitive)
+ Addedtype@2.7.3(transitive)
+ Addeduni-global@1.0.0(transitive)