Skip to content

Commit e430841

Browse files
authored
add typescript webpack sample (#237)
* add the code and configurations * add the documentation
1 parent b1b62d8 commit e430841

File tree

13 files changed

+2982
-0
lines changed

13 files changed

+2982
-0
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
module.exports = {
2+
env: {
3+
browser: true,
4+
es6: true,
5+
node: true,
6+
},
7+
extends: [
8+
"standard",
9+
"eslint:recommended",
10+
"plugin:@typescript-eslint/recommended",
11+
"plugin:functional/recommended",
12+
"prettier",
13+
"plugin:prettier/recommended",
14+
],
15+
parser: "@typescript-eslint/parser",
16+
parserOptions: {
17+
project: "tsconfig.json",
18+
sourceType: "module",
19+
ecmaFeatures: {
20+
jsx: true,
21+
},
22+
},
23+
plugins: [
24+
"@typescript-eslint",
25+
"functional",
26+
"prettier",
27+
],
28+
rules: {
29+
"functional/no-conditional-statement": 0,
30+
},
31+
};
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
node_modules
2+
dist/
3+
lambda_function.zip
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# package.json is formatted by package managers, so we ignore it here
2+
package.json
3+
src/generated/
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"printWidth": 120,
3+
"trailingComma": "es5",
4+
"tabWidth": 2,
5+
"semi": true,
6+
"singleQuote": false
7+
}
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
# Hot Reloading your Typescript Lambda with LocalStack & Webpack
2+
3+
This is a simple example of how to hot reload your Typescript Lambda with Webpack with the help of LocalStack's Hot Reloading feature.
4+
5+
## Pre-requisites
6+
7+
* [LocalStack](https://docs.localstack.cloud/getting-started/installation)
8+
* [Docker](https://docs.docker.com/get-docker/)
9+
* [Node.js](https://nodejs.org/en/download/)
10+
* [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-install.html)
11+
* [`awslocal`](https://github.com/localstack/awscli-local)
12+
* [yarn](https://classic.yarnpkg.com/en/docs/install)
13+
* `jq`
14+
15+
## Start LocalStack
16+
17+
Start your LocalStack Docker container with the following command:
18+
19+
```bash
20+
localstack start
21+
```
22+
23+
## Install dependencies
24+
25+
Install the dependencies with the following command:
26+
27+
```bash
28+
yarn install
29+
```
30+
31+
## Build the Lambda
32+
33+
Build the Lambda with the following command:
34+
35+
```bash
36+
npm run build
37+
```
38+
39+
Note that the `build` script is using Nodemon to watch for changes in the `src` directory and rebuild the Lambda. This would be useful when you are developing & testing your Lambda, while making changes to the code in real-time.
40+
41+
With every change, you will see the following output:
42+
43+
```bash
44+
yarn run build
45+
yarn run v1.22.19
46+
$ yarn run clean && yarn run webpack --watch
47+
$ rimraf dist/*
48+
$ cross-env TS_NODE_PROJECT="tsconfig.webpack.json" webpack --mode production --watch
49+
asset api.js 1.4 KiB [emitted] [minimized] (name: api)
50+
./src/api.ts 1.42 KiB [built] [code generated]
51+
./src/util.ts 314 bytes [built] [code generated]
52+
webpack 5.75.0 compiled successfully in 602 ms
53+
[nodemon] 3.0.1
54+
[nodemon] to restart at any time, enter `rs`
55+
[nodemon] watching path(s): dist/api.js
56+
[nodemon] watching extensions: js,mjs,cjs,json
57+
[nodemon] starting `node dist/api.js`
58+
[nodemon] clean exit - waiting for changes before restart
59+
asset api.js 1.4 KiB [emitted] [minimized] (name: api)
60+
./src/api.ts 1.42 KiB [built] [code generated]
61+
./src/util.ts 314 bytes [built] [code generated]
62+
webpack 5.75.0 compiled successfully in 455 ms
63+
[nodemon] restarting due to changes...
64+
[nodemon] starting `node dist/api.js`
65+
```
66+
67+
## Deploy the Lambda
68+
69+
Deploy the Lambda with the following command:
70+
71+
```bash
72+
awslocal lambda create-function \
73+
--function-name localstack-example \
74+
--runtime nodejs18.x \
75+
--role arn:aws:iam::000000000000:role/lambda-ex \
76+
--code S3Bucket="hot-reload",S3Key="$(PWD)/dist" \
77+
--handler api.default
78+
```
79+
80+
Additionally, you can create a Lambda Function URL with the following command:
81+
82+
```bash
83+
function_url=$(awslocal lambda create-function-url-config --function-name localstack-example --auth-type NONE | jq -r '.FunctionUrl')
84+
```
85+
86+
## Test the Lambda
87+
88+
You can test the Lambda by sending `POST` requests to the Lambda Function URL:
89+
90+
```bash
91+
curl -X POST \
92+
-H "Content-Type: application/json" \
93+
-d '{"name": "John", "age": 30}' \
94+
"$function_url"
95+
```
96+
97+
The following output would be displayed:
98+
99+
```bash
100+
{"payload":{"name":"John","age":30}}
101+
```
102+
103+
You can additionally test the Lambda with the following command:
104+
105+
```bash
106+
curl -X GET "$function_url"
107+
```
108+
109+
This will return the following output:
110+
111+
```bash
112+
{"error":"Only JSON payload is accepted"}
113+
```
114+
115+
## Hot Reload the Lambda
116+
117+
Go to the `src` directory and make changes to the `api.ts` file. For example, change the following line:
118+
119+
```typescript
120+
return errorResponse("Only JSON payloads are accepted", 406);
121+
```
122+
123+
Make the `errorResponse` function return `"Only JSON payload is accepted"` instead of `"Only JSON payloads are accepted"`. Save the file and run the last `curl` command again:
124+
125+
```bash
126+
curl -X GET "$function_url"
127+
```
128+
129+
This will return the following output:
130+
131+
```bash
132+
{"error":"Only JSON payload is accepted"}
133+
```
134+
135+
You can perform further changes to the `api.ts` file and test the Lambda in real-time.
136+
137+
## How does it work?
138+
139+
The Lambda Hot Reloading feature in LocalStack allows you to hot reload your Lambda code in real-time. In this sample, this is achieved using the following:
140+
141+
- The `build` script in the `package.json` file uses Nodemon to watch for changes in the `src` directory and rebuild the Lambda. This is enabled using the [`nodemon-webpack-plugin`](https://www.npmjs.com/package/nodemon-webpack-plugin) plugin, which has been pre-configured in the `webpack.config.js` file.
142+
- The `S3Bucket` and `S3Key` parameters in the `awslocal lambda create-function` command are used to deploy the Lambda code from the `dist` directory. This is done by specifying the `dist` directory as the `S3Key` parameter. Everytime the Lambda is updated, Nodemon triggers another build and the `dist` directory is updated with the latest code changes. LocalStack then automatically updates the Lambda code with the latest changes from the `dist` directory.
143+
144+
## Notes
145+
146+
This sample application is inherited from a [public repository](https://github.com/pdlug/lambda-typescript-webpack).
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
{
2+
"name": "lambda-typescript-webpack",
3+
"version": "1.0.0",
4+
"main": "index.js",
5+
"license": "MIT",
6+
"scripts": {
7+
"build": "yarn run clean && yarn run webpack --watch",
8+
"build-aws": "yarn run clean && yarn run webpack",
9+
"clean": "rimraf dist/*",
10+
"lint": "eslint \"src/**/*.ts*\"",
11+
"lint:fix": "eslint --fix \"src/**/*.ts*\"",
12+
"format": "prettier *.ts \"src/**/*.ts*\" --write",
13+
"webpack": "cross-env TS_NODE_PROJECT=\"tsconfig.webpack.json\" webpack --mode production",
14+
"package": "zip -q -j -r ./lambda_function.zip dist"
15+
},
16+
"devDependencies": {
17+
"@types/aws-lambda": "^8",
18+
"@types/node": "^18",
19+
"@types/webpack": "^5",
20+
"@typescript-eslint/eslint-plugin": "^5",
21+
"@typescript-eslint/parser": "^5",
22+
"builtin-modules": "^3",
23+
"cross-env": "^7",
24+
"eslint": "^8",
25+
"eslint-config-prettier": "^8",
26+
"eslint-config-standard": "^17",
27+
"eslint-plugin-functional": "^4",
28+
"eslint-plugin-import": "^2",
29+
"eslint-plugin-n": "^15",
30+
"eslint-plugin-node": "^11",
31+
"eslint-plugin-prettier": "^4",
32+
"eslint-plugin-promise": "^6",
33+
"json-loader": "^0",
34+
"prettier": "^2",
35+
"rimraf": "^3",
36+
"ts-loader": "^9",
37+
"ts-node": "^10",
38+
"typescript": "^4",
39+
"webpack": "^5",
40+
"webpack-cli": "^5",
41+
"nodemon-webpack-plugin": "^4.8.2"
42+
},
43+
"dependencies": {
44+
"aws-sdk": "^2",
45+
"bufferutil": "^4",
46+
"utf-8-validate": "^6"
47+
}
48+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#!/bin/bash
2+
3+
awslocal lambda create-function \
4+
--function-name localstack-example \
5+
--runtime nodejs18.x \
6+
--role arn:aws:iam::000000000000:role/lambda-ex \
7+
--code S3Bucket="hot-reload",S3Key="$(PWD)/dist" \
8+
--handler api.default
9+
10+
function_url=$(awslocal lambda create-function-url-config --function-name localstack-example --auth-type NONE | jq -r '.FunctionUrl')
11+
12+
curl -X POST \
13+
-H "Content-Type: application/json" \
14+
-d '{"name": "John", "age": 30}' \
15+
"$function_url"
16+
17+
curl -X GET "$function_url"
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import AWSLambda from "aws-lambda";
2+
3+
import { downcaseKeys } from "./util";
4+
5+
const errorResponse = (message: string, code = 500): AWSLambda.APIGatewayProxyResult => ({
6+
body: JSON.stringify({ error: message }),
7+
statusCode: code,
8+
});
9+
10+
export default async (event: AWSLambda.APIGatewayEvent): Promise<AWSLambda.APIGatewayProxyResult> => {
11+
const headers = downcaseKeys(event.headers);
12+
13+
if (!/^application\/json\b/.test(headers["content-type"] || "") || !event.body) {
14+
return errorResponse("Only JSON payloads are accepted", 406);
15+
}
16+
17+
const payload = JSON.parse(event.body);
18+
return Object.keys(payload).length === 0
19+
? errorResponse("No data provided", 422)
20+
: {
21+
body: JSON.stringify({ payload }),
22+
statusCode: 200,
23+
};
24+
};
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
type StringMap<T> = {
2+
readonly [k: string]: T[keyof T];
3+
};
4+
5+
export function downcaseKeys<T extends Record<string, any>>(o: T): StringMap<T> {
6+
return Object.keys(o).reduce<StringMap<T>>((no, k) => {
7+
return { ...no, [k.toLowerCase()]: o[k as keyof T] };
8+
}, {});
9+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"compilerOptions": {
3+
"module": "commonjs",
4+
"target": "es6",
5+
"sourceMap": false,
6+
"removeComments": true,
7+
"moduleResolution": "node",
8+
"strict": true,
9+
"strictNullChecks": true,
10+
"noImplicitAny": true,
11+
"noImplicitReturns": true,
12+
"noFallthroughCasesInSwitch": true,
13+
"noUnusedParameters": true,
14+
"noUnusedLocals": true,
15+
"lib": ["es6"],
16+
"allowJs": true,
17+
"forceConsistentCasingInFileNames": true,
18+
"outDir": "./dist",
19+
"esModuleInterop": true
20+
},
21+
"exclude": [
22+
"node_modules",
23+
"out"
24+
],
25+
"include": [
26+
"**/*.ts",
27+
"**/*.tsx"
28+
]
29+
}

0 commit comments

Comments
 (0)