combohandler
Advanced tools
Comparing version 0.3.1 to 0.3.2
Combo Handler History | ||
===================== | ||
0.3.2 (2013-04-26) | ||
------------------ | ||
* Added --webRoot option to support CSS url() rewriting across multiple | ||
dynamic paths. This option will supplant --basePath if both are present. | ||
* Changed dynamicPath resolution to use fs.stat() instead of fs.realpath(), | ||
allowing symlinks under a well-known rootPath to build an "absolutely | ||
relative" pathname instead of always resolving to the rootPath. | ||
* Reorganized tests to cut down on repetitive string concatenation and | ||
clarify intention. Also added a bunch of complex symlink tests. | ||
0.3.1 (2013-04-24) | ||
@@ -5,0 +18,0 @@ ------------------ |
@@ -24,2 +24,3 @@ /** | ||
"timeout": Number, | ||
"webRoot": path, | ||
"workers": Number | ||
@@ -39,2 +40,3 @@ }; | ||
"t": ["--timeout"], | ||
"w": ["--webRoot"], | ||
"n": ["--workers"] | ||
@@ -60,3 +62,6 @@ }; | ||
msg.push(" -f, --rootsFile Path to JSON routes config, *exclusive* of --root."); | ||
msg.push(" -b, --basePath Path to prepend when rewriting relative url()s. ['']"); | ||
msg.push(" -b, --basePath URL path to prepend when rewriting relative url()s. ['']"); | ||
msg.push(" -w, --webRoot Filesystem path to base rewritten relative url()s from. ['']"); | ||
msg.push(" Use this instead of --basePath when using route parameters."); | ||
msg.push(" Overrides behaviour of --basePath."); | ||
msg.push(" -m, --maxAge 'Cache-Control' and 'Expires' value, in seconds. [31536000]"); | ||
@@ -63,0 +68,0 @@ msg.push(" Set this to `0` to expire immediately, `null` to omit these"); |
@@ -25,3 +25,2 @@ var fs = require('fs'), | ||
mimeTypes = config.mimeTypes || MIME_TYPES, | ||
basePath = config.basePath || "", | ||
rootPathResolved; | ||
@@ -37,3 +36,3 @@ | ||
if (basePath) { | ||
if (config.basePath || config.webRoot) { | ||
callbacks.push(exports.middleware.cssUrls(config)); | ||
@@ -49,2 +48,5 @@ } | ||
rootPathResolved = fs.realpathSync(config.rootPath || ''); | ||
// rootPathResolved should always have a trailing slash | ||
rootPathResolved = path.normalize(rootPathResolved + path.sep); | ||
} | ||
@@ -80,2 +82,3 @@ | ||
res.locals({ | ||
rootPath: rootPath, // ensures this always has a value | ||
bodyContents: body, | ||
@@ -82,0 +85,0 @@ relativePaths: query |
@@ -47,22 +47,32 @@ /** | ||
// opt-in only. | ||
var importsEnabled = (true === options.rewriteImports), | ||
basePath = options.basePath; | ||
var importsEnabled = (true === options.rewriteImports); | ||
var basePath = options.basePath; | ||
var webRoot = options.webRoot; | ||
var enabled = !!(basePath || webRoot); | ||
return function cssUrlsMiddleware(req, res, next) { | ||
var bodyContents, | ||
relativePaths; | ||
var relativePaths = res.locals.relativePaths; | ||
var bodyContents = res.locals.bodyContents; | ||
if (basePath && isCSS(res)) { | ||
relativePaths = res.locals.relativePaths; | ||
bodyContents = res.locals.bodyContents; | ||
if (enabled && !!(relativePaths && bodyContents) && isCSS(res)) { | ||
var opts = { | ||
"basePath": basePath, | ||
"importsEnabled": importsEnabled | ||
}; | ||
if (bodyContents && relativePaths) { | ||
// synchronous replacement | ||
relativePaths.forEach(function (relativePath, i) { | ||
bodyContents[i] = exports.rewrite(basePath, relativePath, bodyContents[i], importsEnabled); | ||
}); | ||
if (webRoot) { | ||
// rebase rootPath to webRoot, creates "absolutely relative" pathname | ||
opts.basePath = path.normalize( | ||
path.resolve(webRoot, res.locals.rootPath) | ||
.replace(webRoot, path.sep) | ||
); | ||
} | ||
// overwrites response body with replaced content | ||
res.body = bodyContents.join('\n'); | ||
} | ||
// synchronous replacement | ||
relativePaths.forEach(function (relativePath, i) { | ||
bodyContents[i] = exports.rewrite(bodyContents[i], relativePath, opts); | ||
}); | ||
// overwrites response body with replaced content | ||
res.body = bodyContents.join('\n'); | ||
} | ||
@@ -74,3 +84,6 @@ | ||
exports.rewrite = function rewriteCSSURLs(basePath, relativePath, data, importsEnabled) { | ||
exports.rewrite = function rewriteCSSURLs(data, relativePath, opts) { | ||
var basePath = opts.basePath; | ||
var importsEnabled = opts.importsEnabled; | ||
var replaceCallback = replacer(basePath, relativePath); | ||
@@ -86,3 +99,4 @@ | ||
function isCSS(res) { | ||
return res.get('Content-Type').indexOf('text/css') > -1; | ||
var contentType = res.get('Content-Type'); | ||
return contentType && contentType.indexOf('text/css') > -1; | ||
} |
@@ -19,8 +19,17 @@ /** | ||
exports = module.exports = function (options) { | ||
options = options || {}; | ||
// don't explode when misconfigured, just pass through with a warning | ||
if (!options || !options.rootPath) { | ||
console.warn("dynamicPathMiddleware: config missing!"); | ||
return function disabledMiddleware(req, res, next) { | ||
next(); | ||
}; | ||
} | ||
var rootPath = options.rootPath, | ||
// private fs.stat cache | ||
var STAT_CACHE = {}; | ||
var rootPath = path.normalize(options.rootPath), | ||
dynamicKey = getDynamicKey(rootPath), | ||
dynamicParameter, | ||
rootSuffix = ''; | ||
dynamicParam, | ||
rootSuffix; | ||
@@ -30,3 +39,3 @@ // str.match() in route config returns null if no matches or [":foo"] | ||
// key for the req.params must be stripped of the colon | ||
dynamicParameter = dynamicKey.substr(1); | ||
dynamicParam = dynamicKey.substr(1); | ||
@@ -48,2 +57,5 @@ // if the parameter is not the last token in the rootPath | ||
// rootPath should always have a trailing slash | ||
rootPath = path.normalize(rootPath + path.sep); | ||
return function dynamicPathMiddleware(req, res, next) { | ||
@@ -53,7 +65,12 @@ // on the off chance this middleware runs twice | ||
// supply subsequent middleware with the res.locals.rootPath property | ||
getDynamicRoot(req.params, rootPath, dynamicParameter, rootSuffix, function (err, resolved) { | ||
getDynamicRoot(req.params, { | ||
"rootPath" : rootPath, | ||
"dynamicParam": dynamicParam, | ||
"rootSuffix" : rootSuffix, | ||
"statCache" : STAT_CACHE | ||
}, function (err, resolvedRootPath) { | ||
if (err) { | ||
next(new BadRequest('Unable to resolve path: ' + req.path)); | ||
} else { | ||
res.locals.rootPath = resolved; | ||
res.locals.rootPath = resolvedRootPath; | ||
next(); | ||
@@ -68,7 +85,10 @@ } | ||
// cache for "compiled" dynamic roots | ||
var DYNAMIC_ROOTS = exports.DYNAMIC_ROOTS = {}; | ||
function getDynamicRoot(params, opts, cb) { | ||
var rootPath = opts.rootPath; | ||
var dynamicParam = opts.dynamicParam; | ||
var rootSuffix = opts.rootSuffix; | ||
var statCache = opts.statCache; | ||
function getDynamicRoot(params, rootPath, dynamicParameter, rootSuffix, cb) { | ||
var dynamicValue = dynamicParameter && params[dynamicParameter]; | ||
var dynamicValue = dynamicParam && params[dynamicParam]; | ||
var dynamicRoot; | ||
@@ -82,9 +102,11 @@ // a dynamic parameter has been configured | ||
if (DYNAMIC_ROOTS[dynamicValue]) { | ||
dynamicRoot = path.normalize(path.join(rootPath, dynamicValue)); | ||
if (statCache[dynamicRoot]) { | ||
// a path has already been resolved | ||
cb(null, DYNAMIC_ROOTS[dynamicValue]); | ||
cb(null, dynamicRoot); | ||
} | ||
else { | ||
// a path needs resolving | ||
fs.realpath(path.normalize(path.join(rootPath, dynamicValue)), function (err, resolved) { | ||
fs.stat(dynamicRoot, function (err, stat) { | ||
if (err) { | ||
@@ -94,4 +116,4 @@ cb(err); | ||
// cache for later short-circuit | ||
DYNAMIC_ROOTS[dynamicValue] = resolved; | ||
cb(null, resolved); | ||
statCache[dynamicRoot] = stat; | ||
cb(null, dynamicRoot); | ||
} | ||
@@ -98,0 +120,0 @@ }); |
{ | ||
"name" : "combohandler", | ||
"description": "Simple Yahoo!-style combo handler.", | ||
"version" : "0.3.1", | ||
"version" : "0.3.2", | ||
"keywords" : [ | ||
@@ -6,0 +6,0 @@ "combo", "combohandler", "combohandle", "combine", "cdn", "css", "yui" |
@@ -123,3 +123,7 @@ Combo Handler | ||
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: | ||
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: | ||
@@ -132,3 +136,4 @@ ```js | ||
This method may be extended in the future to do fancy things with optional combohandler middleware. | ||
This method may be extended in the future to do fancy things with optional | ||
combohandler middleware. | ||
@@ -138,4 +143,4 @@ ### Creating a server | ||
If you just want to get a server up and running quickly by specifying a mapping | ||
of routes to local root paths, use the `combohandler/lib/server` module. It | ||
creates a barebones Express server that will perform combo handling on the | ||
of routes to local root paths, use the `combohandler/lib/server` module. | ||
It creates a barebones Express server that will perform combo handling on the | ||
routes you specify: | ||
@@ -174,5 +179,8 @@ | ||
If installed globally via `npm -g install`, the CLI executable `combohandler` is provided. | ||
If you're operating from a local clone, `npm link` in the repository root and you're off to the races. | ||
To start the default single-process server, it's as simple as | ||
If installed globally via `npm -g install`, | ||
the CLI executable `combohandler` is provided. | ||
If you're operating from a local clone, | ||
`npm link` in the repository root and you're off to the races. | ||
To start the default single-process server, | ||
it's as simple as | ||
@@ -184,9 +192,12 @@ ```bash | ||
Of course, the default output leaves something to be desired: that is to say, any output. | ||
Of course, the default output leaves something to be desired: that is to say, | ||
any output. | ||
#### Root Configuration | ||
At the very least, you need to provide some route-to-rootPath mappings for your CLI combohandler. | ||
At the very least, | ||
you need to provide some route-to-rootPath mappings for your CLI combohandler. | ||
When passed in the `--rootsFile` option, the JSON file contents should follow this pattern: | ||
When passed in the `--rootsFile` option, | ||
the JSON file contents should follow this pattern: | ||
@@ -201,3 +212,4 @@ ```json | ||
When passed as individual `--root` parameters, the equivalent to the JSON above looks like this: | ||
When passed as individual `--root` parameters, | ||
the equivalent to the JSON above looks like this: | ||
@@ -230,3 +242,6 @@ ```bash | ||
-f, --rootsFile Path to JSON routes config, *exclusive* of --root. | ||
-b, --basePath Path to prepend when rewriting relative url()s. [''] | ||
-b, --basePath URL path to prepend when rewriting relative url()s. [''] | ||
-w, --webRoot Filesystem path to base rewritten relative url()s from. [''] | ||
Use this instead of --basePath when using route parameters. | ||
Overrides behaviour of --basePath. | ||
-m, --maxAge 'Cache-Control' and 'Expires' value, in seconds. [31536000] | ||
@@ -259,3 +274,5 @@ Set this to `0` to expire immediately, `null` to omit these | ||
With the advent of `node` v0.8.x, the core `cluster` module is now usable, and `combohandler` now regains the capability it once had. Huzzah! said the villagers. | ||
With the advent of `node` v0.8.x, the core `cluster` module is now usable, | ||
and `combohandler` now regains the capability it once had. | ||
Huzzah! said the villagers. | ||
@@ -268,3 +285,4 @@ To run a clustered combohandler from the CLI, just add the `--cluster` flag: | ||
To clusterize combohandler from a module dependency, `combohandler/lib/cluster` is your friend: | ||
To clusterize combohandler from a module dependency, | ||
`combohandler/lib/cluster` is your friend: | ||
@@ -290,4 +308,5 @@ ```js | ||
relative URLs in CSS files need to be updated to be relative to the | ||
combohandled path. Set the `basePath` configuration option to have the combo | ||
handler do this automatically. | ||
combohandled path. | ||
Set the `basePath` or `webRoot` configuration option to have the | ||
combohandler default middleware do this automatically. | ||
@@ -308,2 +327,8 @@ ```js | ||
}), combo.respond); | ||
// The equivalent config as the previous route, except using webRoot | ||
app.get('/combo', combo.combine({ | ||
rootPath: __dirname + '/public', | ||
webRoot : __dirname | ||
}), combo.respond); | ||
``` | ||
@@ -339,3 +364,3 @@ | ||
rootPath: __dirname + '/public', | ||
basePath: '/public', | ||
webRoot : __dirname, | ||
rewriteImports: true | ||
@@ -351,5 +376,17 @@ }), combo.respond); | ||
#### `basePath` or `webRoot`? | ||
In the simplest case, | ||
`basePath` and `webRoot` reach the same result from different directions. | ||
`basePath` allows you to rewrite a single well-known path under any root, | ||
whereas `webRoot` will handle any number of paths under a well-known root. | ||
In general, if you are using both optional middleware, | ||
you should prefer `webRoot` over `basePath`. | ||
### 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. | ||
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. | ||
@@ -362,3 +399,6 @@ ```js | ||
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: | ||
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: | ||
@@ -371,3 +411,4 @@ ```text | ||
If the built-in `dynamicPath` middleware is used manually, it _must_ be inserted *before* the default `combine` middleware. | ||
If the built-in `dynamicPath` middleware is used manually, it _must_ be | ||
inserted *before* the default `combine` middleware. | ||
@@ -374,0 +415,0 @@ |
/*global describe, before, after, it */ | ||
var fs = require('fs'); | ||
var path = require('path'); | ||
var combo = require('../'), | ||
@@ -31,2 +34,9 @@ server = require('../lib/server'), | ||
it("should return an array of middleware callbacks when invoked", function () { | ||
var callbacks = combo.combine(); | ||
callbacks.should.be.an.instanceOf(Array); | ||
callbacks.should.have.lengthOf(1); | ||
callbacks[0].name.should.equal('combineMiddleware'); | ||
}); | ||
it('should combine JavaScript', function (done) { | ||
@@ -116,2 +126,55 @@ request(BASE_URL + '/js?a.js&b.js', function (err, res, body) { | ||
describe('config: basePath', function () { | ||
describe('when absent', function () { | ||
it("should NOT append cssUrls middleware to callbacks", function () { | ||
var callbacks = combo.combine(); | ||
callbacks.should.have.lengthOf(1); | ||
callbacks[0].name.should.not.equal('cssUrlsMiddleware'); | ||
}); | ||
}); | ||
describe('when present', function () { | ||
it("should append cssUrls middleware to callbacks", function () { | ||
var callbacks = combo.combine({ basePath: 'foo' }); | ||
callbacks.should.have.lengthOf(2); | ||
callbacks[1].name.should.equal('cssUrlsMiddleware'); | ||
}); | ||
}); | ||
}); | ||
describe('config: webRoot', function () { | ||
describe('when absent', function () { | ||
it("should NOT append cssUrls middleware to callbacks", function () { | ||
var callbacks = combo.combine(); | ||
callbacks.should.have.lengthOf(1); | ||
callbacks[0].name.should.not.equal('cssUrlsMiddleware'); | ||
}); | ||
}); | ||
describe('when present', function () { | ||
it("should append cssUrls middleware to callbacks", function () { | ||
var callbacks = combo.combine({ webRoot: 'foo' }); | ||
callbacks.should.have.lengthOf(2); | ||
callbacks[1].name.should.equal('cssUrlsMiddleware'); | ||
}); | ||
}); | ||
}); | ||
describe('config: rootPath', function () { | ||
it("should error when value does not exist", function () { | ||
/*jshint immed: false */ | ||
(function () { | ||
combo.combine({ rootPath: '/foo' }); | ||
}).should.throwError(); | ||
}); | ||
describe('with route parameters', function () { | ||
it("should prepend dynamicPath middleware to callbacks", function () { | ||
var callbacks = combo.combine({ rootPath: __dirname + '/fixtures/:root/js' }); | ||
callbacks.should.have.lengthOf(2); | ||
callbacks[0].name.should.equal('dynamicPathMiddleware'); | ||
}); | ||
}); | ||
}); | ||
// -- Errors --------------------------------------------------------------- | ||
@@ -252,4 +315,58 @@ describe('errors', function () { | ||
// -- URL Rewrites --------------------------------------------------------- | ||
// -- Optional Middleware -------------------------------------------------- | ||
describe("url rewrites", function () { | ||
var TEMPLATE_URLS = [ | ||
"#no-quotes { background: url(__PATH__no-quotes.png);}", | ||
"#single-quotes { background: url(\'__PATH__single-quotes.png\');}", | ||
"#double-quotes { background: url(\"__PATH__double-quotes.png\");}", | ||
"#spaces { background: url(", | ||
" \"__PATH__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(\"__PATH__\\)\\\";\\'\\(.png\"); }", | ||
".unicode-raw { background: url(__PATH__déchaîné.png); }", | ||
// NOTE: we do not currently support the space terminator for CSS escapes. | ||
// ".unicode-escaped { background: url(__PATH__d\\E9 cha\\EEn\\E9.png); }", | ||
".unicode-escaped { background: url(__PATH__d\\0000E9cha\\EEn\\E9.png); }", | ||
".nl-craziness { background:", | ||
" url(__PATH__crazy.png", | ||
" ); }", | ||
"" | ||
].join("\n"); | ||
var TEMPLATE_MORE = [ | ||
"#depth { background: url(__PATH__deeper/deeper.png);}", | ||
"#up-one { background: url(__PATH__shallower.png);}", | ||
"#down-one { background: url(__PATH__deeper/more/down-one.png);}" | ||
].join("\n"); | ||
var TEMPLATE_IMPORTS = [ | ||
"@import '__PATH__basic-sq.css';", | ||
"@import \"__PATH__basic-dq.css\";", | ||
"@import url(__PATH__url-uq.css);", | ||
"@import url('__PATH__url-sq.css');", | ||
"@import url(\"__PATH__url-dq.css\");", | ||
"@import \"__PATH__media-simple.css\" print;", | ||
"@import url(\"__PATH__media-simple-url.css\") print;", | ||
"@import '__PATH__media-simple-comma.css' print, screen;", | ||
"@import \"__PATH__media-complex.css\" screen and (min-width: 400px) and (max-width: 700px);", | ||
"@import url(\"__PATH__media-complex-url.css\") screen and (min-width: 400px) and (max-width: 700px);", | ||
// TODO: are the following rewritten correctly? | ||
"@import \"__DOTS__/rewrite/deeper/more.css\";", | ||
"@import \"__DOTS__/root/css/a.css\" (device-width: 320px);", | ||
"" | ||
].join("\n"); | ||
var URLS_UNMODIFIED = TEMPLATE_URLS.replace(/__PATH__/g, ''); | ||
var IMPORTS_UNMODIFIED = TEMPLATE_IMPORTS.replace(/__PATH__/g, '').replace(/__DOTS__/g, '..'); | ||
function assertRequestBodyIs(reqPath, result, done) { | ||
request(BASE_URL + reqPath, function (err, res, body) { | ||
assert.ifError(err); | ||
body.should.equal(result); | ||
done(); | ||
}); | ||
} | ||
before(function () { | ||
@@ -262,3 +379,3 @@ app.get('/norewrite', combo.combine({ | ||
rootPath: __dirname + '/fixtures/rewrite', | ||
basePath: "/rewritten" | ||
basePath: "/rewritten/" | ||
}), combo.respond); | ||
@@ -268,3 +385,3 @@ | ||
rootPath: __dirname + '/fixtures/rewrite', | ||
basePath: "/rewritten/" | ||
basePath: "/rewritten" | ||
}), combo.respond); | ||
@@ -277,203 +394,91 @@ | ||
}), combo.respond); | ||
app.get('/rewrite-middleware-before-combine', | ||
combo.cssUrls({ basePath: "/rewritten/" }), | ||
combo.combine({ rootPath: __dirname + '/fixtures/rewrite' }), | ||
combo.respond); | ||
app.get('/rewrite-middleware-noconfig', | ||
combo.combine({ rootPath: __dirname + '/fixtures/rewrite' }), | ||
combo.cssUrls(), | ||
combo.respond); | ||
app.get('/rewrite-root', combo.combine({ | ||
rootPath: __dirname + '/fixtures/rewrite', | ||
webRoot: __dirname + '/fixtures/' | ||
}), combo.respond); | ||
app.get('/rewrite-root-noslash', combo.combine({ | ||
rootPath: __dirname + '/fixtures/rewrite', | ||
webRoot: __dirname + '/fixtures' | ||
}), combo.respond); | ||
app.get('/rewrite-root-imports', combo.combine({ | ||
rootPath: __dirname + '/fixtures/rewrite', | ||
webRoot: __dirname + '/fixtures/', | ||
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.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", | ||
" ); }", | ||
"" | ||
].join("\n")); | ||
done(); | ||
}); | ||
it("should not rewrite without a basePath or webRoot", function (done) { | ||
assertRequestBodyIs("/norewrite?urls.css", URLS_UNMODIFIED, done); | ||
}); | ||
it("should not rewrite without a basePath", function (done) { | ||
request(BASE_URL + "/norewrite?urls.css", function (err, res, body) { | ||
assert.ifError(err); | ||
body.should.equal([ | ||
"#no-quotes { background: url(no-quotes.png);}", | ||
"#single-quotes { background: url(\'single-quotes.png\');}", | ||
"#double-quotes { background: url(\"double-quotes.png\");}", | ||
"#spaces { background: url(", | ||
" \"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(\"\\)\\\";\\'\\(.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")); | ||
done(); | ||
}); | ||
it("should not rewrite without a basePath or webRoot as middleware", function (done) { | ||
assertRequestBodyIs("/rewrite-middleware-noconfig?urls.css", URLS_UNMODIFIED, done); | ||
}); | ||
it("should rewrite valid urls", function (done) { | ||
request(BASE_URL + "/rewrite?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); }", | ||
// 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);}", | ||
"#up-one { background: url(/rewritten/shallower.png);}", | ||
"#down-one { background: url(/rewritten/deeper/more/down-one.png);}" | ||
].join("\n")); | ||
done(); | ||
}); | ||
it("should not rewrite when middleware before combine()", function (done) { | ||
assertRequestBodyIs("/rewrite-middleware-before-combine?urls.css", URLS_UNMODIFIED, done); | ||
}); | ||
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("with configured basePath", function () { | ||
var URLS_REWRITTEN = TEMPLATE_URLS.replace(/__PATH__/g, '/rewritten/'); | ||
var MORE_REWRITTEN = TEMPLATE_MORE.replace(/__PATH__/g, '/rewritten/'); | ||
var MORE_URLS_REWRITTEN = [URLS_REWRITTEN, MORE_REWRITTEN].join("\n"); | ||
var IMPORTS_REWRITTEN = TEMPLATE_IMPORTS.replace(/__PATH__/g, '/rewritten/') | ||
.replace(/__DOTS__/g, ''); | ||
it("should allow basePath without trailing slash", function (done) { | ||
assertRequestBodyIs("/rewrite-noslash?urls.css", URLS_REWRITTEN, 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); | ||
it("should rewrite valid urls", function (done) { | ||
assertRequestBodyIs("/rewrite?urls.css&deeper/more.css", MORE_URLS_REWRITTEN, done); | ||
}); | ||
app.get('/rewrite-middleware', | ||
combo.combine({ rootPath: __dirname + '/fixtures/rewrite' }), | ||
combo.cssUrls({ basePath: "/rewritten/" }), | ||
combo.respond); | ||
it("should NOT rewrite import paths when disabled", function (done) { | ||
assertRequestBodyIs("/rewrite?imports.css", IMPORTS_UNMODIFIED, done); | ||
}); | ||
app.get('/rewrite-middleware-imports', | ||
combo.combine({ rootPath: __dirname + '/fixtures/rewrite' }), | ||
combo.cssUrls({ basePath: "/rewritten/", rewriteImports: true }), | ||
combo.respond); | ||
it("should rewrite import paths when enabled", function (done) { | ||
assertRequestBodyIs("/rewrite-imports?imports.css", IMPORTS_REWRITTEN, done); | ||
}); | ||
}); | ||
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(); | ||
}); | ||
describe("with configured webRoot", function () { | ||
var URLS_REBASED = TEMPLATE_URLS.replace(/__PATH__/g, '/rewrite/'); | ||
var MORE_REBASED = TEMPLATE_MORE.replace(/__PATH__/g, '/rewrite/'); | ||
var MORE_URLS_REBASED = [URLS_REBASED, MORE_REBASED].join("\n"); | ||
var IMPORTS_REBASED = TEMPLATE_IMPORTS.replace(/__PATH__/g, '/rewrite/') | ||
.replace(/__DOTS__/g, ''); | ||
it("should allow webRoot without trailing slash", function (done) { | ||
assertRequestBodyIs("/rewrite-root-noslash?urls.css", URLS_REBASED, 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(); | ||
}); | ||
assertRequestBodyIs("/rewrite-root?urls.css&deeper/more.css", MORE_URLS_REBASED, 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(); | ||
}); | ||
assertRequestBodyIs("/rewrite-root?imports.css", IMPORTS_UNMODIFIED, 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(); | ||
}); | ||
assertRequestBodyIs("/rewrite-root-imports?imports.css", IMPORTS_REBASED, done); | ||
}); | ||
@@ -483,3 +488,2 @@ }); | ||
// Dynamic Paths ---------------------------------------------------------- | ||
describe("dynamic paths", function () { | ||
@@ -510,2 +514,7 @@ before(function () { | ||
app.get('/dynamic-no-config', | ||
combo.dynamicPath(), | ||
combo.combine({ rootPath: __dirname + '/fixtures/dynamic/decafbad' }), | ||
combo.respond); | ||
app.get('/route-only/:version/lib', | ||
@@ -582,2 +591,13 @@ combo.combine({ rootPath: __dirname + '/fixtures/root' }), | ||
it("should not fail when config missing", function (done) { | ||
request(BASE_URL + '/dynamic-no-config?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 only found in route, not rootPath", function (done) { | ||
@@ -603,2 +623,184 @@ request(BASE_URL + '/route-only/deadbeef/lib?js/a.js&js/b.js', function (err, res, body) { | ||
}); | ||
// -- Complex Integration -------------------------------------------------- | ||
describe("complex", function () { | ||
// Strange things may happen when you mix symlinks, parameters, and complex routes | ||
var COMPLEX_ROOT = __dirname + '/fixtures/complex'; | ||
var TEMPLATE_IMPORTS_SIMPLE = [ | ||
'@import "__ROOT__css/parent.css";', | ||
'@import "__ROOT__css/urls/child/dir.css";', | ||
'@import "__ROOT__css/urls/sibling.css";', | ||
'@import "__ROOT__css/urls/also-sibling.css";', | ||
'' | ||
].join('\n'); | ||
var TEMPLATE_URLS_SIMPLE = [ | ||
'.relatives { background: url(__ROOT__images/cousin.png); }', | ||
'.offspring { background: url(__ROOT__css/urls/images/grandchild.png); }', | ||
'' | ||
].join('\n'); | ||
var TEMPLATE_SIMPLE = TEMPLATE_IMPORTS_SIMPLE + TEMPLATE_URLS_SIMPLE; | ||
var SIMPLE_IMPORTS_RAW = [ | ||
'@import "../parent.css";', | ||
'@import "child/dir.css";', | ||
'@import "./sibling.css";', | ||
'@import "../urls/also-sibling.css";', | ||
'' | ||
].join('\n'); | ||
var SIMPLE_URLS_RAW = [ | ||
'.relatives { background: url(../../images/cousin.png); }', | ||
'.offspring { background: url(./images/grandchild.png); }', | ||
'' | ||
].join('\n'); | ||
var SIMPLE_RAW = SIMPLE_IMPORTS_RAW + SIMPLE_URLS_RAW; | ||
function assertRequestSuccess(reqPath, done) { | ||
request(BASE_URL + reqPath, function (err, res, body) { | ||
assert.ifError(err); | ||
res.should.have.status(200); | ||
done(); | ||
}); | ||
} | ||
describe("route with fully-qualified dynamic path", function () { | ||
var combined = combo.combine({ | ||
webRoot : COMPLEX_ROOT, | ||
rootPath: COMPLEX_ROOT + '/versioned/:version/base/' | ||
}); | ||
it("should read rootPath from filesystem directly", function (done) { | ||
app.get("/c/:version/fs-fq", combined, function (req, res, next) { | ||
var rootPath = res.locals.rootPath; | ||
rootPath.should.equal(path.join(COMPLEX_ROOT, '/versioned/deeper/base/')); | ||
next(); | ||
}, combo.respond); | ||
assertRequestSuccess("/c/deeper/fs-fq?js/a.js&js/b.js", done); | ||
}); | ||
it("should resolve rootPath through symlink", function (done) { | ||
app.get("/c/:version/ln-fq", combined, function (req, res, next) { | ||
var rootPath = res.locals.rootPath; | ||
rootPath.should.equal(path.join(COMPLEX_ROOT, '/versioned/shallower/base/')); | ||
var relativePath = res.locals.relativePaths[0]; | ||
relativePath.should.equal('js/a.js'); | ||
fs.realpath(path.join(rootPath, relativePath), function (err, resolved) { | ||
assert.ifError(err); | ||
resolved.should.equal(path.join(COMPLEX_ROOT, '/base/', relativePath)); | ||
next(); | ||
}); | ||
}, combo.respond); | ||
assertRequestSuccess("/c/shallower/ln-fq?js/a.js&js/b.js", done); | ||
}); | ||
it("should only rewrite url() through symlink, not imports", function (done) { | ||
app.get("/c/:version/fq-noimports", combined, function (req, res, next) { | ||
var rootPath = res.locals.rootPath; | ||
rootPath.should.equal(path.join(COMPLEX_ROOT, '/versioned/shallower/base/')); | ||
var relativePath = res.locals.relativePaths[0]; | ||
relativePath.should.equal('css/urls/simple.css'); | ||
// console.error(res.body); | ||
var expected = (SIMPLE_IMPORTS_RAW + TEMPLATE_URLS_SIMPLE) | ||
.replace(/__ROOT__/g, '/versioned/shallower/base/'); | ||
res.body.should.equal(expected); | ||
next(); | ||
}, combo.respond); | ||
assertRequestSuccess("/c/shallower/fq-noimports?css/urls/simple.css", done); | ||
}); | ||
}); | ||
describe("route with one-sided dynamic path", function () { | ||
describe("and rootPath symlinked shallower", function () { | ||
var combined = combo.combine({ | ||
rewriteImports: true, | ||
webRoot : COMPLEX_ROOT, | ||
rootPath: COMPLEX_ROOT + '/versioned/shallower/base/' | ||
}); | ||
it("should resolve files from realpath in filesystem", function (done) { | ||
app.get("/c/:version/fs-shallow", combined, function (req, res, next) { | ||
var rootPath = res.locals.rootPath; | ||
rootPath.should.equal(path.join(COMPLEX_ROOT, '/base/')); | ||
var relativePath = res.locals.relativePaths[0]; | ||
relativePath.should.equal('js/a.js'); | ||
fs.realpath(path.join(rootPath, relativePath), function (err, resolved) { | ||
assert.ifError(err); | ||
resolved.should.equal(path.join(COMPLEX_ROOT, '/base/', relativePath)); | ||
next(); | ||
}); | ||
}, combo.respond); | ||
assertRequestSuccess("/c/cafebabe/fs-shallow?js/a.js&js/b.js", done); | ||
}); | ||
it("should rewrite url() through symlink", function (done) { | ||
app.get("/c/:version/ln-shallow", combined, function (req, res, next) { | ||
var rootPath = res.locals.rootPath; | ||
rootPath.should.equal(path.join(COMPLEX_ROOT, '/base/')); | ||
var relativePath = res.locals.relativePaths[0]; | ||
relativePath.should.equal('css/urls/simple.css'); | ||
// console.error(res.body); | ||
res.body.should.equal(TEMPLATE_SIMPLE.replace(/__ROOT__/g, '/base/')); | ||
next(); | ||
}, combo.respond); | ||
assertRequestSuccess("/c/cafebabe/ln-shallow?css/urls/simple.css", done); | ||
}); | ||
}); | ||
describe("and rootPath symlinked deeper", function () { | ||
var combined = combo.combine({ | ||
rewriteImports: true, | ||
webRoot : COMPLEX_ROOT, | ||
rootPath: COMPLEX_ROOT + '/deep-link/' | ||
}); | ||
it("should read rootPath from filesystem directly", function (done) { | ||
app.get("/c/:version/fs-deeper", combined, function (req, res, next) { | ||
var rootPath = res.locals.rootPath; | ||
rootPath.should.equal(path.join(COMPLEX_ROOT, '/versioned/deeper/base/')); | ||
var relativePath = res.locals.relativePaths[0]; | ||
relativePath.should.equal('js/a.js'); | ||
next(); | ||
}, combo.respond); | ||
assertRequestSuccess("/c/cafebabe/fs-deeper?js/a.js&js/b.js", done); | ||
}); | ||
it("should *still* rewrite url() through symlink", function (done) { | ||
app.get("/c/:version/ln-deeper", combined, function (req, res, next) { | ||
var rootPath = res.locals.rootPath; | ||
rootPath.should.equal(path.join(COMPLEX_ROOT, '/versioned/deeper/base/')); | ||
var relativePath = res.locals.relativePaths[0]; | ||
relativePath.should.equal('css/urls/simple.css'); | ||
// console.error(res.body); | ||
var expected = TEMPLATE_SIMPLE | ||
.replace(/__ROOT__/g, '/versioned/deeper/base/'); | ||
res.body.should.equal(expected); | ||
next(); | ||
}, combo.respond); | ||
assertRequestSuccess("/c/cafebabe/ln-deeper?css/urls/simple.css", done); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); |
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
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
124151
47
2499
442
19