Skip to content

Commit 703d1f4

Browse files
robblovellRobb Lovellmattstrom
authored
fix: Hot reloading with chokidar & busting of the require cache. (#515)
* fix: Hot reloading with chokidar & delete require cache. * chore: removed reformatting. * Readme for the hot reloader for esbuild and typescript * feat(hot reload): Hot reload for esbuild and typescript based serverless projects * opted for not changing the default path. * support for url based references in package json files. Co-authored-by: Robb Lovell <robbl@vidigami.com> Co-authored-by: Matt Strom <5853934+mattstrom@users.noreply.github.com>
1 parent 72670ee commit 703d1f4

File tree

6 files changed

+102
-26
lines changed

6 files changed

+102
-26
lines changed

README.md

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ _serverless.yml_
1414
```yaml
1515
service:
1616
name: edge-lambdas
17-
17+
1818
plugins:
1919
- serverless-offline-edge-lambda
2020

@@ -60,7 +60,7 @@ a configuration option `path` that it uses to resolve function handlers.
6060
```yaml
6161
plugins:
6262
- serverless-plugin-typescript
63-
63+
6464
custom:
6565
offlineEdgeLambda:
6666
path: '.build'
@@ -70,8 +70,40 @@ For usage with `serverless-webpack` and `serverless-bundle` the config is simila
7070
```yaml
7171
plugins:
7272
- serverless-webpack # or serverless-bundle
73-
73+
7474
custom:
7575
offlineEdgeLambda:
7676
path: './.webpack/service/'
7777
```
78+
79+
### Hot Reload Support
80+
81+
Hot reload for serverless-esbuild and serverless-plugin-typescript are available with extra configuration.
82+
83+
The watch/reload mechanism is available form serverless-webpack, but is disabled by default for esbuild and typescript.
84+
85+
The flag "watchReload: true" will turn on the watcher so that typescript and esbuild solutions use the watcher to hot reload the handlers.
86+
The path to the built handlers must be specified for the watcher to work correctly.
87+
88+
example:
89+
```yaml
90+
custom:
91+
offlineEdgeLambda:
92+
path: '.esbuild/service'
93+
watchReload: true
94+
```
95+
96+
Additional options can be used to modify the behavior of the file watcher and debounce logic (ignoreInitial, awaitWriteFinish, interval, debounce, and any other chokidar option).
97+
98+
example:
99+
100+
```yaml
101+
custom:
102+
offlineEdgeLambda:
103+
path: '.dist/service'
104+
watchReload: true
105+
ignoreInitial: true
106+
awaitWriteFinish: true
107+
interval: 500,
108+
debounce: 750
109+
```

package-lock.json

Lines changed: 20 additions & 20 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
"release": "semantic-release --no-ci",
1818
"release:dry-run": "semantic-release --no-ci --dry-run",
1919
"snyk-protect": "snyk protect",
20-
"prepare": "npm run snyk-protect"
20+
"prepare": "npm run snyk-protect && npm run build"
2121
},
2222
"config": {
2323
"commitizen": {
@@ -49,6 +49,7 @@
4949
],
5050
"dependencies": {
5151
"body-parser": "^1.20.0",
52+
"chokidar": "^3.5.3",
5253
"connect": "^3.7.0",
5354
"cookie-parser": "^1.4.6",
5455
"flat-cache": "^3.0.4",

src/behavior-router.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@ import { Context } from 'aws-lambda';
22
import bodyParser from 'body-parser';
33
import connect, { HandleFunction } from 'connect';
44
import cookieParser from 'cookie-parser';
5+
import * as chokidar from 'chokidar';
56
import * as fs from 'fs-extra';
67
import { createServer, Server, IncomingMessage, ServerResponse } from 'http';
78
import { StatusCodes } from 'http-status-codes';
89
import * as os from 'os';
910
import * as path from 'path';
1011
import { URL } from 'url';
11-
12+
import { debounce } from './utils/debounce';
1213
import { HttpError, InternalServerError } from './errors/http';
1314
import { FunctionSet } from './function-set';
1415
import { asyncMiddleware, cloudfrontPost } from './middlewares';
@@ -62,6 +63,25 @@ export class BehaviorRouter {
6263

6364
this.origins = this.configureOrigins();
6465
this.cacheService = new CacheService(this.cacheDir);
66+
67+
if (this.serverless.service.custom.offlineEdgeLambda.watchReload) {
68+
this.watchFiles(path.join(this.path, '**/*'), {
69+
ignoreInitial: true,
70+
awaitWriteFinish: true,
71+
interval: 500,
72+
debounce: 750,
73+
...options,
74+
});
75+
}
76+
}
77+
78+
watchFiles(pattern: any, options: any) {
79+
const watcher = chokidar.watch(pattern, options);
80+
watcher.on('all', debounce(async (eventName, srcPath) => {
81+
console.log('Lambda files changed, syncing...');
82+
await this.extractBehaviors();
83+
console.log('Lambda files synced');
84+
}, options.debounce, true));
6585
}
6686

6787
match(req: IncomingMessage): FunctionSet | null {

src/utils/debounce.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
export const debounce =
2+
function (func: { (eventName: any, srcPath: any): Promise<void>; apply?: any; }, threshold: number, execAsap: boolean) {
3+
let timeout: any;
4+
return function debounced(this: any) {
5+
const obj = this;
6+
const args = arguments;
7+
8+
function delayed() {
9+
if (!execAsap) {
10+
func.apply(obj, args);
11+
}
12+
timeout = undefined;
13+
}
14+
15+
if (timeout) {
16+
clearTimeout(timeout);
17+
} else if (execAsap) {
18+
func.apply(obj, args);
19+
}
20+
timeout = setTimeout(delayed, threshold || 100);
21+
};
22+
};

src/utils/load-module.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { resolve } from 'path';
2-
import {clearModule} from './clear-module';
2+
import { clearModule } from './clear-module';
33

44
export class ModuleLoader {
55
protected loadedModules: string[] = [];
@@ -15,6 +15,7 @@ export class ModuleLoader {
1515
const [, modulePath, functionName] = match;
1616
const absPath = resolve(modulePath);
1717

18+
delete require.cache[require.resolve(absPath)];
1819
const module = await import(absPath);
1920

2021
this.loadedModules.push(absPath);

0 commit comments

Comments
 (0)