This repository has been archived by the owner on Jul 15, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.js
executable file
·188 lines (168 loc) · 7.86 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
/*!
* express-adaptive-images
* Copyright(c) 2016 JP Erasmus
* MIT Licensed
*/
var url = require('url');
var path = require('path');
var fs = require('fs-extra');
var mobile = require('is-mobile');
var vary = require('vary');
var chalk = require('chalk');
var getResolution = function(totalWidth, breakpoints) {
var result = null;
breakpoints.forEach(function(breakpoint) {
if (totalWidth <= breakpoint) {
result = breakpoint;
}
});
return result;
};
function valueInArray(value, array) {
for (var i = 0, len = array.length; i < len; i++) {
if (value === array[i]) return true;
}
return false;
}
module.exports = function(dirName, options) {
var opts = Object.create(options || null);
var imageTypes = opts.imageTypes || [ '.jpg', '.png', '.jpeg', '.gif' ];
var breakpoints = opts.breakpoints || [ 1382, 992, 768, 480 ]; // the resolution break-points to use (screen widths, in pixels)
var cachePath = opts.cachePath || 'ai-cache'; // where to store the generated re-sized images. Specify from your document root!
var jpgQuality = opts.jpgQuality || 75; // the quality of any generated JPGs on a scale of 0 to 100
var sharpen = opts.sharpen || true; // Shrinking images can blur details, perform a sharpen on re-scaled images?
var watchCache = opts.watchCache || true; // check that the adapted image isn't stale (ensures updated source images are re-cached)
var cachePeriod = opts.cachePeriod || 60 * 60 * 24 * 7; // How long the BROWSER cache should last (seconds, minutes, hours, days. 7days by default)
var setExpirationHeaders = opts.setExpirationHeaders || true; // Whether expires headers need to be set on cached images
var useImageMagick = opts.useImageMagick || false; // Whether to use ImageMagick or the default GraphicsMagick
var compressionType = opts.compression || 'None'; // Choices are: None, BZip, Fax, Group4, JPEG, Lossless, LZW, RLE, Zip, or LZMA
var debugEnabled = opts.debug || false; // Whether debug is enabled or not
var gm = require('gm').subClass({ imageMagick: useImageMagick });
var debug = function() {
if (debugEnabled) {
console.log('\n');
console.log([].slice.call(chalk.blue(arguments)));
}
};
return function(req, res, next) {
var method = req.method.toUpperCase();
// If not a proper request or cookies don't exist, no point in continuing
if ((method !== 'GET' && method !== 'HEAD') || !req.cookies) {
next();
return;
}
// Get request for image
var requestedUri = req.originalUrl;
// if the requested URL starts with a slash, remove the slash
requestedUri = requestedUri.substr(0, 1) === '/' ? requestedUri.substr(1) : requestedUri;
if (!valueInArray(path.extname(requestedUri), imageTypes) || !req.headers.accept) {
debug('"' + path.extname(requestedUri) + '" is not an accepted image type: ', imageTypes.join(', '));
next();
return;
}
// Check device resolution
var cookieValue = req.cookies.resolution;
if (!cookieValue) {
throw new Error(chalk.red(chalk.underline('"express-adaptive-images":') + ' Make sure to set a cookie named "resolution".\n'));
}
var cookieData = cookieValue.split(',');
var clientWidth = parseInt(cookieData[0] || 0, 10); // the base resolution (CSS pixels)
var totalWidth = clientWidth;
var pixelDensity = parseInt(cookieData[1], 10) || 1; // the device's pixel density factor (physical pixels per CSS pixel)
// make sure the supplied break-points are in reverse size order
breakpoints.sort(function(a, b) {
return b - a;
});
// by default use the largest supported break-point
var resolution = breakpoints[0];
// if pixel density is not 1, then we need to be smart about adapting and fitting into the defined breakpoints
if (pixelDensity !== 1 && totalWidth <= breakpoints[0]) {
resolution = getResolution(clientWidth * pixelDensity, breakpoints) || resolution;
} else { // pixel density is 1, just fit it into one of the breakpoints
resolution = getResolution(totalWidth, breakpoints) || resolution;
}
// No "resolution" cookie was set
if (!clientWidth) {
// We send the lowest resolution for mobile-first approach, and highest otherwise
resolution = mobile(req) ? breakpoints[breakpoints.length - 1] : breakpoints[0];
}
// Update request URL for static middleware to take care of serving it up
var updateRequestUri = function(cacheFile) {
debug('Request URL before update: ', req.url);
req.url = cacheFile.replace(dirName, '');
vary(res, 'Accept');
if (setExpirationHeaders) {
var expires = cachePeriod * 1000;
res.setHeader("Cache-Control", "public, max-age=" + expires);
res.setHeader("Expires", new Date(Date.now() + expires).toUTCString());
}
debug('Updating Request URI to:', req.url);
next();
};
var createCachedImage = function(originalImage, cacheFile, resolution, requestedUri) {
gm(originalImage)
.resize(resolution)
.noProfile()
.quality(jpgQuality)
.compress(compressionType)
.write(cacheFile, function(error) {
if (error) {
debug('Writing of cached image failed.');
next();
} else {
debug('New cached image successfully written.');
updateRequestUri(cacheFile);
}
});
};
var imageHasExpired = function(cacheFile) {
try {
debug('Checking whether image has expired.');
var stats = fs.statSync(cacheFile);
var now = Math.round(Date.now() / 1000);
var modifiedTime = Math.round(stats.mtime.getTime() / 1000);
var difference = now - modifiedTime;
var expired = difference > cachePeriod;
debug('File expired? ', expired);
return expired;
} catch (err) {
debug('Reading cached image\'s statistics failed.');
// If error occurred best to be safe and mark file as expired
return true;
}
};
// Check if image url/<device-width>/image exists and hasn't expired yet
var cacheFileDirectory = path.resolve(dirName, cachePath, (resolution).toString()) + '/';
var cacheFile = path.resolve(dirName, cachePath, (resolution).toString(), requestedUri.substr(requestedUri.lastIndexOf('/') + 1));
var originalPathName = url.parse(req.url).pathname;
originalPathName = originalPathName.substr(0, 1) === '/' ? originalPathName.substr(1) : originalPathName;
var originalImage = path.resolve(dirName, originalPathName);
debug('Cache File Directory:', cacheFileDirectory);
debug('Cache File:', cacheFile);
debug('Original Path Name:', originalPathName);
debug('Original Image:', originalImage);
// Try and access cached images to obtain the size of the image
gm(cacheFile)
.size(function(sizeError) {
// If it doesn't exist or has expired; recreate correct size image in correct directory
if (sizeError) {
debug('Cached image doesn\'t exist, so we have to create it.');
fs.mkdirs(cacheFileDirectory, function(makeError) {
if (makeError) {
debug('Creation of cached directory failed.');
next();
} else {
debug('Cached directory successfully created. Creating cached image next...');
createCachedImage(originalImage, cacheFile, resolution, requestedUri);
}
});
} else if (watchCache && imageHasExpired(cacheFile)) {
debug('Image has expired.');
createCachedImage(originalImage, cacheFile, resolution, requestedUri);
} else {
debug('Cached image existed; updating request uri and passing on to static middleware to handle.');
updateRequestUri(cacheFile);
}
});
};
};