express-static-gzip
Advanced tools
Comparing version
@@ -13,3 +13,3 @@ let serveStatic = require('serve-static'); | ||
* @param {string} rootFolder: folder to staticly serve files from | ||
* @param {{enableBrotli?:boolean, customCompressions?:{encodingName:string,fileExtension:string}[], orderPreference: string[], index?: boolean}} options: options to change module behaviour | ||
* @param {expressStaticGzip.ExpressStaticGzipOptions} options: options to change module behaviour | ||
* @returns express middleware function | ||
@@ -22,3 +22,3 @@ */ | ||
//create a express.static middleware to handle serving files | ||
let defaultStatic = serveStatic(rootFolder, options); | ||
let defaultStatic = serveStatic(rootFolder, opts.serveStatic || null); | ||
let compressions = []; | ||
@@ -35,3 +35,3 @@ let files = {}; | ||
return function middleware(req, res, next) { | ||
return function expressStaticGzip(req, res, next) { | ||
changeUrlFromEmptyToIndexHtml(req); | ||
@@ -105,3 +105,3 @@ | ||
if (opts.index && req.url.endsWith("/")) { | ||
req.url += "index.html"; | ||
req.url += opts.index; | ||
} | ||
@@ -108,0 +108,0 @@ } |
@@ -0,0 +0,0 @@ MIT License |
{ | ||
"name": "express-static-gzip", | ||
"version": "1.1.3", | ||
"version": "2.0.0", | ||
"description": "simple wrapper on top of express.static, that allows serving pre-gziped files", | ||
@@ -22,3 +22,3 @@ "main": "index.js", | ||
"dependencies": { | ||
"serve-static": "^1.12.3" | ||
"serve-static": "^1.14.1" | ||
}, | ||
@@ -25,0 +25,0 @@ "devDependencies": { |
@@ -0,7 +1,12 @@ | ||
# express-static-gzip | ||
Provides a small layer on top of *serve-static*, which allows to serve pre-gzipped files. Supports *brotli* and any other compressions as well. | ||
[![npm][npm-version-image]][npm-url] | ||
[![npm][npm-downloads-image]][npm-url] | ||
Provides a small layer on top of *serve-static*, which allows to serve pre-gzipped files. Supports *brotli* and allows configuring any other compression you can think of as well. | ||
# Requirements | ||
For the express-static-gzip middleware to work properly you need to first ensure that you have all files gzipped (or compressed with your desired algorithm), which you want to serve as a compressed version to the browser. | ||
Simplest use case is to either have a folder with only .gz files, or you have a folder with the .gz files next to the original files. Some goes for other compressions. | ||
Simplest use case is to either have a folder with only .gz files, or you have a folder with the .gz files next to the original files. Same goes for other compressions. | ||
@@ -14,2 +19,24 @@ # Install | ||
# Changelog for v2.0.0 | ||
* Even so this is mayor release, this should be fully backwards compatible and should not have any breaking change to v1.1.3. | ||
* Moves all options for `serverStatic` in it's own section, to prevent collisions when setting up your static fileserving middleware. | ||
* For backwards compatibility all properties in the root options object will be copied to the new `serverStatic` section, except if you have set values there already. Here is a small example of this behaviour: | ||
```JavaScript | ||
{ | ||
enableBrotli: true, // not a serverStatic option, will not be moved | ||
maxAge: 123, // not copied, as already present. | ||
index: 'main.js', // copied to serveStatic section | ||
serveStatic: { | ||
maxAge: 234, // will be kept | ||
cacheControl: false // will be kept as well | ||
} | ||
} | ||
``` | ||
In the above scenario serveStatic will use `cacheControl`: false, `index`: 'main.js', `maxAge`:234. | ||
# Usage | ||
@@ -62,10 +89,8 @@ In case you just want to serve gzipped files only, this simple example would do: | ||
This will enable brotli compression in addition to gzip | ||
Enables support for the brotli compression, using file extension 'br' (e.g. 'index.html.br'). | ||
* **`index`**: boolean (default: **true**) | ||
If not set to false, any request to '/' or 'somepath/' will be answered with the file '/index.html' or 'somepath/index.html' in an accepted compression | ||
By default this module will send "index.html" files in response to a request on a directory. To disable this set false or to supply a new index pass a string. | ||
* **`indexFromEmptyFile`** (**deprecated**, see `index` option) | ||
* **`customCompressions`**: [{encodingName: string, fileExtension: string}] | ||
@@ -79,2 +104,6 @@ | ||
* **`serveStatic`**: [ServeStaticOptions](https://github.com/expressjs/serve-static#options) | ||
This will be forwarded to the underlying `serveStatic` instance used by `expressStaticGzip` | ||
# Behavior warning | ||
@@ -111,1 +140,6 @@ | ||
> GET /main.js >>> /my/rootFolder/main.js | ||
[npm-url]: https://www.npmjs.com/package/express-static-gzip | ||
[npm-downloads-image]: https://img.shields.io/npm/dw/express-static-gzip | ||
[npm-version-image]: https://img.shields.io/npm/v/express-static-gzip |
@@ -41,2 +41,11 @@ const expect = require('chai').expect; | ||
it('should handle index option false, serveStatic.index overwritten', function () { | ||
setupServer({ index: false, serveStatic: { index: "index.html" } }); | ||
return requestFile('/').then(resp => { | ||
expect(resp.statusCode).to.equal(200); | ||
expect(resp.body).to.equal("index.html") | ||
}); | ||
}); | ||
it('should handle index option default', function () { | ||
@@ -51,8 +60,8 @@ setupServer(); | ||
it('should handle index option true', function () { | ||
setupServer({ index: true }); | ||
it('should handle index option set', function () { | ||
setupServer({ index: 'main.js', enableBrotli: true }); | ||
return requestFile('/', { 'accept-encoding': 'gzip' }).then(resp => { | ||
return requestFile('/', { 'accept-encoding': 'br' }).then(resp => { | ||
expect(resp.statusCode).to.equal(200); | ||
expect(resp.body).to.equal('index.html.gz'); | ||
expect(resp.body).to.equal('main.js.br'); | ||
}); | ||
@@ -164,6 +173,6 @@ }); | ||
it('should handle foldername with dot', function(){ | ||
it('should handle foldername with dot', function () { | ||
setupServer(null, 'wwwroot.gzipped'); | ||
return requestFile("/index.html", { 'accept-encoding': 'gzip'}).then(resp => { | ||
return requestFile("/index.html", { 'accept-encoding': 'gzip' }).then(resp => { | ||
expect(resp.statusCode).to.equal(200); | ||
@@ -174,6 +183,6 @@ expect(resp.body).to.equal('index.html.gz'); | ||
it('should handle url encoded path', function(){ | ||
it('should handle url encoded path', function () { | ||
setupServer(); | ||
return requestFile("/filename with spaces.txt", { 'accept-encoding': 'gzip'}).then(resp => { | ||
return requestFile("/filename with spaces.txt", { 'accept-encoding': 'gzip' }).then(resp => { | ||
expect(resp.statusCode).to.equal(200); | ||
@@ -184,2 +193,22 @@ expect(resp.body).to.equal('"filename with spaces.txt.gz"'); | ||
it('should use serveStatic options', function () { | ||
setupServer({ serveStatic: { setHeaders: (res) => { res.setHeader('Test-X', 'Value-Y') } } }); | ||
return requestFile('/index.html').then(resp => { | ||
expect(resp.statusCode).to.equal(200); | ||
expect(resp.headers['test-x']).to.equal('Value-Y'); | ||
expect(resp.headers['test-y']).to.be.undefined; | ||
}); | ||
}); | ||
it('should use serveStatic options set in root options', function () { | ||
setupServer({ setHeaders: (res) => { res.setHeader('Test-X', 'Value-Y') } }); | ||
return requestFile('/index.html').then(resp => { | ||
expect(resp.statusCode).to.equal(200); | ||
expect(resp.headers['test-x']).to.equal('Value-Y'); | ||
expect(resp.headers['test-y']).to.be.undefined; | ||
}); | ||
}); | ||
/** | ||
@@ -206,3 +235,3 @@ * | ||
* | ||
* @param {{enableBrotli?:boolean, customCompressions?:[{encodingName:string,fileExtension:string}], index?: boolean}} options | ||
* @param {expressStaticGzip.ExpressStaticGzipOptions} options | ||
*/ | ||
@@ -209,0 +238,0 @@ function setupServer(options, dir) { |
@@ -0,0 +0,0 @@ const expect = require('chai').expect; |
@@ -7,27 +7,40 @@ const expect = require('chai').expect; | ||
const res = sanitizeOptions(); | ||
expect(res).to.deep.equal({ index: true }); | ||
expect(res).to.deep.equal({ index: 'index.html' }); | ||
}); | ||
it('should parse index options', function () { | ||
let res = sanitizeOptions({ index: true }); | ||
expect(res).to.deep.equal({ index: true }); | ||
let res = sanitizeOptions({ index: 'main.js' }); | ||
expect(res).to.deep.equal({ index: 'main.js', serveStatic: { index: 'main.js' } }); | ||
res = sanitizeOptions({ index: false }); | ||
expect(res).to.deep.equal({ index: false }); | ||
expect(res).to.haveOwnProperty("index"); | ||
expect(res.index).to.equal(false); | ||
}); | ||
it('should parse indexFromEmptyFile options', function () { | ||
let res = sanitizeOptions({ indexFromEmptyFile: true }); | ||
expect(res).to.deep.equal({ index: true }); | ||
let res = sanitizeOptions({ indexFromEmptyFile: 'main.js' }); | ||
expect(res).to.deep.equal({ index: 'main.js' }); | ||
res = sanitizeOptions({ indexFromEmptyFile: false }); | ||
expect(res).to.deep.equal({ index: false }); | ||
expect(res).to.haveOwnProperty("index"); | ||
expect(res.index).to.equal(false); | ||
}); | ||
it('should keep serveStatic options', function () { | ||
const res = sanitizeOptions({ serveStatic: { index: 'main.js' } }); | ||
expect(res).to.deep.equal({ index: 'index.html', serveStatic: { index: 'main.js' } }); | ||
}); | ||
it('should not overwrite serverStatic.index when already set', function () { | ||
const res = sanitizeOptions({ index: false, serveStatic: { index: 'index.html' } }); | ||
expect(res).to.deep.equal({ index: false, serveStatic: { index: 'index.html' } }); | ||
}); | ||
it('should parse enableBrotli option', function () { | ||
let res = sanitizeOptions({ enableBrotli: true }); | ||
expect(res).to.deep.equal({ enableBrotli: true, index: true }); | ||
expect(res).to.deep.equal({ enableBrotli: true, index: 'index.html' }); | ||
res = sanitizeOptions({ enableBrotli: 'true' }); | ||
expect(res).to.deep.equal({ enableBrotli: true, index: true }); | ||
expect(res).to.deep.equal({ enableBrotli: true, index: 'index.html' }); | ||
}); | ||
@@ -38,3 +51,3 @@ | ||
const res = sanitizeOptions({ customCompressions: [compression] }); | ||
expect(res).to.deep.equal({ customCompressions: [compression], index: true }); | ||
expect(res).to.deep.equal({ customCompressions: [compression], index: 'index.html' }); | ||
}); | ||
@@ -44,3 +57,3 @@ | ||
const res = sanitizeOptions({ customCompressions: [] }); | ||
expect(res).to.deep.equal({ customCompressions: [], index: true }); | ||
expect(res).to.deep.equal({ customCompressions: [], index: 'index.html' }); | ||
}); | ||
@@ -50,8 +63,8 @@ | ||
const res = sanitizeOptions({ customCompressions: true }); | ||
expect(res).to.deep.equal({ index: true }); | ||
expect(res).to.deep.equal({ index: 'index.html' }); | ||
}); | ||
it('should strip additional options', function () { | ||
const res = sanitizeOptions({ index: true, test: 'value' }); | ||
expect(res).to.deep.equal({ index: true }); | ||
const res = sanitizeOptions({ index: 'main.js', test: 'value' }); | ||
expect(res).to.deep.equal({ index: 'main.js', serveStatic: { index: 'main.js' } }); | ||
}); | ||
@@ -61,4 +74,23 @@ | ||
const res = sanitizeOptions({ orderPreference: ['br'] }); | ||
expect(res).to.deep.equal({ index: true, orderPreference: ['br'] }); | ||
expect(res).to.deep.equal({ index: 'index.html', orderPreference: ['br'] }); | ||
}); | ||
it('should copy serveStatic options from root', function () { | ||
const res = sanitizeOptions({ fallthrough: false }); | ||
expect(res.serveStatic.fallthrough).to.equal(false); | ||
const res2 = sanitizeOptions({ setHeaders: () => 'test' }); | ||
expect(res2.serveStatic.setHeaders()).to.equal('test'); | ||
}); | ||
it('should copy serveStatic options from root, while keeping other serveStatic options', function () { | ||
const options = sanitizeOptions({ fallthrough: false, serveStatic: { maxAge: 234 } }); | ||
expect(options.serveStatic).to.deep.equal({ fallthrough: false, maxAge: 234 }); | ||
}); | ||
it('should not overwrite serveStatic options with options from root', function () { | ||
const options = sanitizeOptions({ maxAge: 123, serveStatic: { maxAge: 234 } }); | ||
expect(options.serveStatic).to.deep.equal({ maxAge: 234 }); | ||
}); | ||
}); |
@@ -0,0 +0,0 @@ // see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Encoding |
@@ -0,1 +1,4 @@ | ||
module.exports = { | ||
sanitizeOptions: sanitizeOptions | ||
}; | ||
@@ -5,3 +8,4 @@ /** | ||
* Removes problematic options from the input options object. | ||
* @param {{enableBrotli?:boolean, customCompressions?:[{encodingName:string,fileExtension:string}], indexFromEmptyFile?:boolean, index?: boolean}} userOptions | ||
* @param {expressStaticGzip.ExpressStaticGzipOptions} userOptions | ||
* @returns {expressStaticGzip.ExpressStaticGzipOptions} | ||
*/ | ||
@@ -11,38 +15,82 @@ function sanitizeOptions(userOptions) { | ||
let options = { | ||
/** | ||
* @type {expressStaticGzip.ExpressStaticGzipOptions} | ||
*/ | ||
let sanitizedOptions = { | ||
index: getIndexValue(userOptions) | ||
} | ||
if(userOptions.index){ | ||
// required to not interfere with serve-static | ||
delete userOptions.index; | ||
if (typeof (userOptions.enableBrotli) !== "undefined") { | ||
sanitizedOptions.enableBrotli = !!userOptions.enableBrotli; | ||
} | ||
if (typeof (userOptions.enableBrotli) !== "undefined") { | ||
options.enableBrotli = !!userOptions.enableBrotli; | ||
if (typeof (userOptions.customCompressions) === "object") { | ||
sanitizedOptions.customCompressions = userOptions.customCompressions; | ||
} | ||
if (typeof (userOptions.customCompressions) === "object" ) { | ||
options.customCompressions = userOptions.customCompressions; | ||
if (typeof (userOptions.orderPreference) === "object") { | ||
sanitizedOptions.orderPreference = userOptions.orderPreference; | ||
} | ||
if (typeof (userOptions.orderPreference) === "object" ) { | ||
options.orderPreference = userOptions.orderPreference; | ||
prepareServeStaticOptions(userOptions, sanitizedOptions); | ||
return sanitizedOptions; | ||
} | ||
/** | ||
* | ||
* @param {expressStaticGzip.ExpressStaticGzipOptions} userOptions | ||
* @param {expressStaticGzip.ExpressStaticGzipOptions} sanitizedOptions | ||
*/ | ||
function prepareServeStaticOptions(userOptions, sanitizedOptions) { | ||
if (typeof (userOptions.serveStatic) !== 'undefined') { | ||
sanitizedOptions.serveStatic = userOptions.serveStatic; | ||
} | ||
return options; | ||
copyServeStaticOptions(userOptions, sanitizedOptions); | ||
} | ||
function getIndexValue(opts) { | ||
if (typeof (opts.indexFromEmptyFile) === "undefined" && typeof (opts.index) !== "undefined") { | ||
return opts.index; | ||
} else if (typeof (opts.index) === "undefined" && typeof (opts.indexFromEmptyFile) !== "undefined") { | ||
return opts.indexFromEmptyFile; | ||
} else { | ||
return true; | ||
/** | ||
* Used to be backwards compatible by copying options in root level to the serveStatic options property. | ||
* @param {expressStaticGzip.ExpressStaticGzipOptions} userOptions | ||
* @param {expressStaticGzip.ExpressStaticGzipOptions} sanitizedOptions | ||
*/ | ||
function copyServeStaticOptions(userOptions, sanitizedOptions) { | ||
var staticGzipOptionsProperties = ['cacheControl', 'dotfiles', 'etag', 'extensions', 'index', 'fallthrough', 'immutable', 'lastModified', 'maxAge', 'redirect', 'setHeaders']; | ||
for (var propertyIdx in staticGzipOptionsProperties) { | ||
var property = staticGzipOptionsProperties[propertyIdx]; | ||
if (typeof (userOptions[property]) !== 'undefined' && (!sanitizedOptions.serveStatic || typeof (sanitizedOptions.serveStatic[property]) === 'undefined')) { | ||
setStaticGzipOptionsProperty(sanitizedOptions, property, userOptions[property]); | ||
} | ||
} | ||
} | ||
module.exports = { | ||
sanitizeOptions: sanitizeOptions | ||
}; | ||
/** | ||
* | ||
* @param {expressStaticGzip.ExpressStaticGzipOptions} sanitizedOptions | ||
* @param {string} property | ||
* @param {any} value | ||
*/ | ||
function setStaticGzipOptionsProperty(sanitizedOptions, property, value) { | ||
if (typeof (sanitizedOptions.serveStatic) !== 'object') { | ||
sanitizedOptions.serveStatic = {}; | ||
} | ||
sanitizedOptions.serveStatic[property] = value; | ||
} | ||
/** | ||
* Takes care of retrieving the index value, by also checking the deprecated `indexFromEmptyFile` | ||
* @param {expressStaticGzip.ExpressStaticGzipOptions} options | ||
*/ | ||
function getIndexValue(options) { | ||
if (typeof (options.indexFromEmptyFile) === "undefined" && typeof (options.index) !== "undefined") { | ||
return options.index; | ||
} else if (typeof (options.index) === "undefined" && typeof (options.indexFromEmptyFile) !== "undefined") { | ||
return options.indexFromEmptyFile; | ||
} else { | ||
return 'index.html'; | ||
} | ||
} |
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
40042
29.66%20
5.26%707
24.25%142
31.48%Updated