Skip to content

Commit 2a88c7a

Browse files
authored
Merge pull request #351 from serverless-heaven/fix-local-invoke-watch
Purge handler module from require cache on watch
2 parents ad24e5c + 810e27a commit 2a88c7a

File tree

3 files changed

+136
-18
lines changed

3 files changed

+136
-18
lines changed

lib/prepareLocalInvoke.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
'use strict';
22

3-
const BbPromise = require('bluebird');
43
const path = require('path');
54
const _ = require('lodash');
5+
const Utils = require('./utils');
66

77
module.exports = {
88
prepareLocalInvoke() {
@@ -23,6 +23,13 @@ module.exports = {
2323
// Set service path as CWD to allow accessing bundled files correctly
2424
process.chdir(this.serverless.config.servicePath);
2525

26-
return BbPromise.resolve();
26+
// Remove handler from module cache to allow load of changed code.
27+
const func = this.serverless.service.getFunction(this.options.function);
28+
const handlerFile = path.join(
29+
this.serverless.config.servicePath,
30+
this.options.extraServicePath || '',
31+
_.join(_.initial(_.split(func.handler, '.')), '.')
32+
);
33+
return Utils.purgeCache(handlerFile);
2734
}
2835
};

lib/prepareLocalInvoke.test.js

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
'use strict';
2+
3+
const _ = require('lodash');
4+
const BbPromise = require('bluebird');
5+
const chai = require('chai');
6+
const sinon = require('sinon');
7+
const Serverless = require('serverless');
8+
const path = require('path');
9+
const Utils = require('./utils');
10+
11+
chai.use(require('chai-as-promised'));
12+
chai.use(require('sinon-chai'));
13+
14+
const expect = chai.expect;
15+
16+
describe('prepareLocalInvoke', () => {
17+
let serverless;
18+
let baseModule;
19+
let module;
20+
let sandbox;
21+
22+
before(() => {
23+
sandbox = sinon.createSandbox();
24+
sandbox.usingPromise(BbPromise.Promise);
25+
26+
baseModule = require('./prepareLocalInvoke');
27+
Object.freeze(baseModule);
28+
});
29+
30+
beforeEach(() => {
31+
serverless = new Serverless();
32+
serverless.cli = {
33+
log: sandbox.stub()
34+
};
35+
_.set(serverless, 'config.serverless.processedInput.options', {
36+
path: './event.json'
37+
});
38+
39+
sandbox.stub(Utils, 'purgeCache');
40+
sandbox.stub(process, 'chdir');
41+
sandbox.stub(serverless.service, 'getFunction');
42+
43+
module = _.assign({
44+
serverless,
45+
options: {},
46+
}, baseModule);
47+
});
48+
49+
afterEach(() => {
50+
sandbox.restore();
51+
});
52+
53+
it('should store original service path', () => {
54+
module.serverless.service.package = {};
55+
module.serverless.service.getFunction.returns({ handler: 'myFuncHandler' });
56+
module.webpackOutputPath = '.';
57+
module.serverless.config.servicePath = './servicePath';
58+
module.prepareLocalInvoke();
59+
60+
expect(module.originalServicePath).to.equal('./servicePath');
61+
});
62+
63+
it('should use the function folder as cwd', () => {
64+
module.serverless.service.package = {
65+
individually: true
66+
};
67+
module.options.function = 'myFunc';
68+
module.serverless.service.getFunction.returns({ handler: 'myFuncHandler' });
69+
module.webpackOutputPath = '.webpack';
70+
module.serverless.config.servicePath = './servicePath';
71+
module.prepareLocalInvoke();
72+
73+
expect(process.chdir).to.have.been.calledWithExactly(path.join('.webpack', 'myFunc'));
74+
});
75+
76+
it('should use the service folder as cwd', () => {
77+
module.serverless.service.package = {};
78+
module.options.function = 'myFunc';
79+
module.serverless.service.getFunction.returns({ handler: 'myFuncHandler' });
80+
module.webpackOutputPath = '.webpack';
81+
module.serverless.config.servicePath = './servicePath';
82+
module.prepareLocalInvoke();
83+
84+
expect(process.chdir).to.have.been.calledWithExactly(path.join('.webpack', 'service'));
85+
});
86+
87+
it('should work without path option', () => {
88+
module.serverless.service.package = {};
89+
module.serverless.service.getFunction.returns({ handler: 'myFuncHandler' });
90+
module.webpackOutputPath = '.';
91+
module.serverless.config.servicePath = './servicePath';
92+
_.unset(module, 'serverless.config.serverless.processedInput.options.path');
93+
module.prepareLocalInvoke();
94+
95+
expect(module.originalServicePath).to.equal('./servicePath');
96+
});
97+
});

lib/utils.js

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use strict';
22

33
const _ = require('lodash');
4+
const BbPromise = require('bluebird');
45

56
function guid() {
67
function s4() {
@@ -11,31 +12,44 @@ function guid() {
1112
return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
1213
}
1314

15+
/**
16+
* Remove the specified module from the require cache.
17+
* @param {string} moduleName
18+
*/
1419
function purgeCache(moduleName) {
15-
searchCache(moduleName, function (mod) {
20+
return searchAndProcessCache(moduleName, function (mod) {
1621
delete require.cache[mod.id];
17-
});
18-
_.forEach(_.keys(module.constructor._pathCache), function(cacheKey) {
19-
if (cacheKey.indexOf(moduleName)>0) {
20-
delete module.constructor._pathCache[cacheKey];
21-
}
22+
})
23+
.then(() => {
24+
_.forEach(_.keys(module.constructor._pathCache), function(cacheKey) {
25+
if (cacheKey.indexOf(moduleName)>0) {
26+
delete module.constructor._pathCache[cacheKey];
27+
}
28+
});
29+
return BbPromise.resolve();
2230
});
2331
}
2432

25-
function searchCache(moduleName, callback) {
26-
let mod = require.resolve(moduleName);
27-
if (mod && ((mod = require.cache[mod]) !== undefined)) {
28-
(function traverse(mod) {
29-
_.forEach(mod.children, function (child) {
30-
traverse(child);
31-
});
32-
callback(mod);
33-
}(mod));
33+
function searchAndProcessCache(moduleName, processor) {
34+
let mod_src = require.resolve(moduleName);
35+
const visitedModules = [];
36+
if (mod_src && ((mod_src = require.cache[mod_src]) !== undefined)) {
37+
const modStack = [mod_src];
38+
39+
while (!_.isEmpty(modStack)) {
40+
const mod = modStack.pop();
41+
if (!_.includes(visitedModules, mod)) {
42+
visitedModules.push(mod);
43+
Array.prototype.push.apply(modStack, mod.children);
44+
processor(mod);
45+
}
46+
}
3447
}
48+
return BbPromise.resolve();
3549
}
3650

3751
module.exports = {
3852
guid,
3953
purgeCache,
40-
searchCache,
54+
searchAndProcessCache,
4155
};

0 commit comments

Comments
 (0)