Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

combohandler

Package Overview
Dependencies
Maintainers
2
Versions
17
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

combohandler - npm Package Compare versions

Comparing version 0.3.1 to 0.3.2

test/fixtures/complex/base/css/urls/simple.css

13

HISTORY.md
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 @@ ------------------

7

lib/args.js

@@ -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");

7

lib/combohandler.js

@@ -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);
});
});
});
});
});
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc