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

jac

Package Overview
Dependencies
Maintainers
1
Versions
5
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

jac - npm Package Compare versions

Comparing version 0.0.3 to 0.0.4

bin/css

5

CHANGELOG.md

@@ -0,1 +1,6 @@

## v0.0.4
* Add support to use `jac.resolve` with CSS files (offline)
* Allow json and js files to be used as configuration inputs for ./bin/jac
## v0.0.3

@@ -2,0 +7,0 @@

7

index.js

@@ -0,4 +1,7 @@

"use strict";
module.exports = {
create: require('./src/middleware').create,
digest: require('./src/digester').process
middleware: require('./src/middleware').create,
digest: require('./src/digester').process,
css: require('./src/css').process
};
{
"name": "jac",
"version": "0.0.3",
"version": "0.0.4",
"description": "jac provides methods to reference asset urls using permanently cachable urls.",

@@ -17,6 +17,7 @@ "main": "index",

"express": "~2.5.11",
"should": "~1.2.1"
"should": "~1.2.1",
"URIjs": "~1.8.3"
},
"scripts": {
"test": "./node_modules/.bin/mocha --reporter spec --bail"
"test": "./node_modules/.bin/mocha --reporter spec"
},

@@ -36,2 +37,2 @@ "dependencies": {

]
}
}

@@ -1,2 +0,14 @@

#jac
#jac [![Build Status](https://travis-ci.org/busbud/jac.png)](https://travis-ci.org/busbud/jac)
> Really when you push something out live to the world, you never want to change it without changing the name because
> there are so many misconfigured proxies out there. About 1% to 10% of your users will never get an update
> unless you change the name.
> You can make it cachable for 10 years, you're never going to push a change without changing the name of the file.<br/>
> <cite>- Steve Souders. HTML5DevConf (Jan 10, 2013).
<a href="http://marakana.com/s/post/1360/cache_is_king_steve_souders_html5_video">Cache is king</a>
availalable from <a href="http://mrkn.co/3wzua">http://mrkn.co/3wzua</a></cite>
jac provides methods to reference asset urls using permanently cachable urls.

@@ -7,4 +19,2 @@

