├── example
├── css
│ ├── a.css
│ ├── b.css
│ └── c.css
├── js
│ ├── a.js
│ ├── b.js
│ └── c.js
├── prod
│ ├── min.css
│ └── min.js
├── index.ejs
├── run.js
└── README.md
├── .gitignore
├── index.js
├── .travis.yml
├── test
├── test-data
│ └── test-file.js
├── compat-test.js
├── express-test.js
└── connect-cachify-test.js
├── .jshintrc
├── lib
├── compat.js
└── connect-cachify.js
├── package.json
├── docs
└── API.md
├── README.md
└── LICENSE
/example/css/a.css:
--------------------------------------------------------------------------------
1 | /* a.css */
--------------------------------------------------------------------------------
/example/css/b.css:
--------------------------------------------------------------------------------
1 | /* b.css */
--------------------------------------------------------------------------------
/example/css/c.css:
--------------------------------------------------------------------------------
1 | /* c.css */
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *~
2 | #*
3 | node_modules
--------------------------------------------------------------------------------
/example/js/a.js:
--------------------------------------------------------------------------------
1 | console.log("a.js");
2 |
--------------------------------------------------------------------------------
/example/js/b.js:
--------------------------------------------------------------------------------
1 | console.log("b.js");
2 |
--------------------------------------------------------------------------------
/example/js/c.js:
--------------------------------------------------------------------------------
1 | console.log("c.js");
2 |
--------------------------------------------------------------------------------
/example/prod/min.css:
--------------------------------------------------------------------------------
1 | /* a.css *//* b.css *//* c.css */
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | module.exports = require('./lib/connect-cachify');
2 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - 0.6
4 | - 0.8
--------------------------------------------------------------------------------
/example/prod/min.js:
--------------------------------------------------------------------------------
1 | console.log("a.js");
2 | console.log("b.js");
3 | console.log("c.js");
4 |
--------------------------------------------------------------------------------
/test/test-data/test-file.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | console.log('this is a test file used to calculate an md5 to see if url_to_paths is working correctly.');
3 | }());
4 |
--------------------------------------------------------------------------------
/example/index.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | i love cachify
5 | <%- cachify_css('/prod/min.css') %>
6 |
7 |
8 | This is a page that uses cachify
9 |
10 | <%- cachify_js('/prod/min.js') %>
11 |
12 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "passfail": false,
3 | "maxerr": 100,
4 | "node": true,
5 | "forin": false,
6 | "boss": true,
7 | "noarg": true,
8 | "undef": true,
9 | "unused": true,
10 | "browser": true,
11 | "laxbreak": true,
12 | "laxcomma": true,
13 | "eqeqeq": true,
14 | "eqnull": true,
15 | "expr": true,
16 | "indent": 2,
17 | "predef": [
18 | "exports",
19 | "require",
20 | "process"
21 | ],
22 | "es5": true,
23 | "esnext": true,
24 | "shadow": false,
25 | "supernew": false,
26 | "strict": false
27 | }
28 |
--------------------------------------------------------------------------------
/example/run.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | var express = require('express'),
4 | cachify = require('../');
5 |
6 | var app = express.createServer();
7 |
8 | app.use(cachify.setup({
9 | '/prod/min.css': [
10 | "/css/a.css",
11 | "/css/b.css",
12 | "/css/c.css"
13 | ],
14 | '/prod/min.js': [
15 | "/js/a.js",
16 | "/js/b.js",
17 | "/js/c.js"
18 | ]
19 | }, {
20 | root: __dirname,
21 | production: !process.env['DEV']
22 | }));
23 |
24 | app.set('views', __dirname);
25 | app.set('view options', { layout: false });
26 | app.get('/', function(req, res){
27 | res.render('index.ejs');
28 | });
29 |
30 | app.use(express.static(__dirname));
31 |
32 | app.listen(3000);
33 |
--------------------------------------------------------------------------------
/example/README.md:
--------------------------------------------------------------------------------
1 | ## Overview
2 |
3 | A simple application that demonstrates cachify.
4 |
5 | * `run.js` - the express app that runs the show
6 | * `prod/` - where "production" minified resources are kept
7 | * `js/` or `css/` - where "development" sources are kept
8 | * `index.ejs` - a page that uses all the resources
9 |
10 | ## Running it
11 |
12 | $ ./run.js
13 |
14 | now look at `http://127.0.0.1:3000` in your browser, notice
15 | only one resource for css and one for js is served with a
16 | url containing a has based on file contents. Have a look at
17 | the HTTP headers too.
18 |
19 | $ DEV=true ./run.js
20 |
21 | now look at `http://127.0.0.1:3000` in your browser, notice
22 | the development resources are individually served.
23 |
--------------------------------------------------------------------------------
/lib/compat.js:
--------------------------------------------------------------------------------
1 | //var format = require('util').format;
2 |
3 | /**
4 | * Weak version of util.format supports only %s placeholders.
5 | * Fixes Issue#3 compatibility with node < 5.4
6 | */
7 | exports.format = function (fmt, args) {
8 | if (fmt.indexOf('%d') != -1 ||
9 | fmt.indexOf('%j') != -1) {
10 | throw "This format only supports %s placeholders";
11 | }
12 | if (typeof arguments[0] != 'string') {
13 | throw "First argument to format, must be a format string";
14 | }
15 | var i = 0;
16 | var params = arguments;
17 | return fmt.replace(/%s/g, function () {
18 | i++;
19 | if (params[i] === undefined) {
20 | throw "Number of arguments didn't match format placeholders.";
21 | }
22 | return params[i];
23 | });
24 | }
--------------------------------------------------------------------------------
/test/compat-test.js:
--------------------------------------------------------------------------------
1 | var compat = require('../lib/compat'),
2 | format = compat.format,
3 | nodeunit = require('nodeunit');
4 |
5 | exports.format = nodeunit.testCase({
6 | "typical format": function (test) {
7 | test.equal(format("/%s%s", "foo", "/bar"), "/foo/bar");
8 | test.equal(format("%s/%s", "foo", "bar"), "foo/bar");
9 | test.equal(format("%s", "foo"), "foo");
10 | test.equal(format("ID#%s", 32432), "ID#32432");
11 | test.equal(format("foo"), "foo");
12 | test.done();
13 | },
14 | "programmer error": function (test) {
15 | test.throws(function () {
16 | format(1, "foo", "/bar");
17 | });
18 | test.throws(function () {
19 | format("%d/%d", 1, 2);
20 | });
21 | test.throws(function () {
22 | format("%s/%s");
23 | });
24 | test.done();
25 | }
26 | });
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Austin King (http://ozten.com)",
3 | "name": "connect-cachify",
4 | "description": "Connect middleware to provide easy frontend caching.",
5 | "keywords": ["cache", "HTTP", "expires", "asset", "max-age", "caching"],
6 | "version": "0.0.16",
7 | "homepage": "http://github.com/mozilla/connect-cachify",
8 | "licenses": [{
9 | "type": "MPL2",
10 | "url": "https://github.com/mozilla/connect-cachify/blob/master/LICENSE"
11 | }],
12 | "repository": {
13 | "type": "git",
14 | "url": "https://github.com/mozilla/connect-cachify.git"
15 | },
16 | "bugs": {
17 | "url": "https://github.com/mozilla/connect-cachify/issues"
18 | },
19 | "main": "index",
20 | "engines": {
21 | "node": ">=0.4.7"
22 | },
23 | "dependencies": {
24 | "underscore": ">=1.3.1"
25 | },
26 | "devDependencies": {
27 | "ejs": "0.6.1",
28 | "express": "2.5.8",
29 | "nodeunit": ">=0.6.4",
30 | "shelljs": "0.0.8",
31 | "step": ">=0.0.5"
32 | },
33 | "scripts": {
34 | "test": "nodeunit test"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/test/express-test.js:
--------------------------------------------------------------------------------
1 | /* Test Express 2.x to maintain compatability */
2 |
3 | var cachify = require('../lib/connect-cachify'),
4 | express2,
5 | express3,
6 | express4,
7 | http = require('http'),
8 | nodeunit = require('nodeunit'),
9 | os = require('os'),
10 | path = require('path'),
11 | shell = require('shelljs'),
12 | spawn = require('child_process').spawn;
13 |
14 | var tmpDir = os.tmpDir ? os.tmpDir() : '/tmp';
15 |
16 | var express2dir = path.join(tmpDir, 'connect-cachify-express2-test');
17 | var express3dir = path.join(tmpDir, 'connect-cachify-express3-test');
18 | var express4dir = path.join(tmpDir, 'connect-cachify-express4-test');
19 |
20 | const EXPRESS_DIRS = [ express2dir, express3dir, express4dir ];
21 |
22 | const EXPRESS_2_VER = '2.5.10';
23 | const EXPRESS_3_VER = '3.0.0';
24 | const EXPRESS_4_VER = '4.0.0';
25 |
26 | const EXPRESS_DEPS = [
27 | {
28 | dir: express2dir,
29 | ver: EXPRESS_2_VER
30 | },
31 | {
32 | dir: express3dir,
33 | ver: EXPRESS_3_VER
34 | },
35 | {
36 | dir: express4dir,
37 | ver: EXPRESS_4_VER
38 | }
39 | ];
40 |
41 | var debug = function (data) {
42 | process.stdout.write(data.toString('utf8'));
43 | };
44 |
45 | exports.setup = nodeunit.testCase({
46 | setUp: function (cb) {
47 | var remaining = EXPRESS_DEPS.length;
48 |
49 | EXPRESS_DEPS.forEach(function (express, i) {
50 | console.log('Creating ' + express.dir);
51 | shell.mkdir('-p', express.dir);
52 | process.chdir(express.dir);
53 |
54 | var npm = spawn('npm', ['install', 'express@' + express.ver], {});
55 | npm.stdout.on('data', debug);
56 | npm.stderr.on('data', debug);
57 |
58 | npm.on('exit', function (code) {
59 | if (0 !== code) {
60 | throw new Error('Unable to npm install express');
61 | }
62 |
63 | if (0 === i) {
64 | express2 = require(path.join(express2dir, 'node_modules/express'));
65 | } else if (1 === i) {
66 | express3 = require(path.join(express3dir, 'node_modules/express'));
67 | } else {
68 | express4 = require(path.join(express4dir, 'node_modules/express'));
69 | }
70 |
71 | --remaining;
72 | debug('remaining: ' + remaining);
73 | if (! remaining) {
74 | cb();
75 | }
76 | });
77 | });
78 | },
79 | tearDown: function (cb) {
80 | EXPRESS_DIRS.forEach(function (expressDir) {
81 | console.log('Removing ' + expressDir);
82 | shell.rm('-rf', expressDir);
83 | });
84 | cb();
85 | },
86 | "Run Server": function (test) {
87 | test.equal(express2.version, EXPRESS_2_VER);
88 | test.equal(express3.version, EXPRESS_3_VER);
89 |
90 | // Express 4 doesn't have a version so check for a property instead
91 | test.ok( !! express4.Router);
92 |
93 | var expresses = [express2, express3, express4];
94 | function testNextServer() {
95 | var express = expresses.shift();
96 | if (! express) return test.done();
97 |
98 | testExpress(test, express, testNextServer);
99 | }
100 |
101 | testNextServer();
102 | }
103 | });
104 |
105 | function testExpress(test, express, done) {
106 | var app = express.createServer ? express.createServer() : express();
107 | app.use(cachify.setup([]));
108 | app.use(function (req, res) {
109 | var locals = typeof res.locals === 'function' ? res.locals() : res.locals;
110 | test.ok(!! locals.cachify);
111 | test.ok(!! locals.cachify_css);
112 | test.ok(!! locals.cachify_js);
113 | test.ok(!! locals.cachify_prefetch);
114 | res.send('ok');
115 | });
116 | var server = app.listen(0);
117 | var port = server.address().port;
118 | debug('Started web server on port ' + port);
119 | var resText = "";
120 | var req = http.request({ port: port, path: '/baddecafe1/foo.js'}, function (res) {
121 | test.equal(res.statusCode, 200);
122 | res.on('data', function (chunk) {
123 | resText += chunk;
124 | });
125 | res.on('end', function () {
126 | test.equal(resText, 'ok');
127 | app.close ? app.close() : server.close();
128 | debug('Stopped web server on port ' + port);
129 | done();
130 | });
131 | });
132 | req.end();
133 | }
134 |
--------------------------------------------------------------------------------
/docs/API.md:
--------------------------------------------------------------------------------
1 | # connect-cachify API #
2 |
3 | The [README](../README.md) for this project has a good tutorial on using
4 | ``connect-cachify``. This documents the API and goes deep on all the options.
5 |
6 | ## setup(assets, [options]) ##
7 |
8 | Creates the cachify middleware and allows you to customizes how you'll use
9 | it in your Node.js app.
10 |
11 | ``assets`` is a dictionary where the keys are your production urls,
12 | and the value is a list of development urls that produce the same asset.
13 | See the example below for format.
14 |
15 | ``options`` is an optional dictionary with the following optional values:
16 |
17 | * ``debug`` - Boolean indicating we should always re-write urls with a hash. (**Default: false**)
18 | * ``prefix`` - String to prepend to the hash in links. (**Default: none**)
19 | * ``production`` - Boolean indicating if your in development or production mode.
20 | Effects how links for js and css files are generated. (**Defaults to ``true``**)
21 | * ``root`` - A fully qualified path which is a root where static
22 | resources are served up. This is the same value you'd send to the
23 | static middleware. (**Default: ``'.'``**)
24 | * ``url_to_paths`` - an associative array of URLs to absolute filename paths.
25 | Useful to specify paths to files that are not in the ``root`` directory.
26 | (**Default: ``{}``**)
27 |
28 | ### Example use of ``setup`` ###
29 |
30 | var assets = {
31 | '/js/main.min.js': ['/js/jquery.js', '/js/widget.js', '/js/main.js'],
32 | '/js/dashboard.js': ['/js/jquery.js', '/js/dashboard.js'],
33 | '/css/main.min.css': ['/css/reset.css', '/main.css'] };
34 | app.use(cachify.setup(assets, { root: __dirname }));
35 |
36 | ### Using url_to_paths and root in ``setup`` ###
37 |
38 | ``url_to_paths`` is an associative array that specifies the absolute path of an asset. This is useful for files that are not located under the root directory. In the following example, both jquery.js and reset.css are located outside of the root directory and would not normally be found by connect-cachify.
39 |
40 | var assets = {
41 | '/js/main.min.js': ['/js/jquery.js', '/js/widget.js', '/js/main.js'],
42 | '/js/dashboard.js': ['/js/jquery.js', '/js/dashboard.js'],
43 | '/css/main.min.css': ['/css/reset.css', '/main.css'] };
44 |
45 | // specify paths outside of the root directory to find files.
46 | var url_to_paths = {
47 | '/js/jquery.js': '/home/development/jquery/jquery.js',
48 | '/css/reset.css': '/home/development/css-reset/reset.css'
49 | };
50 |
51 | app.use(cachify.setup(assets, {
52 | root: __dirname,
53 | url_to_paths: url_to_paths
54 | }));
55 |
56 | Both ``root`` and ``url_to_paths`` are optional, though it is safe to use them together.
57 |
58 | ## Middleware Helper Functions ##
59 |
60 | The ``setup`` middleware will expose several **helpers** to your views:
61 |
62 | * cachify_js
63 | * cachify_css
64 | * cachify
65 |
66 | Using the optional ``prefix`` will slightly improve middleware performance
67 | when attempting to detect if a request is safe to re-write it's request url.
68 |
69 | ## cachify_js(production_js_url, [options]) ##
70 | Helper function for generating ``script`` tags for your Javascript files.
71 |
72 | ``production_js_url`` is a url to your minified, production ready Javascript.
73 |
74 | ``options`` is an optional dictionary with the following optional value:
75 |
76 | * ``hash`` - MD5 hash to use instead of calculating from disk (**Default: none**)
77 | * ``defer`` - if true, adds the defer attribute to scripts in production mode. (**Default: false**)
78 | * ``async`` - if true, adds the async attribute to scripts in production mode. (**Default: false**)
79 |
80 | In production mode, a single script tag is generated, with a cache-busting
81 | url. In development mode (``production: false``), Multiple script tags are
82 | generated, one per dependent file from the ``assets`` argument to the ``setup`` function.
83 |
84 | ### Example EJS template: ###
85 |
86 |
87 | <%- cachify_js('/js/main.min.js') %>
88 |