Skip to content

Commit

Permalink
feat(web-server): use SHA hash instead of timestamps
Browse files Browse the repository at this point in the history
Heavy caching relies on SHA of the content rather than mtime timestamps.

Benefits:
- touching a file (without changing content) does not re-fetch the file
- reverting a file does not re-fetch the file (browser will use the previous cache)
- changing preprocessor configuration does invalidate cache (Closes #520)
  • Loading branch information
vojtajina committed Dec 1, 2013
1 parent c786ee2 commit 6e31cb2
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 30 deletions.
4 changes: 2 additions & 2 deletions lib/middleware/karma.js
Expand Up @@ -83,7 +83,7 @@ var createKarmaMiddleware = function(filesPromise, serveStaticFile,
filePath = filePathToUrlPath(filePath, basePath);

if (requestUrl === '/context.html') {
filePath += '?' + file.mtime.getTime();
filePath += '?' + file.sha;
}
}

Expand All @@ -98,7 +98,7 @@ var createKarmaMiddleware = function(filesPromise, serveStaticFile,
var mappings = files.served.map(function(file) {
var filePath = filePathToUrlPath(file.path, basePath);

return util.format(' \'%s\': \'%d\'', filePath, file.mtime.getTime());
return util.format(' \'%s\': \'%s\'', filePath, file.sha);
});

mappings = 'window.__karma__.files = {\n' + mappings.join(',\n') + '\n};\n';
Expand Down
14 changes: 8 additions & 6 deletions lib/preprocessor.js
Expand Up @@ -53,13 +53,15 @@ var createPreprocessor = function(config, basePath, injector) {
}
}

if (preprocessors.length) {
return fs.readFile(file.originalPath, function(err, buffer) {
nextPreprocessor(buffer.toString());
});
}
// compute SHA of the content
preprocessors.push(function(content, file, done) {
file.sha = sha1(content);
done(content);
});

return process.nextTick(done);
return fs.readFile(file.originalPath, function(err, buffer) {
nextPreprocessor(buffer.toString());
});
};
};
createPreprocessor.$inject = ['config.preprocessors', 'config.basePath', 'injector'];
Expand Down
48 changes: 26 additions & 22 deletions test/unit/middleware/karma.spec.coffee
Expand Up @@ -9,6 +9,10 @@ describe 'middleware.karma', ->
File = require('../../../lib/file-list').File
Url = require('../../../lib/file-list').Url

MockFile = (path, sha) ->
File.call @, path
@sha = sha or 'sha-default'

fsMock = mocks.fs.create
karma:
static:
Expand Down Expand Up @@ -101,61 +105,61 @@ describe 'middleware.karma', ->

it 'should serve context.html with replaced script tags', (done) ->
includedFiles [
new File('/first.js', new Date 12345)
new File('/second.dart', new Date 67890)
new MockFile('/first.js', 'sha123')
new MockFile('/second.dart', 'sha456')
]

response.once 'end', ->
expect(nextSpy).not.to.have.been.called
expect(response).to.beServedAs 200, 'CONTEXT\n' +
'<script type="text/javascript" src="/absolute/first.js?12345"></script>\n' +
'<script type="application/dart" src="/absolute/second.dart?67890"></script>'
'<script type="text/javascript" src="/absolute/first.js?sha123"></script>\n' +
'<script type="application/dart" src="/absolute/second.dart?sha456"></script>'
done()

callHandlerWith '/__karma__/context.html'


it 'should serve context.html with replaced link tags', (done) ->
includedFiles [
new File('/first.css', new Date 12345)
new MockFile('/first.css', 'sha007')
]

response.once 'end', ->
expect(nextSpy).not.to.have.been.called
expect(response).to.beServedAs 200, 'CONTEXT\n' +
'<link type="text/css" href="/absolute/first.css?12345" rel="stylesheet">'
'<link type="text/css" href="/absolute/first.css?sha007" rel="stylesheet">'
done()

callHandlerWith '/__karma__/context.html'


it 'should serve context.html with the correct path for the script tags', (done) ->
includedFiles [
new File('/some/abc/a.js', new Date 12345)
new File('/base/path/b.js', new Date 67890)
new MockFile('/some/abc/a.js', 'sha')
new MockFile('/base/path/b.js', 'shaaa')
]

