Skip to content

Commit

Permalink
fix: Hot reloading with chokidar & busting of the require cache. (#515)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
Co-authored-by: Matt Strom <[email protected]>
  • Loading branch information
3 people authored Sep 19, 2022
1 parent 72670ee commit 703d1f4
Show file tree
Hide file tree
Showing 6 changed files with 102 additions and 26 deletions.
38 changes: 35 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ _serverless.yml_
```yaml
service:
name: edge-lambdas

plugins:
- serverless-offline-edge-lambda

Expand Down Expand Up @@ -60,7 +60,7 @@ a configuration option `path` that it uses to resolve function handlers.
```yaml
plugins:
- serverless-plugin-typescript

custom:
offlineEdgeLambda:
path: '.build'
Expand All @@ -70,8 +70,40 @@ For usage with `serverless-webpack` and `serverless-bundle` the config is simila
```yaml
plugins:
- serverless-webpack # or serverless-bundle
custom:
offlineEdgeLambda:
path: './.webpack/service/'
```

### Hot Reload Support

Hot reload for serverless-esbuild and serverless-plugin-typescript are available with extra configuration.

The watch/reload mechanism is available form serverless-webpack, but is disabled by default for esbuild and typescript.

The flag "watchReload: true" will turn on the watcher so that typescript and esbuild solutions use the watcher to hot reload the handlers.
The path to the built handlers must be specified for the watcher to work correctly.

example:
```yaml
custom:
offlineEdgeLambda:
path: '.esbuild/service'
watchReload: true
```

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).

example:

```yaml
custom:
offlineEdgeLambda:
path: '.dist/service'
watchReload: true
ignoreInitial: true
awaitWriteFinish: true
interval: 500,
debounce: 750
```
40 changes: 20 additions & 20 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"release": "semantic-release --no-ci",
"release:dry-run": "semantic-release --no-ci --dry-run",
"snyk-protect": "snyk protect",
"prepare": "npm run snyk-protect"
"prepare": "npm run snyk-protect && npm run build"
},
"config": {
"commitizen": {
Expand Down Expand Up @@ -49,6 +49,7 @@
],
"dependencies": {
"body-parser": "^1.20.0",
"chokidar": "^3.5.3",
"connect": "^3.7.0",
"cookie-parser": "^1.4.6",
"flat-cache": "^3.0.4",
Expand Down
22 changes: 21 additions & 1 deletion src/behavior-router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ import { Context } from 'aws-lambda';
import bodyParser from 'body-parser';
import connect, { HandleFunction } from 'connect';
import cookieParser from 'cookie-parser';
import * as chokidar from 'chokidar';
import * as fs from 'fs-extra';
import { createServer, Server, IncomingMessage, ServerResponse } from 'http';
import { StatusCodes } from 'http-status-codes';
import * as os from 'os';
import * as path from 'path';
import { URL } from 'url';

import { debounce } from './utils/debounce';
import { HttpError, InternalServerError } from './errors/http';
import { FunctionSet } from './function-set';
import { asyncMiddleware, cloudfrontPost } from './middlewares';
Expand Down Expand Up @@ -62,6 +63,25 @@ export class BehaviorRouter {

this.origins = this.configureOrigins();
this.cacheService = new CacheService(this.cacheDir);

if (this.serverless.service.custom.offlineEdgeLambda.watchReload) {
this.watchFiles(path.join(this.path, '**/*'), {
ignoreInitial: true,
awaitWriteFinish: true,
interval: 500,
debounce: 750,
...options,
});
}
}

watchFiles(pattern: any, options: any) {
const watcher = chokidar.watch(pattern, options);
watcher.on('all', debounce(async (eventName, srcPath) => {
console.log('Lambda files changed, syncing...');
await this.extractBehaviors();
console.log('Lambda files synced');
}, options.debounce, true));
}

match(req: IncomingMessage): FunctionSet | null {
Expand Down
22 changes: 22 additions & 0 deletions src/utils/debounce.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
export const debounce =
function (func: { (eventName: any, srcPath: any): Promise<void>; apply?: any; }, threshold: number, execAsap: boolean) {
let timeout: any;
return function debounced(this: any) {
const obj = this;
const args = arguments;

function delayed() {
if (!execAsap) {
func.apply(obj, args);
}
timeout = undefined;
}

if (timeout) {
clearTimeout(timeout);
} else if (execAsap) {
func.apply(obj, args);
}
timeout = setTimeout(delayed, threshold || 100);
};
};
3 changes: 2 additions & 1 deletion src/utils/load-module.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { resolve } from 'path';
import {clearModule} from './clear-module';
import { clearModule } from './clear-module';

export class ModuleLoader {
protected loadedModules: string[] = [];
Expand All @@ -15,6 +15,7 @@ export class ModuleLoader {
const [, modulePath, functionName] = match;
const absPath = resolve(modulePath);

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

this.loadedModules.push(absPath);
Expand Down

0 comments on commit 703d1f4

Please sign in to comment.