[![Build Status](https://travis-ci.org/busbud/jac.png)](https://travis-ci.org/busbud/jac)
#Usage

@@ -26,8 +36,8 @@ jac middleware will handle asset file serving and asset url resolution

var express = require('express')
, config = require('./config')
, jac = require('jac').create(config)
, app = express.createServer();
, jac = require('jac')
, app = express.createServer()
, config = require('./config');
// Add middleware for all requests
app.use(jac.middleware);
app.use(jac.middleware(config));

@@ -37,3 +47,3 @@ // Add view that resolves an image url

var jac = res.local('jac') // returns jac view helper
, key = '/images/spacer.gif' // matches config key
, key = '/images/happy.png' // matches config key
, url = jac.resolve(key); // returns url with digest, handled by middleware

@@ -53,5 +63,6 @@

assets: [{
fullPath: require('path').resolve(__dirname, './public/images/spacer.gif'),
key: '/images/spacer.gif',
route: '/images/spacer.gif?b64Digest'
fullPath: require('path').resolve(__dirname, './public/images/happy.png'), // local file path
key: '/images/happy.png', // key used in views: jac.resolve('/images/happy.png')
route: '/images/b64Digest/happy.png', // route for middleware
url: '//cdn.host.net/images/b64Digest/happy.png' // url output to response and css
}],

@@ -89,6 +100,52 @@

### Replacing references in CSS
jac can be configured to process CSS files as part of the update process. It will ignore image references from external
sites (eg urls with an [authority](http://medialize.github.com/URI.js/docs.html#accessors-authority)) and data-uris,
and will attempt to resolve all other urls.
If it fails to resolve a url, it will throw an error, allowing you to find the problematic reference. Most likely, this
will easily be corrected by adjusting the path to the image to make it root relative.
Here's an example that will result in jac replacing the image reference
__src/stylesheets/main.css__
```css
body {background: url(/images/happy.png);}
```
__config.json__
```js
{
// required - files served by jac
assets: [{
fullPath: require('path').resolve(__dirname, './public/images/happy.png'), // local file path
key: '/images/happy.png', // key used in views: jac.resolve('/images/happy.png')
route: '/images/b64Digest/happy.png', // route for middleware
url: '//cdn.host.net/images/b64Digest/happy.png' // url output to response and css
}],
css: {
'public/stylesheets/main.css': 'src/stylesheets/main.css' // format <output>: <input>
}
}
```
To run the CSS replacement, update the jac config file to include the css property and use the following command
```bash
css --config ./config.json
```
The file at public/stylesheets/main.css will now contain the following
```css
body {background: url(//cdn.host.net/images/b64Digest/happy.png);}
```
## Compatibility
This version of jac is compatible with express 2.5.
It depends on `res.local()` to get and set the view local `jac.img` via middleware.
It depends on `res.local()` to get and set the view local `jac` via middleware.

@@ -119,2 +176,4 @@ ## Production

By default, jac will load the config from the `jac.json` file at the project root.
# Running Tests

@@ -121,0 +180,0 @@ To run the test suite first invoke the following command within the repo, installing the development dependencies:

@@ -0,1 +1,3 @@

"use strict";
var _ = require('lodash');

@@ -5,2 +7,3 @@ var url = require('url');

var async = require('async');
var URIjs = require('URIjs');
var readdirp = require('readdirp');

@@ -15,3 +18,3 @@

if (!_.isFunction(strategy)) {
strategy = require('./strategy/hash').create();
strategy = require('./strategy/hash').create(opts);
digestfn = strategy.digest.bind(strategy);

@@ -23,6 +26,2 @@ }

function routeName(entry) {
return entry.url + '?' + entry.digest;
}
/**

@@ -38,2 +37,3 @@ * Processes all files under opts.root and generates the config

var vdir = opts.vdir || '/';
var host = opts.host || '';
var filter = opts.fileFilter || ['*.gif', '*.jpg', '*.jpeg', '*.png'];

@@ -76,4 +76,4 @@ var silent = opts.silent;

fullPath: path.relative(base, f.fullPath),
key: f.path,
url: url.resolve(vdir, f.path)
key: url.resolve(vdir, f.path),
url: (new URIjs(url.resolve(vdir, f.path))).host(host).href()
};

@@ -100,3 +100,11 @@ }

.map(function (entry) {
entry.route = routeName(entry);
// generate the urls and route by injecting the digest into the path
var original = new URIjs(entry.url);
var modified = original
.clone()
.directory(original.directory() + '/' + entry.digest);
entry.url = modified.href();
entry.route = modified.host('').href();
return entry;

@@ -103,0 +111,0 @@ })

@@ -0,1 +1,3 @@

"use strict";
var _ = require('lodash');

@@ -13,3 +15,3 @@ var send = require('send');

var routes = assets.reduce(function (memo, entry) {
var assetsByRoute = assets.reduce(function (memo, entry) {
memo[entry.route] = entry;

@@ -19,4 +21,4 @@ return memo;

var keys = assets.reduce(function (memo, entry) {
memo[entry.key] = entry.route;
var assetsByKey = assets.reduce(function (memo, entry) {
memo[entry.key] = entry;
return memo;

@@ -35,10 +37,10 @@ }, {});

*/
function resolve (key) {
var route = keys[key];
function resolveAsset (key) {
var asset = assetsByKey[key];
if (!route) {
throw new Error('jac-img: key ' + key + ' not found, regenerate jac-img config');
if (!asset) {
throw new Error('jac: key ' + key + ' not found, regenerate jac config or update key value to match key in jac config');
}
return route;
return asset.url;
}

@@ -53,3 +55,3 @@

var jac = res.local('jac') || {};
jac.resolve = resolve;
jac.resolve = resolveAsset;

@@ -60,5 +62,5 @@ res.local('jac', jac);

function middleware (req, res, next) {
var hit = routes[req.url];
var asset = assetsByRoute[req.url];
if (!hit) {
if (!asset) {
locals(res);

@@ -73,3 +75,3 @@ return next();

send(req, hit.fullPath)
send(req, asset.fullPath)
.maxage(config.maxAge)

@@ -80,6 +82,3 @@ .on('error', next)

return {
middleware: middleware,
resolve: resolve
};
return middleware;
};

@@ -0,1 +1,3 @@

"use strict";
var fs = require('fs');

@@ -5,8 +7,9 @@ var crypto = require('crypto');

module.exports.create = function (opts) {
return new hashStrategy(opts);
return new HashStrategy(opts);
};
function hashStrategy(opts) {
function HashStrategy(opts) {
this.algorithm = opts && opts.algorithm || 'md5';
this.length = opts && opts.length || 7;
this.salt = opts && opts.salt || '';
this.length = opts && opts.length || 7;
}

@@ -20,3 +23,3 @@

*/
hashStrategy.prototype.digest = function (entry, done) {
HashStrategy.prototype.digest = function (entry, done) {
var self = this;

@@ -27,2 +30,4 @@ var hash = crypto.createHash(self.algorithm);

hash.update(self.salt.toString());
s.on('data', function(d) {

@@ -29,0 +34,0 @@ hash.update(d);

@@ -0,1 +1,3 @@

"use strict";
var should = require('should');

@@ -64,10 +66,81 @@ var path = require('path');

it('entries\' route should have a querystring digest', function () {
/**
Don't include a query string in the URL for static resources.
Most proxies, most notably Squid up through version 3.0, do not cache resources with a "?" in
their URL even if a Cache-control: public header is present in the response. To enable proxy
caching for these resources, remove query strings from references to static resources, and
instead encode the parameters into the file names themselves.
- https://developers.google.com/speed/docs/best-practices/caching
*/
it('entries\' route should not have a querystring, but should contain digest in path', function () {
entries.forEach(verify);
function verify (e) {
e.route.should.equal(e.url + '?' + e.digest);
e.route.indexOf('?').should.equal(-1);
e.route.indexOf(e.digest).should.not.equal(-1);
e.url.indexOf('?').should.equal(-1);
e.url.indexOf(e.digest).should.not.equal(-1);
}
});
it('entries\' key should start with a \'/\'', function () {
entries.forEach(verify);
function verify (e) {
e.key[0].should.equal('/');
}
});
});
describe('digest directory with explicit host', function () {
var entries, error;
before(function (done) {
var opts = {
root: __dirname,
fileFilter: ["*.gif"],
silent: false,
host: 'cdn.net'
};
digester.process(opts, cb);
function cb (err, res) {
error = err;
entries = res;
done();
}
});
it('entries should have the host set only on the url', function () {
entries.forEach(verify);
function verify (e) {
e.should.have.property('fullPath');
e.should.have.property('key');
e.should.have.property('url');
e.should.have.property('digest');
e.should.have.property('route');
var prefix = '//cdn.net/';
e.url.indexOf(prefix).should.equal(0);
e.route.indexOf(prefix).should.equal(-1);
e.key.indexOf(prefix).should.equal(-1);
}
});
it('entries\' route should not have a querystring, but should contain digest in path', function () {
entries.forEach(verify);
function verify (e) {
e.route.indexOf('?').should.equal(-1);
e.route.indexOf(e.digest).should.not.equal(-1);
e.url.indexOf('?').should.equal(-1);
e.url.indexOf(e.digest).should.not.equal(-1);
}
});
});
});

@@ -0,1 +1,3 @@

"use strict";
var should = require('should');

@@ -20,8 +22,8 @@ var path = require('path');

fullPath: path.resolve(__dirname, './fixtures/spacer.gif'),
key: 'images/spacer.gif',
route: '/images/spacer.gif.b64Digest',
mtime: new Date()
key: '/images/spacer.gif',
route: '/images/b64Digest/spacer.gif',
url: '/images/b64Digest/spacer.gif'
}
]
}).middleware;
});

@@ -34,6 +36,6 @@ app = express.createServer();

app.get('/view-spacer', function (req, res) {
res.send(res.local('jac').resolve('images/spacer.gif'));
res.send(res.local('jac').resolve('/images/spacer.gif'));
});
app.get('/view-unresolved', function (req, res) {
res.send(res.local('jac').resolve('images/noexisto.gif'));
res.send(res.local('jac').resolve('/images/noexisto.gif'));
});

@@ -59,3 +61,3 @@ app.error(function (err, req, res, next) {

request(app)
.get('/images/spacer.gif.b64Digest')
.get('/images/b64Digest/spacer.gif')
.expect('Cache-Control', 'public, max-age=' + twoweeks)

@@ -69,3 +71,3 @@ .expect('Content-Type', 'image/gif')

request(app)
.get('/images/spacer.gif.b64DigestX')
.get('/images/b64DigestX/spacer.gif')
.expect(404, done);

@@ -83,3 +85,3 @@ });

.get('/view-spacer')
.expect(200, '/images/spacer.gif.b64Digest', done);
.expect(200, '/images/b64Digest/spacer.gif', done);
});

@@ -90,5 +92,5 @@

.get('/view-unresolved')
.expect(500, 'jac-img: key images/noexisto.gif not found, regenerate jac-img config', done);
.expect(500, 'jac: key /images/noexisto.gif not found, regenerate jac config or update key value to match key in jac config', done);
});
});
});

