From 68b37d3c8909a9c999d40fe20543bffe33f1e096 Mon Sep 17 00:00:00 2001 From: Jonathan Felchlin Date: Thu, 6 Sep 2018 09:07:53 -0700 Subject: [PATCH] feat(server): Add support for encoded source files (#3123) --- docs/dev/05-plugins.md | 11 +++++++ lib/file.js | 4 +++ lib/middleware/source_files.js | 11 ++++++- test/unit/middleware/source_files.spec.js | 36 +++++++++++++++++++++++ 4 files changed, 61 insertions(+), 1 deletion(-) diff --git a/docs/dev/05-plugins.md b/docs/dev/05-plugins.md index 477fe607d..478b7303e 100644 --- a/docs/dev/05-plugins.md +++ b/docs/dev/05-plugins.md @@ -18,6 +18,17 @@ Karma can be extended through plugins. A plugin is essentially an NPM module. Ty - use NPM keywords `karma-plugin`, `karma-launcher` ## Preprocessors + +A preprocessor is a function that accepts three arguments (`content`, `file`, and `next`), mutates the content in some way, and passes it on to the next preprocessor. + +- arguments passed to preprocessor plugins: + - **`content`** of the file being processed + - **`file`** object describing the file being processed + - **path:** the current file, mutable file path. e. g. `some/file.coffee` -> `some/file.coffee.js` _This path is mutable and may not actually exist._ + - **originalPath:** the original, unmutated path + - **encodings:** A mutable, keyed object where the keys are a valid encoding type ('gzip', 'compress', 'br', etc.) and the values are the encoded content. Encoded content should be stored here and not resolved using `next(null, encodedContent)` + - **type:** the pattern used to match the file + - **`next`** function to be called when preprocessing is complete, should be called as `next(null, processedContent)` or `next(error)` - example plugins: [karma-coffee-preprocessor], [karma-ng-html2js-preprocessor] - use naming convention is `karma-*-preprocessor` - user NPM keywords `karma-plugin`, `karma-preprocessor` diff --git a/lib/file.js b/lib/file.js index 094df9e0e..8689db103 100644 --- a/lib/file.js +++ b/lib/file.js @@ -14,6 +14,10 @@ class File { // where the content is stored (processed) this.contentPath = path + // encodings format {[encodingType]: encodedContent} + // example: {gzip: } + this.encodings = Object.create(null) + this.mtime = mtime this.isUrl = false diff --git a/lib/middleware/source_files.js b/lib/middleware/source_files.js index 5fd236ccd..efe1f4a29 100644 --- a/lib/middleware/source_files.js +++ b/lib/middleware/source_files.js @@ -35,13 +35,22 @@ function createSourceFilesMiddleware (filesPromise, serveFile, basePath, urlRoot const rangeHeader = request.headers['range'] if (file) { + const acceptEncodingHeader = request.headers['accept-encoding'] + const matchedEncoding = Object.keys(file.encodings).find( + (encoding) => new RegExp(`(^|.*, ?)${encoding}(,|$)`).test(acceptEncodingHeader) + ) + const content = file.encodings[matchedEncoding] || file.content + serveFile(file.contentPath || file.path, rangeHeader, response, function () { if (/\?\w+/.test(request.url)) { common.setHeavyCacheHeaders(response) // files with timestamps - cache one year, rely on timestamps } else { common.setNoCacheHeaders(response) // without timestamps - no cache (debug) } - }, file.content, file.doNotCache) + if (matchedEncoding) { + response.setHeader('Content-Encoding', matchedEncoding) + } + }, content, file.doNotCache) } else { next() } diff --git a/test/unit/middleware/source_files.spec.js b/test/unit/middleware/source_files.spec.js index 540e9cf0b..64d481298 100644 --- a/test/unit/middleware/source_files.spec.js +++ b/test/unit/middleware/source_files.spec.js @@ -1,6 +1,7 @@ var http = require('http') var mocks = require('mocks') var request = require('supertest') +var zlib = require('zlib') var helper = require('../../../lib/helper') var File = require('../../../lib/file') @@ -109,6 +110,41 @@ describe('middleware.source_files', function () { }) }) + describe('file encoding', function () { + let file + beforeEach(function () { + file = new File('/src/some.js') + servedFiles([ + file + ]) + }) + + it('serves encoded files', function () { + file.encodings.gzip = zlib.gzipSync('gzipped-js-source') + return request(server) + .get('/absolute/src/some.js') + .set('Accept-Encoding', 'gzip, deflate') + .expect(200, 'gzipped-js-source') + .expect('Content-Encoding', 'gzip') + .expect('Content-Type', 'application/javascript') + }) + + it('serves unencoded files when request does not accept available encodings', function (done) { + file.encodings.gzip = zlib.gzipSync('gzipped-js-source') + request(server) + .get('/absolute/src/some.js') + .set('Accept-Encoding', 'gzippy, deflate') + .expect(200, 'js-source') + .end((error, res) => { + if (error) { + return done(error) + } + expect(res.headers).to.not.have.property('content-encoding') + return done() + }) + }) + }) + it('should serve absolute js source files ignoring timestamp', function () { servedFiles([ new File('/src/some.js')