response.once 'end', ->
expect(nextSpy).not.to.have.been.called
expect(response).to.beServedAs 200, 'CONTEXT\n' +
'<script type="text/javascript" src="/absolute/some/abc/a.js?12345"></script>\n' +
'<script type="text/javascript" src="/base/b.js?67890"></script>'
'<script type="text/javascript" src="/absolute/some/abc/a.js?sha"></script>\n' +
'<script type="text/javascript" src="/base/b.js?shaaa"></script>'
done()

callHandlerWith '/__karma__/context.html'


it 'should serve context.html with the correct path for link tags', (done) ->
includedFiles [
new File('/some/abc/a.css', new Date 12345)
new File('/base/path/b.css', new Date 67890)
new MockFile('/some/abc/a.css', 'sha1')
new MockFile('/base/path/b.css', 'sha2')
]

response.once 'end', ->
expect(nextSpy).not.to.have.been.called
expect(response).to.beServedAs 200, 'CONTEXT\n' +
'<link type="text/css" href="/absolute/some/abc/a.css?12345" rel="stylesheet">\n' +
'<link type="text/css" href="/base/b.css?67890" rel="stylesheet">'
'<link type="text/css" href="/absolute/some/abc/a.css?sha1" rel="stylesheet">\n' +
'<link type="text/css" href="/base/b.css?sha2" rel="stylesheet">'
done()

callHandlerWith '/__karma__/context.html'
Expand Down Expand Up @@ -193,14 +197,14 @@ describe 'middleware.karma', ->
it 'should inline mappings with all served files', (done) ->
fsMock._touchFile '/karma/static/context.html', 0, '%MAPPINGS%'
servedFiles [
new File('/some/abc/a.js', new Date 12345)
new File('/base/path/b.js', new Date 67890)
new MockFile('/some/abc/a.js', 'sha_a')
new MockFile('/base/path/b.js', 'sha_b')
]

response.once 'end', ->
expect(response).to.beServedAs 200, 'window.__karma__.files = {\n' +
" '/absolute/some/abc/a.js': '12345',\n" +
" '/base/b.js': '67890'\n" +
" '/absolute/some/abc/a.js': 'sha_a',\n" +
" '/base/b.js': 'sha_b'\n" +
"};\n"
done()

Expand All @@ -209,8 +213,8 @@ describe 'middleware.karma', ->

it 'should serve debug.html with replaced script tags without timestamps', (done) ->
includedFiles [
new File('/first.js', new Date 12345)
new File('/base/path/b.js', new Date 67890)
new MockFile('/first.js')
new MockFile('/base/path/b.js')
]

response.once 'end', ->
Expand All @@ -225,8 +229,8 @@ describe 'middleware.karma', ->

it 'should serve debug.html with replaced link tags without timestamps', (done) ->
includedFiles [
new File('/first.css', new Date 12345)
new File('/base/path/b.css', new Date 67890)
new MockFile('/first.css')
new MockFile('/base/path/b.css')
]

response.once 'end', ->
Expand Down
20 changes: 20 additions & 0 deletions test/unit/preprocessor.spec.coffee
Expand Up @@ -11,6 +11,7 @@ describe 'preprocessor', ->
mockFs = mocks.fs.create
some:
'a.js': mocks.fs.file 0, 'content'
'a.txt': mocks.fs.file 0, 'some-text'

mocks_ =
'graceful-fs': mockFs
Expand Down Expand Up @@ -74,3 +75,22 @@ describe 'preprocessor', ->
expect(file.path).to.equal 'path-p1-p2'
expect(file.content).to.equal 'content-c1-c2'
done()


it 'should compute SHA', (done) ->
pp = m.createPreprocessor {}, null, new di.Injector([])
file = {originalPath: '/some/a.js', path: 'path'}

pp file, ->
expect(file.sha).to.exist
expect(file.sha.length).to.equal 40
previousSHA = file.sha

pp file, ->
expect(file.sha).to.equal previousSHA
mockFs._touchFile '/some/a.js', null, 'new-content'

pp file, ->
expect(file.sha.length).to.equal 40
expect(file.sha).not.to.equal previousSHA
done()

0 comments on commit 6e31cb2

Please sign in to comment.