Skip to content

Commit 66cb262

Browse files
authored
Merge pull request #319 from dwelch2344/watch-compile-hook
#315 - Add watch-compile event
2 parents 34dc85c + 3524849 commit 66cb262

File tree

6 files changed

+81
-12
lines changed

6 files changed

+81
-12
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,7 @@ All events (H) can be hooked by a plugin.
473473
-> webpack:validate:validate (H)
474474
-> webpack:compile
475475
-> webpack:compile:compile (H)
476+
-> webpack:compile:watch:compile (H)
476477
-> webpack:package
477478
-> webpack:package:packExternalModules (H)
478479
-> webpack:package:packageModules (H)

index.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,14 @@ class ServerlessWebpack {
7171
lifecycleEvents: [
7272
'compile',
7373
],
74+
commands: {
75+
watch: {
76+
type: 'entrypoint',
77+
lifecycleEvents: [
78+
'compile'
79+
]
80+
}
81+
}
7482
},
7583
package: {
7684
type: 'entrypoint',
@@ -139,6 +147,8 @@ class ServerlessWebpack {
139147
'webpack:compile:compile': () => BbPromise.bind(this)
140148
.then(this.compile),
141149

150+
'webpack:compile:watch:compile': () => BbPromise.resolve(),
151+
142152
'webpack:package:packExternalModules': () => BbPromise.bind(this)
143153
.then(this.packExternalModules),
144154

lib/wpwatch.js

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,10 @@ module.exports = {
3131
};
3232

3333
// This starts the watch and waits for the immediate compile that follows to end or fail.
34+
let lastHash = null;
3435
const startWatch = (callback) => {
3536
let firstRun = true;
36-
compiler.watch(watchOptions, (err, stats) => {
37+
const watcher = compiler.watch(watchOptions, (err, stats) => {
3738
if (err) {
3839
if (firstRun) {
3940
firstRun = false;
@@ -42,15 +43,40 @@ module.exports = {
4243
throw err;
4344
}
4445

46+
process.env.SLS_DEBUG && this.serverless.cli.log(`Webpack watch invoke: HASH NEW=${stats.hash} CUR=${lastHash}`);
47+
48+
// If the file hash did not change there were no effective code changes detected
49+
// (comment changes do not change the compile hash and do not account for a rebuild!)
50+
// See here: https://webpack.js.org/api/node/#watching (note below watching)
51+
if (stats && stats.hash === lastHash) {
52+
if (firstRun) {
53+
firstRun = false;
54+
callback();
55+
}
56+
return;
57+
}
58+
4559
if (stats) {
60+
lastHash = stats.hash;
4661
this.serverless.cli.consoleLog(stats.toString(consoleStats));
4762
}
4863

49-
this.serverless.cli.log('Watching for changes...');
50-
5164
if (firstRun) {
5265
firstRun = false;
66+
this.serverless.cli.log('Watching for changes...');
5367
callback();
68+
} else {
69+
// We will close the watcher while the compile event is triggered and resume afterwards to prevent race conditions.
70+
watcher.close(() => {
71+
return this.serverless.pluginManager.spawn('webpack:compile:watch')
72+
.then(() => {
73+
// Resume watching after we triggered the compile:watch event
74+
return BbPromise.fromCallback(cb => {
75+
startWatch(cb);
76+
})
77+
.then(() => this.serverless.cli.log('Watching for changes...'));
78+
});
79+
});
5480
}
5581
});
5682
};

tests/run.test.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,29 @@ describe('run', () => {
8787
expect(module.isWatching).to.be.true;
8888
});
8989

90+
it('should not spawn on watch first run', () => {
91+
module.isWatching = false;
92+
const watch = module.watch.bind(module);
93+
webpackMock.compilerMock.watch = sandbox.stub().yields(null, {});
94+
_.set(module, 'options.function', 'myFunction');
95+
96+
watch('compile:watch:compile');
97+
expect(spawnStub).to.not.have.been.called;
98+
expect(module.isWatching).to.be.true;
99+
});
100+
101+
it('should spawn on watch second run', () => {
102+
module.isWatching = false;
103+
const watch = module.watch.bind(module);
104+
webpackMock.compilerMock.watch = sandbox.stub().yields(null, {});
105+
_.set(module, 'options.function', 'myFunction');
106+
107+
watch('compile:watch:compile');
108+
watch('compile:watch:compile');
109+
expect(spawnStub).to.have.been.calledOnce;
110+
expect(module.isWatching).to.be.true;
111+
});
112+
90113
it('should spawn invoke local on subsequent runs', () => {
91114
module.isWatching = true;
92115
const watch = module.watch.bind(module);

tests/webpack.mock.js

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,23 @@ const StatsMock = () => ({
1212
toString: sinon.stub().returns('testStats'),
1313
});
1414

15-
const CompilerMock = (sandbox, statsMock) => ({
15+
const WatchMock = sandbox => ({
16+
close: sandbox.stub().callsFake(cb => cb())
17+
});
18+
19+
const CompilerMock = (sandbox, statsMock, watchMock) => ({
1620
run: sandbox.stub().yields(null, statsMock),
17-
watch: sandbox.stub().yields(null, statsMock)
21+
watch: sandbox.stub().returns(watchMock).yields(null, statsMock)
1822
});
1923

2024
const webpackMock = sandbox => {
2125
const statsMock = StatsMock(sandbox);
22-
const compilerMock = CompilerMock(sandbox, statsMock);
26+
const watchMock = WatchMock(sandbox);
27+
const compilerMock = CompilerMock(sandbox, statsMock, watchMock);
2328
const mock = sinon.stub().returns(compilerMock);
2429
mock.compilerMock = compilerMock;
2530
mock.statsMock = statsMock;
31+
mock.watchMock = watchMock;
2632
return mock;
2733
};
2834

tests/wpwatch.test.js

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -145,23 +145,26 @@ describe('wpwatch', function() {
145145
it('should call callback on subsequent runs', () => {
146146
const wpwatch = module.wpwatch.bind(module);
147147
let watchCallbackSpy;
148-
webpackMock.compilerMock.watch.callsFake((options, cb) => {
148+
webpackMock.compilerMock.watch.onFirstCall().callsFake((options, cb) => {
149149
// We'll spy the callback registered for watch
150150
watchCallbackSpy = sandbox.spy(cb);
151151

152152
// Schedule second call after 2 seconds
153153
setTimeout(() => {
154-
watchCallbackSpy(null, { call: 2 });
154+
process.nextTick(() => watchCallbackSpy(null, { call: 2, hash: '2' }));
155155
}, 2000);
156-
process.nextTick(() => watchCallbackSpy(null, { call: 1 }));
156+
process.nextTick(() => watchCallbackSpy(null, { call: 1, hash: '1' }));
157+
return webpackMock.watchMock;
157158
});
158159
spawnStub.resolves();
159160

160161
return expect(wpwatch()).to.be.fulfilled
161162
.then(() => BbPromise.delay(3000))
162163
.then(() => BbPromise.join(
163-
expect(spawnStub).to.not.have.been.called,
164-
expect(webpackMock.compilerMock.watch).to.have.been.calledOnce,
164+
expect(spawnStub).to.have.been.calledOnce,
165+
expect(spawnStub).to.have.been.calledWithExactly('webpack:compile:watch'),
166+
expect(webpackMock.compilerMock.watch).to.have.been.calledTwice,
167+
expect(webpackMock.watchMock.close).to.have.been.calledOnce,
165168
expect(watchCallbackSpy).to.have.been.calledTwice
166169
));
167170
});
@@ -181,7 +184,7 @@ describe('wpwatch', function() {
181184
// Ignore the exception. The spy will record it.
182185
}
183186
}, 2000);
184-
process.nextTick(() => watchCallbackSpy(null, { call: 1 }));
187+
process.nextTick(() => watchCallbackSpy(null, { call: 3, hash: '3' }));
185188
});
186189
spawnStub.resolves();
187190

0 commit comments

Comments
 (0)