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: <Buffer 1f 8b 08...>}
+    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')