combohandler
Advanced tools
Comparing version 0.2.1 to 0.2.2
@@ -10,3 +10,13 @@ module.exports = { | ||
// | ||
// The :version placeholder is used to version the combo-handled requests by | ||
// "global" directory name (usually a sha1 hash), instead of per-file. | ||
// | ||
// The URL and local path must both contain the placeholder ":version" | ||
// | ||
// http://example.com/84b94bb/combo?build/mod-a/mod-a-min.js&build/mod-b/mod-b-min.js | ||
// resolves to: | ||
// /local/path/to/84b94bb/build/mod-a/mod-a-min.js | ||
// /local/path/to/84b94bb/build/mod-b/mod-b-min.js | ||
roots: { | ||
'/:version/combo': '/local/path/to/:version', | ||
'/yui3': '/local/path/to/yui3' | ||
@@ -13,0 +23,0 @@ }, |
Combo Handler History | ||
===================== | ||
0.2.2 (2013-04-21) | ||
------------------ | ||
* Changed default `combine` middleware to return an array of middleware instead | ||
of a function, allowing addition of bundled middlware depending on configured | ||
values. (Express flattens any arrays passed as route callbacks) | ||
* Changed default error handler to pass the error to `next()` when it isn't a | ||
`BadRequest`, which was itself extracted to a separate file. | ||
* Changed CSS `url()` rewriter to use built-in `path` methods instead of custom | ||
algorithm, with expanded test coverage. | ||
* Added `bodyContents` and `relativePaths` arrays to `res.locals` object in the | ||
default `combine` middleware, allowing subsequent middleware access to those | ||
values. | ||
* Added `cssUrls` middleware, an extraction of the CSS `url()` rewriter, with | ||
the optional capability of rewriting `@import` statements as well. | ||
* Added `dynamicPath` middleware, supporting route params (e.g., `:version`) | ||
that point to different trees under the same filesystem root. | ||
* Added `respond` middleware for convenience. It simply responds 200 with the | ||
contents of the `res.body` property. | ||
* Added code coverage with `istanbul`. | ||
* Added Travis support. | ||
* Added [Daniel Stockman](https://github.com/evocateur) as a maintainer. | ||
* Updated mocha dependency to 1.9.0. | ||
* Updated express dependency to 3.2.x. [Eric Ferraiuolo] | ||
0.2.1 (2013-04-01) | ||
@@ -5,0 +41,0 @@ ------------------ |
var fs = require('fs'), | ||
path = require('path'), | ||
util = require('util'), | ||
URI = require("URIjs"), | ||
// exported to allow instanceof checks | ||
BadRequest = exports.BadRequest = require('./error/bad-request'), | ||
// Default set of MIME types supported by the combo handler. Attempts to | ||
@@ -19,10 +20,13 @@ // combine one or more files with an extension not in this mapping (or not | ||
exports.combine = function (config) { | ||
var maxAge = config.maxAge, | ||
config = config || {}; | ||
var callbacks = [], | ||
maxAge = config.maxAge, | ||
mimeTypes = config.mimeTypes || MIME_TYPES, | ||
basePath = config.basePath || "", | ||
rootPathResolved; | ||
// 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); | ||
// Express flattens any arrays passed as route callbacks. | ||
// By always returning an array, we can add middleware based on config. | ||
callbacks.push(combineMiddleware); | ||
@@ -32,8 +36,17 @@ if (typeof maxAge === 'undefined') { | ||
} | ||
if ((/[^\/]$/).test(basePath)) { | ||
basePath += "/"; | ||
if (basePath) { | ||
callbacks.push(exports.middleware.cssUrls(config)); | ||
} | ||
return function (req, res, next) { | ||
if (config.rootPath && /:\w+/.test(config.rootPath)) { | ||
callbacks.unshift(exports.middleware.dynamicPath(config)); | ||
} else { | ||
// 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. | ||
rootPathResolved = fs.realpathSync(config.rootPath || ''); | ||
} | ||
function combineMiddleware(req, res, next) { | ||
var body = [], | ||
@@ -45,3 +58,3 @@ query = parseQuery(req.url), | ||
type = fileTypes.length === 1 && mimeTypes[fileTypes[0]], | ||
rewrite = basePath && ("text/css" === type), | ||
rootPath = res.locals.rootPath || rootPathResolved, | ||
lastModified; | ||
@@ -60,3 +73,13 @@ | ||
res.header('Content-Type', (type || 'text/plain') + ';charset=utf-8'); | ||
// charset must be specified before contentType | ||
// https://github.com/visionmedia/express/issues/1261 | ||
res.charset = 'utf-8'; | ||
res.contentType(type); | ||
// provide metadata to subsequent middleware via res.locals | ||
res.locals({ | ||
bodyContents: body, | ||
relativePaths: query | ||
}); | ||
res.body = body.join('\n'); | ||
@@ -117,3 +140,3 @@ | ||
body[i] = rewrite ? rewriteCSSURLs(basePath, relativePath, data) : data; | ||
body[i] = data; | ||
pending -= 1; | ||
@@ -127,16 +150,11 @@ | ||
}); // forEach | ||
}; | ||
} | ||
return callbacks; | ||
}; | ||
// 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) { | ||
Error.call(this); | ||
this.name = 'BadRequest'; | ||
this.message = message; | ||
Error.captureStackTrace(this, arguments.callee); | ||
} | ||
util.inherits(BadRequest, Error); | ||
exports.BadRequest = BadRequest; // exported to allow instanceof checks | ||
// By convention, this is the last middleware passed to any combo route | ||
exports.respond = function respondMiddleware(req, res) { | ||
res.send(res.body); | ||
}; | ||
@@ -196,27 +214,11 @@ // -- Private Methods ---------------------------------------------------------- | ||
function rewriteCSSURLs(base, path, data) { | ||
return data.replace(/[\s:]url\(\s*(['"]?)(\S+)\1\s*\)/g, function (substr, quote, match) { | ||
var root, | ||
dest; | ||
// There is a ton of complexity related to URL parsing related | ||
// to unicode, escapement, etc. Rather than try to capture that, | ||
// this just does a simple sniff to validate whether the URL | ||
// needs to be rewritten. | ||
if (!URI(match).is("relative")) { | ||
return substr; | ||
} | ||
// the current directory | ||
root = (base + path).split("/"), | ||
// the place to which we're traversing | ||
dest = match.split("/"); | ||
// pull off the file name | ||
root.pop(); | ||
// while the destination contains "../", knock that off and | ||
// knock off the last directory of root | ||
while (dest.length > 1 && ".." === dest[0]) { | ||
root.pop(); | ||
dest.shift(); | ||
} | ||
return substr.replace(match, root.concat(dest).join("/")); | ||
}); | ||
} | ||
// Auto-load bundled middleware with getters, Connect-style | ||
exports.middleware = {}; | ||
fs.readdirSync(__dirname + '/middleware').forEach(function (filename) { | ||
if (!/\.js$/.test(filename)) { return; } | ||
var name = path.basename(filename, '.js'); | ||
function load() { return require('./middleware/' + name); } | ||
exports.middleware.__defineGetter__(name, load); | ||
exports.__defineGetter__(name, load); | ||
}); |
@@ -37,3 +37,3 @@ var combo = require('./combohandler'), | ||
} else { | ||
next(); | ||
next(err); | ||
} | ||
@@ -44,5 +44,3 @@ }); | ||
for (route in roots) { | ||
app.get(route, combo.combine({rootPath: roots[route]}), function (req, res) { | ||
res.send(res.body); | ||
}); | ||
app.get(route, combo.combine({rootPath: roots[route]}), combo.respond); | ||
} | ||
@@ -49,0 +47,0 @@ |
{ | ||
"name" : "combohandler", | ||
"description": "Simple Yahoo!-style combo handler.", | ||
"version" : "0.2.1", | ||
"version" : "0.2.2", | ||
"keywords" : [ | ||
@@ -27,8 +27,9 @@ "combo", "combohandler", "combohandle", "combine", "cdn", "css", "yui" | ||
"dependencies": { | ||
"express": "3.1.1", | ||
"URIjs" : "1.10.0" | ||
"express": "3.2.x", | ||
"URIjs" : "1.10.1" | ||
}, | ||
"devDependencies": { | ||
"mocha" : "1.6.0", | ||
"istanbul": "~0.1.34", | ||
"mocha" : "1.9.0", | ||
"request": "~2.9", | ||
@@ -43,4 +44,4 @@ "should" : "1.2.0" | ||
"scripts": { | ||
"test": "./node_modules/.bin/mocha" | ||
"test": "istanbul test --print both ./node_modules/mocha/bin/_mocha" | ||
} | ||
} |
Combo Handler | ||
============= | ||
[![Build Status](https://travis-ci.org/rgrove/combohandler.png?branch=master)](https://travis-ci.org/rgrove/combohandler) | ||
This is a simple combo handler for Node.js, usable either as [Connect][] | ||
@@ -59,5 +61,3 @@ middleware or as an [Express][] server. It works just like the combo handler | ||
```js | ||
app.get('/foo', combo.combine({rootPath: '/local/path/to/foo'}), function (req, res) { | ||
res.send(res.body); | ||
}); | ||
app.get('/foo', combo.combine({rootPath: '/local/path/to/foo'}), combo.respond); | ||
``` | ||
@@ -98,5 +98,5 @@ | ||
res.type('text/plain'); | ||
res.send(400, 'Bad request.'); | ||
res.send(400, 'Bad request. ' + err.message); | ||
} else { | ||
next(); | ||
next(err); | ||
} | ||
@@ -110,5 +110,3 @@ }); | ||
// | ||
app.get('/yui3', combo.combine({rootPath: '/local/path/to/yui3'}), function (req, res) { | ||
res.send(res.body); | ||
}); | ||
app.get('/yui3', combo.combine({rootPath: '/local/path/to/yui3'}), combo.respond); | ||
@@ -118,2 +116,14 @@ app.listen(3000); | ||
#### `combo.respond` | ||
The `respond` method exported by `require('combohandler')` is a convenience method intended to be the last callback passed to an [express route](http://expressjs.com/api.html#app.VERB). Unless you have a *very* good reason to avoid it, you should probably use it. Here is the equivalent callback: | ||
```js | ||
function respond(req, res) { | ||
res.send(res.body); | ||
} | ||
``` | ||
This method may be extended in the future to do fancy things with optional combohandler middleware. | ||
### Creating a server | ||
@@ -189,10 +199,65 @@ | ||
// | ||
app.get('/combo', combohandler.combine({ | ||
app.get('/combo', combo.combine({ | ||
rootPath: __dirname + '/public', | ||
basePath: '/public' | ||
}), function (req, res) { | ||
res.send(res.body); | ||
}); | ||
}), combo.respond); | ||
``` | ||
Alternatively, you can use the built-in `cssUrls` middleware as a separate | ||
route callback. `cssUrls` must always be placed after the default `combine` | ||
middleware when used in this fashion. | ||
```js | ||
// This route provides the same behaviour as the previous example, providing | ||
// better separation of concerns and the possibility of inserting custom | ||
// middleware between the built-in steps. | ||
app.get('/combo', | ||
combo.combine({ | ||
rootPath: __dirname + '/public' | ||
}), | ||
combo.cssUrls({ | ||
basePath: '/public' | ||
}), | ||
combo.respond); | ||
``` | ||
Finally, the `cssUrls` middleware has the ability (disabled by default) to | ||
rewrite `@import` paths in the same manner as `url()` values. As `@import` is | ||
considered an anti-pattern in production code, this functionality is strictly | ||
opt-in and requires passing `true` as the `rewriteImports` property in the | ||
middleware options object. | ||
```js | ||
// Automagically | ||
app.get('/combo', combo.combine({ | ||
rootPath: __dirname + '/public', | ||
basePath: '/public', | ||
rewriteImports: true | ||
}), combo.respond); | ||
// As explicit middleware | ||
app.get('/combo', | ||
combo.combine({ rootPath: __dirname + '/public' }), | ||
combo.cssUrls({ basePath: '/public', rewriteImports: true }), | ||
combo.respond); | ||
``` | ||
### Dynamic Paths via Route Parameters | ||
To enable resolution of dynamic subtree paths under a given `rootPath`, simply add a [route parameter](http://expressjs.com/api.html#req.params) to both the route and the `rootPath` config. | ||
```js | ||
app.get('/combo/yui/:version', combo.combine({ | ||
rootPath: '/local/path/to/yui/:version/build' | ||
}), combo.respond); | ||
``` | ||
Given this config, any [YUI release tarball](http://yuilibrary.com/download/yui3/) you explode into a versioned subdirectory of `/local/path/to/yui/` would be available under a much shorter URL than the default config provides: | ||
http://example.com/combo/yui/3.9.1?yui/yui-min.js&yui-throttle/yui-throttle-min.js | ||
// vs | ||
http://example.com/combo/yui?3.9.1/build/yui/yui-min.js&3.9.1/build/yui-throttle/yui-throttle-min.js | ||
If the built-in `dynamicPath` middleware is used manually, it _must_ be inserted *before* the default `combine` middleware. | ||
Using as a YUI 3 combo handler | ||
@@ -199,0 +264,0 @@ ------------------------------ |
@@ -0,1 +1,2 @@ | ||
/*global describe, before, after, it */ | ||
var combo = require('../'), | ||
@@ -5,3 +6,2 @@ server = require('../lib/server'), | ||
assert = require('assert'), | ||
express = require('express'), | ||
request = require('request'), | ||
@@ -21,18 +21,5 @@ | ||
'/css': __dirname + '/fixtures/root/css', | ||
'/js' : __dirname + '/fixtures/root/js', | ||
'/norewrite': __dirname + '/fixtures/rewrite' | ||
'/js' : __dirname + '/fixtures/root/js' | ||
} | ||
}); | ||
app.get('/rewrite', combo.combine({ | ||
rootPath: __dirname + '/fixtures/rewrite', | ||
basePath: "/rewritten" | ||
}), function (req, res) { | ||
res.send(res.body); | ||
}); | ||
app.get('/rewrite-noslash', combo.combine({ | ||
rootPath: __dirname + '/fixtures/rewrite', | ||
basePath: "/rewritten/" | ||
}), function (req, res) { | ||
res.send(res.body); | ||
}); | ||
@@ -48,5 +35,5 @@ httpServer = app.listen(PORT); | ||
request(BASE_URL + '/js?a.js&b.js', function (err, res, body) { | ||
assert.equal(err, null); | ||
assert.ifError(err); | ||
res.should.have.status(200); | ||
res.should.have.header('content-type', 'application/javascript;charset=utf-8'); | ||
res.should.have.header('content-type', 'application/javascript; charset=utf-8'); | ||
res.should.have.header('last-modified'); | ||
@@ -60,5 +47,5 @@ body.should.equal('a();\n\nb();\n'); | ||
request(BASE_URL + '/css?a.css&b.css', function (err, res, body) { | ||
assert.equal(err, null); | ||
assert.ifError(err); | ||
res.should.have.status(200); | ||
res.should.have.header('content-type', 'text/css;charset=utf-8'); | ||
res.should.have.header('content-type', 'text/css; charset=utf-8'); | ||
res.should.have.header('last-modified'); | ||
@@ -72,5 +59,5 @@ body.should.equal('.a { color: green; }\n\n.b { color: green; }\n'); | ||
request(BASE_URL + '/js?a.js&b.js&outside.js', function (err, res, body) { | ||
assert.equal(err, null); | ||
assert.ifError(err); | ||
res.should.have.status(200); | ||
res.should.have.header('content-type', 'application/javascript;charset=utf-8'); | ||
res.should.have.header('content-type', 'application/javascript; charset=utf-8'); | ||
res.should.have.header('last-modified'); | ||
@@ -88,5 +75,3 @@ body.should.equal('a();\n\nb();\n\noutside();\n'); | ||
maxAge : null | ||
}), function (req, res) { | ||
res.send(res.body, 200); | ||
}); | ||
}), combo.respond); | ||
@@ -96,5 +81,3 @@ app.get('/max-age-0', combo.combine({ | ||
maxAge : 0 | ||
}), function (req, res) { | ||
res.send(res.body, 200); | ||
}); | ||
}), combo.respond); | ||
}); | ||
@@ -104,3 +87,3 @@ | ||
request(BASE_URL + '/js?a.js', function (err, res, body) { | ||
assert.equal(err, null); | ||
assert.ifError(err); | ||
res.should.have.status(200); | ||
@@ -118,3 +101,3 @@ res.should.have.header('cache-control', 'public,max-age=31536000'); | ||
request(BASE_URL + '/max-age-null?a.js', function (err, res, body) { | ||
assert.equal(err, null); | ||
assert.ifError(err); | ||
res.should.have.status(200); | ||
@@ -129,3 +112,3 @@ res.headers.should.not.have.property('cache-control'); | ||
request(BASE_URL + '/max-age-0?a.js', function (err, res, body) { | ||
assert.equal(err, null); | ||
assert.ifError(err); | ||
res.should.have.status(200); | ||
@@ -144,5 +127,44 @@ res.should.have.header('cache-control', 'public,max-age=0'); | ||
describe('errors', function () { | ||
before(function () { | ||
app.get('/error-next?', function (req, res, next) { | ||
var poo = new Error('poo'); | ||
poo.stack = null; // silence irrelevant output | ||
next(poo); | ||
}, combo.combine({ | ||
rootPath: __dirname + '/fixtures/root/js' | ||
}), combo.respond); | ||
app.get('/error-throw?', combo.combine({ | ||
rootPath: __dirname + '/fixtures/root/js' | ||
}), function (req, res, next) { | ||
throw 'poo'; | ||
}, combo.respond); | ||
}); | ||
it('should inherit from Error', function () { | ||
var err = new combo.BadRequest('test'); | ||
err.should.be.an.instanceOf(Error); | ||
err.name.should.equal('BadRequest'); | ||
err.message.should.equal('test'); | ||
}); | ||
it('should return a 500 when error before middleware', function (done) { | ||
request(BASE_URL + '/error-next?a.js', function (err, res, body) { | ||
assert.ifError(err); | ||
res.should.have.status(500); | ||
done(); | ||
}); | ||
}); | ||
it('should return a 500 when error after middleware', function (done) { | ||
request(BASE_URL + '/error-throw?a.js', function (err, res, body) { | ||
assert.ifError(err); | ||
res.should.have.status(500); | ||
done(); | ||
}); | ||
}); | ||
it('should return a 400 Bad Request error when no files are specified', function (done) { | ||
request(BASE_URL + '/js', function (err, res, body) { | ||
assert.equal(err, null); | ||
assert.ifError(err); | ||
res.should.have.status(400); | ||
@@ -156,3 +178,3 @@ body.should.equal('Bad request. No files requested.'); | ||
request(BASE_URL + '/js?bogus.js', function (err, res, body) { | ||
assert.equal(err, null); | ||
assert.ifError(err); | ||
res.should.have.status(400); | ||
@@ -167,3 +189,3 @@ res.should.have.header('content-type', 'text/plain; charset=utf-8'); | ||
request(BASE_URL + '/js?foo.bar', function (err, res, body) { | ||
assert.equal(err, null); | ||
assert.ifError(err); | ||
res.should.have.status(400); | ||
@@ -177,3 +199,3 @@ body.should.equal('Bad request. Illegal MIME type present.'); | ||
request(BASE_URL + '/js?a.js&foo.bar', function (err, res, body) { | ||
assert.equal(err, null); | ||
assert.ifError(err); | ||
res.should.have.status(400); | ||
@@ -187,3 +209,3 @@ body.should.equal('Bad request. Only one MIME type allowed per request.'); | ||
request(BASE_URL + '/js?a.js&b.css', function (err, res, body) { | ||
assert.equal(err, null); | ||
assert.ifError(err); | ||
res.should.have.status(400); | ||
@@ -197,3 +219,3 @@ body.should.equal('Bad request. Only one MIME type allowed per request.'); | ||
request(BASE_URL + '/js?a.js&b', function (err, res, body) { | ||
assert.equal(err, null); | ||
assert.ifError(err); | ||
res.should.have.status(400); | ||
@@ -207,3 +229,3 @@ body.should.equal('Bad request. Truncated query parameters.'); | ||
request(BASE_URL + '/js?a', function (err, res, body) { | ||
assert.equal(err, null); | ||
assert.ifError(err); | ||
res.should.have.status(400); | ||
@@ -234,3 +256,3 @@ body.should.equal('Bad request. Truncated query parameters.'); | ||
request(BASE_URL + '/js?' + path, function (err, res, body) { | ||
assert.equal(err, null); | ||
assert.ifError(err); | ||
res.should.have.status(400); | ||
@@ -248,5 +270,27 @@ res.should.have.header('content-type', 'text/plain; charset=utf-8'); | ||
describe("url rewrites", function () { | ||
it ("should allow the basePath to end in a slash", function (done) { | ||
before(function () { | ||
app.get('/norewrite', combo.combine({ | ||
rootPath: __dirname + '/fixtures/rewrite' | ||
}), combo.respond); | ||
app.get('/rewrite', combo.combine({ | ||
rootPath: __dirname + '/fixtures/rewrite', | ||
basePath: "/rewritten" | ||
}), combo.respond); | ||
app.get('/rewrite-noslash', combo.combine({ | ||
rootPath: __dirname + '/fixtures/rewrite', | ||
basePath: "/rewritten/" | ||
}), combo.respond); | ||
app.get('/rewrite-imports', combo.combine({ | ||
rootPath: __dirname + '/fixtures/rewrite', | ||
basePath: "/rewritten/", | ||
rewriteImports: true | ||
}), combo.respond); | ||
}); | ||
it("should allow the basePath to end in a slash", function (done) { | ||
request(BASE_URL + "/rewrite-noslash?urls.css", function (err, res, body) { | ||
assert.equal(err, null); | ||
assert.ifError(err); | ||
body.should.equal([ | ||
@@ -262,2 +306,8 @@ "#no-quotes { background: url(/rewritten/no-quotes.png);}", | ||
"#escaped-stuff { background:url(\"/rewritten/\\)\\\";\\'\\(.png\"); }", | ||
".unicode-raw { background: url(/rewritten/déchaîné.png); }", | ||
".unicode-escaped { background: url(/rewritten/d\\0000E9cha\\EEn\\E9.png); }", | ||
".nl-craziness { background:", | ||
" url(/rewritten/crazy.png", | ||
" ); }", | ||
"" | ||
].join("\n")); | ||
@@ -267,5 +317,6 @@ done(); | ||
}); | ||
it ("should not rewrite without a basePath", function (done) { | ||
it("should not rewrite without a basePath", function (done) { | ||
request(BASE_URL + "/norewrite?urls.css", function (err, res, body) { | ||
assert.equal(err, null); | ||
assert.ifError(err); | ||
body.should.equal([ | ||
@@ -280,3 +331,9 @@ "#no-quotes { background: url(no-quotes.png);}", | ||
"#protocol-relative-url { background: url(//www.example.com/foo.gif?a=b&c=d#bebimbop);}", | ||
"#escaped-stuff { background:url(\"\\)\\\";\\'\\(.png\"); }" | ||
"#escaped-stuff { background:url(\"\\)\\\";\\'\\(.png\"); }", | ||
".unicode-raw { background: url(déchaîné.png); }", | ||
".unicode-escaped { background: url(d\\0000E9cha\\EEn\\E9.png); }", | ||
".nl-craziness { background:", | ||
" url(crazy.png", | ||
" ); }", | ||
"" | ||
].join("\n")); | ||
@@ -286,5 +343,6 @@ done(); | ||
}); | ||
it ("should rewrite valid urls", function (done) { | ||
it("should rewrite valid urls", function (done) { | ||
request(BASE_URL + "/rewrite?urls.css&deeper/more.css", function (err, res, body) { | ||
assert.equal(err, null); | ||
assert.ifError(err); | ||
body.should.equal([ | ||
@@ -300,2 +358,10 @@ "#no-quotes { background: url(/rewritten/no-quotes.png);}", | ||
"#escaped-stuff { background:url(\"/rewritten/\\)\\\";\\'\\(.png\"); }", | ||
".unicode-raw { background: url(/rewritten/déchaîné.png); }", | ||
// NOTE: we do not currently support the space terminator for CSS escapes. | ||
// ".unicode-escaped { background: url(/rewritten/d\\E9 cha\\EEn\\E9.png); }", | ||
".unicode-escaped { background: url(/rewritten/d\\0000E9cha\\EEn\\E9.png); }", | ||
".nl-craziness { background:", | ||
" url(/rewritten/crazy.png", | ||
" ); }", | ||
"", | ||
"#depth { background: url(/rewritten/deeper/deeper.png);}", | ||
@@ -308,3 +374,231 @@ "#up-one { background: url(/rewritten/shallower.png);}", | ||
}); | ||
it("should rewrite import paths when enabled from combine", function (done) { | ||
request(BASE_URL + "/rewrite-imports?imports.css", function (err, res, body) { | ||
assert.ifError(err); | ||
body.should.equal([ | ||
"@import '/rewritten/basic-sq.css';", | ||
"@import \"/rewritten/basic-dq.css\";", | ||
"@import url(/rewritten/url-uq.css);", | ||
"@import url('/rewritten/url-sq.css');", | ||
"@import url(\"/rewritten/url-dq.css\");", | ||
"@import \"/rewritten/media-simple.css\" print;", | ||
"@import url(\"/rewritten/media-simple-url.css\") print;", | ||
"@import '/rewritten/media-simple-comma.css' print, screen;", | ||
"@import \"/rewritten/media-complex.css\" screen and (min-width: 400px) and (max-width: 700px);", | ||
"@import url(\"/rewritten/media-complex-url.css\") screen and (min-width: 400px) and (max-width: 700px);", | ||
// TODO: are the following rewritten correctly? | ||
"@import \"/rewrite/deeper/more.css\";", | ||
"@import \"/root/css/a.css\" (device-width: 320px);", | ||
"" | ||
].join("\n")); | ||
done(); | ||
}); | ||
}); | ||
describe("as middleware", function () { | ||
before(function () { | ||
app.get('/rewrite-middleware-ignore', | ||
combo.combine({ rootPath: __dirname + '/fixtures/root/js' }), | ||
combo.cssUrls({ basePath: "/rewritten/" }), | ||
combo.respond); | ||
app.get('/rewrite-middleware', | ||
combo.combine({ rootPath: __dirname + '/fixtures/rewrite' }), | ||
combo.cssUrls({ basePath: "/rewritten/" }), | ||
combo.respond); | ||
app.get('/rewrite-middleware-imports', | ||
combo.combine({ rootPath: __dirname + '/fixtures/rewrite' }), | ||
combo.cssUrls({ basePath: "/rewritten/", rewriteImports: true }), | ||
combo.respond); | ||
}); | ||
it("should avoid modifying non-CSS requests", function (done) { | ||
request(BASE_URL + '/rewrite-middleware-ignore?a.js&b.js', function (err, res, body) { | ||
assert.ifError(err); | ||
res.should.have.status(200); | ||
res.should.have.header('content-type', 'application/javascript; charset=utf-8'); | ||
res.should.have.header('last-modified'); | ||
body.should.equal('a();\n\nb();\n'); | ||
done(); | ||
}); | ||
}); | ||
it("should rewrite valid urls", function (done) { | ||
request(BASE_URL + "/rewrite-middleware?urls.css&deeper/more.css", function (err, res, body) { | ||
assert.ifError(err); | ||
body.should.equal([ | ||
"#no-quotes { background: url(/rewritten/no-quotes.png);}", | ||
"#single-quotes { background: url(\'/rewritten/single-quotes.png\');}", | ||
"#double-quotes { background: url(\"/rewritten/double-quotes.png\");}", | ||
"#spaces { background: url(", | ||
" \"/rewritten/spaces.png\" );}", | ||
"#data-url { background: url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==);}", | ||
"#absolute-url { background: url(http://www.example.com/foo.gif?a=b&c=d#bebimbop);}", | ||
"#protocol-relative-url { background: url(//www.example.com/foo.gif?a=b&c=d#bebimbop);}", | ||
"#escaped-stuff { background:url(\"/rewritten/\\)\\\";\\'\\(.png\"); }", | ||
".unicode-raw { background: url(/rewritten/déchaîné.png); }", | ||
".unicode-escaped { background: url(/rewritten/d\\0000E9cha\\EEn\\E9.png); }", | ||
".nl-craziness { background:", | ||
" url(/rewritten/crazy.png", | ||
" ); }", | ||
"", | ||
"#depth { background: url(/rewritten/deeper/deeper.png);}", | ||
"#up-one { background: url(/rewritten/shallower.png);}", | ||
"#down-one { background: url(/rewritten/deeper/more/down-one.png);}" | ||
].join("\n")); | ||
done(); | ||
}); | ||
}); | ||
it("should NOT rewrite import paths when disabled", function (done) { | ||
request(BASE_URL + "/rewrite-middleware?imports.css", function (err, res, body) { | ||
assert.ifError(err); | ||
body.should.equal([ | ||
"@import 'basic-sq.css';", | ||
"@import \"basic-dq.css\";", | ||
"@import url(url-uq.css);", | ||
"@import url('url-sq.css');", | ||
"@import url(\"url-dq.css\");", | ||
"@import \"media-simple.css\" print;", | ||
"@import url(\"media-simple-url.css\") print;", | ||
"@import 'media-simple-comma.css' print, screen;", | ||
"@import \"media-complex.css\" screen and (min-width: 400px) and (max-width: 700px);", | ||
"@import url(\"media-complex-url.css\") screen and (min-width: 400px) and (max-width: 700px);", | ||
"@import \"../rewrite/deeper/more.css\";", | ||
"@import \"../root/css/a.css\" (device-width: 320px);", | ||
"" | ||
].join("\n")); | ||
done(); | ||
}); | ||
}); | ||
it("should rewrite import paths when enabled", function (done) { | ||
request(BASE_URL + "/rewrite-middleware-imports?imports.css", function (err, res, body) { | ||
assert.ifError(err); | ||
body.should.equal([ | ||
"@import '/rewritten/basic-sq.css';", | ||
"@import \"/rewritten/basic-dq.css\";", | ||
"@import url(/rewritten/url-uq.css);", | ||
"@import url('/rewritten/url-sq.css');", | ||
"@import url(\"/rewritten/url-dq.css\");", | ||
"@import \"/rewritten/media-simple.css\" print;", | ||
"@import url(\"/rewritten/media-simple-url.css\") print;", | ||
"@import '/rewritten/media-simple-comma.css' print, screen;", | ||
"@import \"/rewritten/media-complex.css\" screen and (min-width: 400px) and (max-width: 700px);", | ||
"@import url(\"/rewritten/media-complex-url.css\") screen and (min-width: 400px) and (max-width: 700px);", | ||
// TODO: are the following rewritten correctly? | ||
"@import \"/rewrite/deeper/more.css\";", | ||
"@import \"/root/css/a.css\" (device-width: 320px);", | ||
"" | ||
].join("\n")); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
// Dynamic Paths ---------------------------------------------------------- | ||
describe("dynamic paths", function () { | ||
before(function () { | ||
app.get('/dynamic/:version', | ||
combo.combine({ rootPath: __dirname + '/fixtures/dynamic/:version' }), | ||
combo.respond); | ||
app.get('/:version/param-first', | ||
combo.combine({ rootPath: __dirname + '/fixtures/dynamic/:version' }), | ||
combo.respond); | ||
app.get('/dynamic/:version/empty-combo', | ||
combo.dynamicPath({ rootPath: __dirname + '/fixtures/dynamic/:version/static' }), | ||
combo.combine(), | ||
combo.respond); | ||
app.get('/dynamic/:version/doubled', | ||
combo.dynamicPath({ rootPath: __dirname + '/fixtures/dynamic/:version/static' }), | ||
combo.combine({ rootPath: __dirname + '/fixtures/dynamic/:version/static' }), | ||
combo.respond); | ||
app.get('/non-dynamic', | ||
combo.dynamicPath({ rootPath: __dirname + '/fixtures/dynamic/decafbad' }), | ||
combo.combine({ rootPath: __dirname + '/fixtures/dynamic/decafbad' }), | ||
combo.respond); | ||
}); | ||
it("should work when param found at end of path", function (done) { | ||
request(BASE_URL + '/dynamic/decafbad?a.js&b.js', function (err, res, body) { | ||
assert.ifError(err); | ||
res.should.have.status(200); | ||
res.should.have.header('content-type', 'application/javascript; charset=utf-8'); | ||
res.should.have.header('last-modified'); | ||
body.should.equal('a();\n\nb();\n'); | ||
done(); | ||
}); | ||
}); | ||
it("should work when param found at beginning of path", function (done) { | ||
request(BASE_URL + '/decafbad/param-first?a.js&static/c.js', function (err, res, body) { | ||
assert.ifError(err); | ||
res.should.have.status(200); | ||
res.should.have.header('content-type', 'application/javascript; charset=utf-8'); | ||
res.should.have.header('last-modified'); | ||
body.should.equal('a();\n\nc();\n'); | ||
done(); | ||
}); | ||
}); | ||
it("should work when rootPath not passed to combine()", function (done) { | ||
request(BASE_URL + '/dynamic/decafbad/empty-combo?c.js&d.js', function (err, res, body) { | ||
assert.ifError(err); | ||
res.should.have.status(200); | ||
res.should.have.header('content-type', 'application/javascript; charset=utf-8'); | ||
res.should.have.header('last-modified'); | ||
body.should.equal('c();\n\nd();\n'); | ||
done(); | ||
}); | ||
}); | ||
it("should work when param found before end of path", function (done) { | ||
request(BASE_URL + '/dynamic/decafbad/empty-combo?c.js&d.js', function (err, res, body) { | ||
assert.ifError(err); | ||
res.should.have.status(200); | ||
res.should.have.header('content-type', 'application/javascript; charset=utf-8'); | ||
res.should.have.header('last-modified'); | ||
body.should.equal('c();\n\nd();\n'); | ||
done(); | ||
}); | ||
}); | ||
it("should work when middleware is run twice on same route", function (done) { | ||
request(BASE_URL + '/dynamic/decafbad/doubled?c.js&d.js', function (err, res, body) { | ||
assert.ifError(err); | ||
res.should.have.status(200); | ||
res.should.have.header('content-type', 'application/javascript; charset=utf-8'); | ||
res.should.have.header('last-modified'); | ||
body.should.equal('c();\n\nd();\n'); | ||
done(); | ||
}); | ||
}); | ||
it("should not fail when param not present", function (done) { | ||
request(BASE_URL + '/non-dynamic?a.js&b.js', function (err, res, body) { | ||
assert.ifError(err); | ||
res.should.have.status(200); | ||
res.should.have.header('content-type', 'application/javascript; charset=utf-8'); | ||
res.should.have.header('last-modified'); | ||
body.should.equal('a();\n\nb();\n'); | ||
done(); | ||
}); | ||
}); | ||
it("should error when param does not correspond to existing path", function (done) { | ||
request(BASE_URL + '/dynamic/deadbeef?a.js', function (err, res, body) { | ||
assert.ifError(err); | ||
res.should.have.status(400); | ||
body.should.equal('Bad request. Unable to resolve path: /dynamic/deadbeef'); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
}); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
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
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
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
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
59792
29
973
299
4
4
1
+ AddedURIjs@1.10.1(transitive)
+ Addedbuffer-crc32@0.2.1(transitive)
+ Addedconnect@2.7.11(transitive)
+ Addedcookie@0.1.0(transitive)
+ Addedcookie-signature@1.0.1(transitive)
+ Addedexpress@3.2.6(transitive)
+ Addedformidable@1.0.14(transitive)
+ Addedmime@1.2.11(transitive)
+ Addedmkdirp@0.3.4(transitive)
+ Addedqs@0.6.5(transitive)
+ Addedsend@0.1.1(transitive)
- RemovedURIjs@1.10.0(transitive)
- Removedbuffer-crc32@0.1.10.2.13(transitive)
- Removedconnect@2.7.4(transitive)
- Removedcookie-signature@0.0.1(transitive)
- Removedexpress@3.1.1(transitive)
- Removedformidable@1.0.11(transitive)
- Removedmkdirp@0.3.5(transitive)
- Removedqs@0.5.1(transitive)
UpdatedURIjs@1.10.1
Updatedexpress@3.2.x