@@ -0,1 +1,3 @@

"use strict";
var should = require('should');

@@ -11,67 +13,75 @@ var path = require('path');

// Setup algorithm/hash mapping for spacer file
var algorithms = {
'md5': 'MlRyYBVx8x4b8AZ0w2jTNQ==',
'sha1':'La6qi18Z8LwgnZdsAr1qy1GwCwo='
var digests = {
'': {
'md5': 'MlRyYBVx8x4b8AZ0w2jTNQ==',
'sha1':'La6qi18Z8LwgnZdsAr1qy1GwCwo='
},
1: {
'md5': 'TKX3uHbkUGXNoc4yxKgLJA==',
'sha1':'06ZZ1arUCeEiXsqE3_CAzeJN-LY='
}
};
var lengths = [5, 7];
lengths.forEach(function (length) {
Object.keys(algorithms).forEach(function(algo) {
var expectedDigest = algorithms[algo].substr(0, length);
Object.keys(digests).forEach(function (salt) {
Object.keys(digests[salt]).forEach(function(algo) {
var expectedDigest = digests[salt][algo].substr(0, length);
describe(length + ' char ' + algo + ' digest', function () {
var strategy = factory.create({
algorithm: algo,
length: length
});
describe(length + ' char ' + algo + ' digest with ' + (salt||'no') + ' salt', function () {
var strategy = factory.create({
algorithm: algo,
length: length,
salt: salt
});
it('should not be null', function () {
should.exist(strategy);
});
it('should not be null', function () {
should.exist(strategy);
});
it('should have a digest method', function () {
should.exist(strategy.digest);
strategy.digest.should.be.instanceof(Function);
});
it('should have a digest method', function () {
should.exist(strategy.digest);
strategy.digest.should.be.instanceof(Function);
});
describe('handles existing content', function () {
var entry = {
fullPath: path.resolve(__dirname, './fixtures/spacer.gif')
};
describe('handles existing content', function () {
var entry = {
fullPath: path.resolve(__dirname, './fixtures/spacer.gif')
};
before(function (done) {
strategy.digest(entry, done);
});
before(function (done) {
strategy.digest(entry, done);
});
it('should set digest as first N chars of hash', function () {
should.exist(entry);
entry.should.have.ownProperty('digest');
entry.digest.should.equal(expectedDigest);
entry.digest.should.have.length(length);
it('should set digest as first N chars of hash', function () {
should.exist(entry);
entry.should.have.ownProperty('digest');
entry.digest.should.equal(expectedDigest);
entry.digest.should.have.length(length);
});
});
});
describe('handles non-existent content', function () {
var error;
var entry = {
fullPath: path.resolve(__dirname, '../fixtures/no existo.gif')
};
describe('handles non-existent content', function () {
var error;
var entry = {
fullPath: path.resolve(__dirname, '../fixtures/no existo.gif')
};
before(function (done) {
strategy.digest(entry, function (err, result) {
error = err;
entry = result;
done();
before(function (done) {
strategy.digest(entry, function (err, result) {
error = err;
entry = result;
done();
});
});
});
it('should call back with error', function () {
should.exist(error);
error.code.should.equal('ENOENT');
});
it('should call back with error', function () {
should.exist(error);
error.code.should.equal('ENOENT');
});
it('entry should be undefined', function () {
should.not.exist(entry);
it('entry should be undefined', function () {
should.not.exist(entry);
});
});

@@ -78,0 +88,0 @@ });

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

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