Skip to content

Commit c5f3560

Browse files
authored
feat(preprocessor): preprocessor_priority execution order. (#3303)
Between 3.x and 4.x we moved from combineLists to underscore.union in preprocessor. Apparently combineLists was incorrect: the order of preprocessing changed in some cases. Conceptually the order ought to depend upon the preprocessor, not the file. Implement config.preprocessor_priority['preprocessor-name'] = priority, higher means run earlier. Default priority is 0. Add back compat API for karma-browserify. Update docs.
1 parent 6c5add2 commit c5f3560

File tree

5 files changed

+89
-27
lines changed

5 files changed

+89
-27
lines changed

docs/config/04-preprocessors.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,9 @@ preprocessors: {
108108

109109
Then karma will execute `'a'` before executing `'b'`.
110110

111-
If a file matches multiple keys, karma will do its best to execute the
112-
preprocessors in a reasonable order. So if you have:
111+
If a file matches multiple keys, karma will use the `config.preprocessor_priority`
112+
map to set the order. If this config option is not set, karma do its best to
113+
execute the preprocessors in a reasonable order. So if you have:
113114

114115
```js
115116
preprocessors: {

lib/config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,7 @@ class Config {
302302
this.proxies = {}
303303
this.proxyValidateSSL = true
304304
this.preprocessors = {}
305+
this.preprocessor_priority = {}
305306
this.urlRoot = '/'
306307
this.upstreamProxy = undefined
307308
this.reportSlowerThan = 0

lib/preprocessor.js

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ function createNextProcessor (preprocessors, file, done) {
3333
}
3434
}
3535

36-
function createPreprocessor (config, basePath, injector) {
36+
function createPriorityPreprocessor (config, preprocessorPriority, basePath, injector) {
3737
const emitter = injector.get('emitter')
3838
const alreadyDisplayedErrors = {}
3939
const instances = {}
@@ -96,9 +96,15 @@ function createPreprocessor (config, basePath, injector) {
9696
}
9797
})
9898

99+
// Apply preprocessor priority.
100+
let sortedPreprocessorNames = preprocessorNames
101+
.map((name) => [name, preprocessorPriority[name] || 0])
102+
.sort((a, b) => b[1] - a[1])
103+
.map((duo) => duo[0])
104+
99105
let preprocessors = []
100106
const nextPreprocessor = createNextProcessor(preprocessors, file, done)
101-
preprocessorNames.forEach((name) => {
107+
sortedPreprocessorNames.forEach((name) => {
102108
const p = instances[name] || instantiatePreprocessor(name)
103109

104110
if (p == null) {
@@ -125,6 +131,14 @@ function createPreprocessor (config, basePath, injector) {
125131
}
126132
}
127133

134+
// Deprecated API
135+
function createPreprocessor (preprocessors, basePath, injector) {
136+
console.log('Deprecated private createPreprocessor() API')
137+
const preprocessorPriority = injector.get('config.preprocessor_priority')
138+
return createPriorityPreprocessor(preprocessors, preprocessorPriority, basePath, injector)
139+
}
128140
createPreprocessor.$inject = ['config.preprocessors', 'config.basePath', 'injector']
129-
130141
exports.createPreprocessor = createPreprocessor
142+
143+
createPriorityPreprocessor.$inject = ['config.preprocessors', 'config.preprocessor_priority', 'config.basePath', 'injector']
144+
exports.createPriorityPreprocessor = createPriorityPreprocessor

lib/server.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ class Server extends KarmaEventEmitter {
7575
watcher: ['value', watcher],
7676
launcher: ['type', Launcher],
7777
config: ['value', config],
78-
preprocess: ['factory', preprocessor.createPreprocessor],
78+
preprocess: ['factory', preprocessor.createPriorityPreprocessor],
7979
fileList: ['factory', FileList.factory],
8080
webServer: ['factory', createWebServer],
8181
serveFile: ['factory', createServeFile],

test/unit/preprocessor.spec.js

Lines changed: 67 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ describe('preprocessor', () => {
4747
'factory', function () { return fakePreprocessor }
4848
]
4949
}, emitterSetting])
50-
pp = m.createPreprocessor({ '**/*.js': ['fake'] }, null, injector)
50+
pp = m.createPriorityPreprocessor({ '**/*.js': ['fake'] }, {}, null, injector)
5151

5252
const file = { originalPath: '/some/a.js', path: 'path' }
5353

@@ -68,7 +68,7 @@ describe('preprocessor', () => {
6868
const injector = new di.Injector([{
6969
'preprocessor:fake': ['factory', function () { return fakePreprocessor }]
7070
}, emitterSetting])
71-
pp = m.createPreprocessor({ '**/*.js': ['fake'] }, null, injector)
71+
pp = m.createPriorityPreprocessor({ '**/*.js': ['fake'] }, {}, null, injector)
7272

7373
const file = { originalPath: '/some/.dir/a.js', path: 'path' }
7474

@@ -90,7 +90,7 @@ describe('preprocessor', () => {
9090
'preprocessor:fake': ['factory', function () { return fakePreprocessor }]
9191
}, emitterSetting])
9292
const config = { '**/*.txt': ['fake'] }
93-
pp = m.createPreprocessor(config, null, injector)
93+
pp = m.createPriorityPreprocessor(config, {}, null, injector)
9494

9595
const file = { originalPath: '/some/a.js', path: 'path' }
9696

@@ -112,7 +112,7 @@ describe('preprocessor', () => {
112112
const injector = new di.Injector([{
113113
'preprocessor:fake': ['factory', function () { return fakePreprocessor }]
114114
}, emitterSetting])
115-
pp = m.createPreprocessor({ '**/*.js': ['fake'] }, null, injector)
115+
pp = m.createPriorityPreprocessor({ '**/*.js': ['fake'] }, {}, null, injector)
116116

117117
const file = { originalPath: '/some/a.txt', path: 'path' }
118118

@@ -138,7 +138,7 @@ describe('preprocessor', () => {
138138
'preprocessor:fake2': ['factory', function () { return fakePreprocessor2 }]
139139
}, emitterSetting])
140140

141-
pp = m.createPreprocessor({ '**/*.js': ['fake1', 'fake2'] }, null, injector)
141+
pp = m.createPriorityPreprocessor({ '**/*.js': ['fake1', 'fake2'] }, {}, null, injector)
142142

143143
const file = { originalPath: '/some/a.js', path: 'path' }
144144

@@ -152,7 +152,7 @@ describe('preprocessor', () => {
152152
})
153153

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

158158
pp(file, () => {
@@ -182,7 +182,7 @@ describe('preprocessor', () => {
182182
'preprocessor:fake': ['factory', function () { return fakePreprocessor }]
183183
}, emitterSetting])
184184

185-
pp = m.createPreprocessor({ '**/a.js': ['fake'] }, null, injector)
185+
pp = m.createPriorityPreprocessor({ '**/a.js': ['fake'] }, {}, null, injector)
186186

187187
const fileProcess = { originalPath: '/some/a.js', path: 'path' }
188188
const fileSkip = { originalPath: '/some/b.js', path: 'path' }
@@ -208,7 +208,7 @@ describe('preprocessor', () => {
208208
'preprocessor:failing': ['factory', function () { return failingPreprocessor }]
209209
}, emitterSetting])
210210

211-
pp = m.createPreprocessor({ '**/*.js': ['failing'] }, null, injector)
211+
pp = m.createPriorityPreprocessor({ '**/*.js': ['failing'] }, {}, null, injector)
212212

213213
const file = { originalPath: '/some/a.js', path: 'path' }
214214

@@ -232,7 +232,7 @@ describe('preprocessor', () => {
232232
'preprocessor:fake': ['factory', function () { return fakePreprocessor }]
233233
}, emitterSetting])
234234

235-
pp = m.createPreprocessor({ '**/*.js': ['failing', 'fake'] }, null, injector)
235+
pp = m.createPriorityPreprocessor({ '**/*.js': ['failing', 'fake'] }, {}, null, injector)
236236

237237
const file = { originalPath: '/some/a.js', path: 'path' }
238238

@@ -261,7 +261,7 @@ describe('preprocessor', () => {
261261
'preprocessor:fake': ['factory', function () { return fakePreprocessor }]
262262
}, emitterSetting])
263263

264-
const pp = m.createPreprocessor({ '**/*.js': ['fake'] }, null, injector)
264+
const pp = m.createPriorityPreprocessor({ '**/*.js': ['fake'] }, {}, null, injector)
265265

266266
pp(file, () => {
267267
expect(fakePreprocessor).to.have.been.called
@@ -277,7 +277,7 @@ describe('preprocessor', () => {
277277
it('should throw after 3 retries', (done) => {
278278
const injector = new di.Injector([{}, emitterSetting])
279279

280-
const pp = m.createPreprocessor({ '**/*.js': [] }, null, injector)
280+
const pp = m.createPriorityPreprocessor({ '**/*.js': [] }, {}, null, injector)
281281

282282
pp(file, () => { })
283283

@@ -299,7 +299,7 @@ describe('preprocessor', () => {
299299
'preprocessor:fake': ['factory', function () { return fakePreprocessor }]
300300
}, emitterSetting])
301301

302-
pp = m.createPreprocessor({ '**/*': ['fake'] }, null, injector)
302+
pp = m.createPriorityPreprocessor({ '**/*': ['fake'] }, {}, null, injector)
303303

304304
const file = { originalPath: '/some/photo.png', path: 'path' }
305305

@@ -322,7 +322,7 @@ describe('preprocessor', () => {
322322
'preprocessor:fake': ['factory', function () { return fakePreprocessor }]
323323
}, emitterSetting])
324324

325-
pp = m.createPreprocessor({ '**/*': ['fake'] }, null, injector)
325+
pp = m.createPriorityPreprocessor({ '**/*': ['fake'] }, {}, null, injector)
326326

327327
const file = { originalPath: '/some/photo.png', path: 'path' }
328328

@@ -344,7 +344,7 @@ describe('preprocessor', () => {
344344
'preprocessor:fake': ['factory', function () { fakePreprocessor }]
345345
}, emitterSetting])
346346

347-
pp = m.createPreprocessor({ '**/*': ['fake'] }, null, injector)
347+
pp = m.createPriorityPreprocessor({ '**/*': ['fake'] }, {}, null, injector)
348348

349349
const file = { originalPath: '/some/CAM_PHOTO.JPG', path: 'path' }
350350

@@ -357,7 +357,7 @@ describe('preprocessor', () => {
357357
})
358358
})
359359

360-
it('should merge lists of preprocessors', (done) => {
360+
it('should merge lists of preprocessors using default priority', (done) => {
361361
const callOrder = []
362362
const fakePreprocessorA = sinon.spy((content, file, done) => {
363363
callOrder.push('a')
@@ -383,11 +383,11 @@ describe('preprocessor', () => {
383383
'preprocessor:fakeD': ['factory', function () { return fakePreprocessorD }]
384384
}, emitterSetting])
385385

386-
pp = m.createPreprocessor({
386+
pp = m.createPriorityPreprocessor({
387387
'/*/a.js': ['fakeA', 'fakeB'],
388388
'/some/*': ['fakeB', 'fakeC'],
389389
'/some/a.js': ['fakeD']
390-
}, null, injector)
390+
}, {}, null, injector)
391391

392392
const file = { originalPath: '/some/a.js', path: 'path' }
393393

@@ -399,10 +399,56 @@ describe('preprocessor', () => {
399399
expect(fakePreprocessorC).to.have.been.called
400400
expect(fakePreprocessorD).to.have.been.called
401401

402-
expect(callOrder.indexOf('d')).not.to.equal(-1)
403-
expect(callOrder.filter((letter) => {
404-
return letter !== 'd'
405-
})).to.eql(['a', 'b', 'c'])
402+
expect(callOrder).to.eql(['a', 'b', 'c', 'd'])
403+
done()
404+
})
405+
})
406+
407+
it('should merge lists of preprocessors obeying priority', (done) => {
408+
const callOrder = []
409+
const fakePreprocessorA = sinon.spy((content, file, done) => {
410+
callOrder.push('a')
411+
done(null, content)
412+
})
413+
const fakePreprocessorB = sinon.spy((content, file, done) => {
414+
callOrder.push('b')
415+
done(null, content)
416+
})
417+
const fakePreprocessorC = sinon.spy((content, file, done) => {
418+
callOrder.push('c')
419+
done(null, content)
420+
})
421+
const fakePreprocessorD = sinon.spy((content, file, done) => {
422+
callOrder.push('d')
423+
done(null, content)
424+
})
425+
426+
const injector = new di.Injector([{
427+
'preprocessor:fakeA': ['factory', function () { return fakePreprocessorA }],
428+
'preprocessor:fakeB': ['factory', function () { return fakePreprocessorB }],
429+
'preprocessor:fakeC': ['factory', function () { return fakePreprocessorC }],
430+
'preprocessor:fakeD': ['factory', function () { return fakePreprocessorD }]
431+
}, emitterSetting])
432+
433+
const priority = { 'fakeA': -1, 'fakeB': 1, 'fakeD': 100 }
434+
435+
pp = m.createPriorityPreprocessor({
436+
'/*/a.js': ['fakeA', 'fakeB'],
437+
'/some/*': ['fakeB', 'fakeC'],
438+
'/some/a.js': ['fakeD']
439+
}, priority, null, injector)
440+
441+
const file = { originalPath: '/some/a.js', path: 'path' }
442+
443+
pp(file, (err) => {
444+
if (err) throw err
445+
446+
expect(fakePreprocessorA).to.have.been.called
447+
expect(fakePreprocessorB).to.have.been.called
448+
expect(fakePreprocessorC).to.have.been.called
449+
expect(fakePreprocessorD).to.have.been.called
450+
451+
expect(callOrder).to.eql(['d', 'b', 'c', 'a'])
406452
done()
407453
})
408454
})

0 commit comments

Comments
 (0)