Skip to content

Commit 6e31cb2

Browse files
committed
feat(web-server): use SHA hash instead of timestamps
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)
1 parent c786ee2 commit 6e31cb2

File tree

4 files changed

+56
-30
lines changed

4 files changed

+56
-30
lines changed

lib/middleware/karma.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ var createKarmaMiddleware = function(filesPromise, serveStaticFile,
8383
filePath = filePathToUrlPath(filePath, basePath);
8484

8585
if (requestUrl === '/context.html') {
86-
filePath += '?' + file.mtime.getTime();
86+
filePath += '?' + file.sha;
8787
}
8888
}
8989

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

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

104104
mappings = 'window.__karma__.files = {\n' + mappings.join(',\n') + '\n};\n';

lib/preprocessor.js

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,13 +53,15 @@ var createPreprocessor = function(config, basePath, injector) {
5353
}
5454
}
5555

56-
if (preprocessors.length) {
57-
return fs.readFile(file.originalPath, function(err, buffer) {
58-
nextPreprocessor(buffer.toString());
59-
});
60-
}
56+
// compute SHA of the content
57+
preprocessors.push(function(content, file, done) {
58+
file.sha = sha1(content);
59+
done(content);
60+
});
6161

62-
return process.nextTick(done);
62+
return fs.readFile(file.originalPath, function(err, buffer) {
63+
nextPreprocessor(buffer.toString());
64+
});
6365
};
6466
};
6567
createPreprocessor.$inject = ['config.preprocessors', 'config.basePath', 'injector'];

test/unit/middleware/karma.spec.coffee

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ describe 'middleware.karma', ->
99
File = require('../../../lib/file-list').File
1010
Url = require('../../../lib/file-list').Url
1111

12+
MockFile = (path, sha) ->
13+
File.call @, path
14+
@sha = sha or 'sha-default'
15+
1216
fsMock = mocks.fs.create
1317
karma:
1418
static:
@@ -101,61 +105,61 @@ describe 'middleware.karma', ->
101105

102106
it 'should serve context.html with replaced script tags', (done) ->
103107
includedFiles [
104-
new File('/first.js', new Date 12345)
105-
new File('/second.dart', new Date 67890)
108+
new MockFile('/first.js', 'sha123')
109+
new MockFile('/second.dart', 'sha456')
106110
]
107111

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

115119
callHandlerWith '/__karma__/context.html'
116120

117121

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

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

129133
callHandlerWith '/__karma__/context.html'
130134

131135

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

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

145149
callHandlerWith '/__karma__/context.html'
146150

147151

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

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

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

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

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

210214
it 'should serve debug.html with replaced script tags without timestamps', (done) ->
211215
includedFiles [
212-
new File('/first.js', new Date 12345)
213-
new File('/base/path/b.js', new Date 67890)
216+
new MockFile('/first.js')
217+
new MockFile('/base/path/b.js')
214218
]
215219

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

226230
it 'should serve debug.html with replaced link tags without timestamps', (done) ->
227231
includedFiles [
228-
new File('/first.css', new Date 12345)
229-
new File('/base/path/b.css', new Date 67890)
232+
new MockFile('/first.css')
233+
new MockFile('/base/path/b.css')
230234
]
231235

232236
response.once 'end', ->

test/unit/preprocessor.spec.coffee

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ describe 'preprocessor', ->
1111
mockFs = mocks.fs.create
1212
some:
1313
'a.js': mocks.fs.file 0, 'content'
14+
'a.txt': mocks.fs.file 0, 'some-text'
1415

1516
mocks_ =
1617
'graceful-fs': mockFs
@@ -74,3 +75,22 @@ describe 'preprocessor', ->
7475
expect(file.path).to.equal 'path-p1-p2'
7576
expect(file.content).to.equal 'content-c1-c2'
7677
done()
78+
79+
80+
it 'should compute SHA', (done) ->
81+
pp = m.createPreprocessor {}, null, new di.Injector([])
82+
file = {originalPath: '/some/a.js', path: 'path'}
83+
84+
pp file, ->
85+
expect(file.sha).to.exist
86+
expect(file.sha.length).to.equal 40
87+
previousSHA = file.sha
88+
89+
pp file, ->
90+
expect(file.sha).to.equal previousSHA
91+
mockFs._touchFile '/some/a.js', null, 'new-content'
92+
93+
pp file, ->
94+
expect(file.sha.length).to.equal 40
95+
expect(file.sha).not.to.equal previousSHA
96+
done()

0 commit comments

Comments
 (0)