SAM x TypeScript Lambda x Image

タイトルの構成でやろうと思ったら意外と面倒だったので、次回セットアップを楽にするためのめも

↓の資料はだいたい同じ内容で参考にさせてもらいました

前提

  • SAM CLI is installed
  • Docker is installed

TL;DR

ここに上げた

sam init

注意点として、init するときに package type = Image にすると、TypeScript のテンプレートを CLI に作ってもらえなくなる。なので、ここではいったん package type = Zip にする

抜粋

What package type would you like to use?
    1 - Zip
    2 - Image
Package type: 1

Based on your selections, the only dependency manager available is npm.
We will proceed copying the template using npm.

Select your starter template
    1 - Hello World Example
    2 - Hello World Example TypeScript
Template: 2

全部

❯ sam init

You can preselect a particular runtime or package type when using the `sam init` experience.
Call `sam init --help` to learn more.

Which template source would you like to use?
    1 - AWS Quick Start Templates
    2 - Custom Template Location
Choice: 1

Choose an AWS Quick Start application template
    1 - Hello World Example
    2 - Data processing
    3 - Hello World Example with Powertools for AWS Lambda
    4 - Multi-step workflow
    5 - Scheduled task
    6 - Standalone function
    7 - Serverless API
    8 - Infrastructure event management
    9 - Lambda Response Streaming
    10 - Serverless Connector Hello World Example
    11 - Multi-step workflow with Connectors
    12 - GraphQLApi Hello World Example
    13 - Full Stack
    14 - Lambda EFS example
    15 - Hello World Example With Powertools for AWS Lambda
    16 - DynamoDB Example
    17 - Machine Learning
Template: 1

Use the most popular runtime and package type? (Python and zip) [y/N]: n

Which runtime would you like to use?
    1 - aot.dotnet7 (provided.al2)
    2 - dotnet6
    3 - go1.x
    4 - go (provided.al2)
    5 - go (provided.al2023)
    6 - graalvm.java11 (provided.al2)
    7 - graalvm.java17 (provided.al2)
    8 - java21
    9 - java17
    10 - java11
    11 - java8.al2
    12 - java8
    13 - nodejs20.x
    14 - nodejs18.x
    15 - nodejs16.x
    16 - python3.9
    17 - python3.8
    18 - python3.12
    19 - python3.11
    20 - python3.10
    21 - ruby3.2
    22 - ruby2.7
    23 - rust (provided.al2)
    24 - rust (provided.al2023)
Runtime: 13

What package type would you like to use?
    1 - Zip
    2 - Image
Package type: 1

Based on your selections, the only dependency manager available is npm.
We will proceed copying the template using npm.

Select your starter template
    1 - Hello World Example
    2 - Hello World Example TypeScript
Template: 2

Would you like to enable X-Ray tracing on the function(s) in your application?  [y/N]: n

Would you like to enable monitoring using CloudWatch Application Insights?
For more info, please view https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/cloudwatch-application-insights.html [y/N]: n

Would you like to set Structured Logging in JSON format on your Lambda functions?  [y/N]: n

Project name [sam-app]: sam-typescript-demo

    -----------------------
    Generating application:
    -----------------------
    Name: sam-typescript-demo
    Runtime: nodejs20.x
    Architectures: x86_64
    Dependency Manager: npm
    Application Template: hello-world-typescript
    Output Directory: .
    Configuration file: sam-typescript-demo/samconfig.toml

    Next steps can be found in the README file at sam-typescript-demo/README.md


Commands you can use next
=========================
[*] Create pipeline: cd sam-typescript-demo && sam pipeline init --bootstrap
[*] Validate SAM template: cd sam-typescript-demo && sam validate
[*] Test Function in the Cloud: cd sam-typescript-demo && sam sync --stack-name {stack-name} --watch

コード修正

やることは

  • ファイルのリネーム
  • Dockerfile の追加
  • package.json の修正
  • template.yaml の修正

ここ↓に Dockerfile とかもろもろ中身が書いてあるので、基本的にこれに沿って進める

ファイルのリネーム

init でできた hello-world/app.tshello-world/index.ts に変える。なぜかというと、このあとコピペするファイルが index.ts 前提なのでその方が楽になるから。 app.ts のままでももちろんできるけど、以降の作業がちょっと変わる。

Dockerfile

hello-world/Dockerfile を作成して、上のリンクの中身をペーストする。ただし、

  • image の node version
  • CMD

が違う可能性があるので、適宜修正する。

2024年1月現在だと、ドキュメントは

FROM public.ecr.aws/lambda/nodejs:18 as builder となっているけど、自分は nodejs:20 を追加したいので変えた。

CMD については、公式は CMD ["index.handler"] と書いているけど、init でできたコードは lambdaHandler と微妙に違うのでそれも直す。

最終できたのはこれ

# hello-world/Dockerfile

FROM public.ecr.aws/lambda/nodejs:20 as builder # changed from nodejs:18
WORKDIR /usr/app
COPY package.json index.ts  ./
RUN npm install
RUN npm run build
    
FROM public.ecr.aws/lambda/nodejs:20 # changed from nodejs:18
WORKDIR ${LAMBDA_TASK_ROOT}
COPY --from=builder /usr/app/dist/* ./
CMD ["index.lambdaHandler"] # changed from index.handler

package.json の修正

TS → JS に transpile するので、そのための build コマンドも追加する

これは最初の Deploy transpiled ... のリンクに書いてあるとおりのコマンドを貼り付ければ OK

  ...
  "scripts": {
    "build": "esbuild index.ts --bundle --minify --sourcemap --platform=node --target=es2020 --outfile=dist/index.js", 
    ...
  }
  ...

template.yaml の修正

これは Resources > HelloWorldFunction の中を zip 用から image 用に変える。変える先のコードは、別のディレクトリで sam init を JS x package type = Zip で実行して、その template.yaml を見ながらコピペした。

最終こうなればおーけー。

# ...

Resources:
  HelloWorldFunction:
    Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
    Properties:
      PackageType: Image
      Architectures:
        - x86_64
      Architectures:
        - x86_64
      Events:
        HelloWorld:
          Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
          Properties:
            Path: /hello
            Method: get
    Metadata:
      DockerTag: nodejs20.x-v1
      DockerContext: ./hello-world
      Dockerfile: Dockerfile


# ...

動作確認

$ sam build
$ sam local invoke

これが両方通れば大丈夫なはず!

その他

vitest を入れる

インストールして、

  • テストファイルのインポートを jest から vitest に書き換える (vitest は jest 互換あるのでたぶん import { ... } from "vitest" に書き換えるだけでいけるはず)
$ npm install -D test

以下は任意だけど、やっておくとテストファイルごとに vitest から expect などをインポートしなくてもよくなる

hello-world/vitest.config.ts を追加

/// <reference types="vitest" />
import { defineConfig } from "vite";

// https://vitejs.dev/config/
export default defineConfig({
  test: {
    globals: true,
  },
});

hello-world/tsconfig.json に追記

"types": ["vitest/globals"],

alias の設定

hello-world/packaage.json に以下を追記

"test": "vitest"

alias の設定

import ... from "@/..." をやるための設定

tsconfig.json の "compilerOptions" に以下を追記

"baseUrl": ".",
"paths": {
  "@/*": ["*"]
}

あわせて vitest.config.ts もかぞえる

...
resolve: {
    alias: {
        '@': path.resolve(__dirname, '.'),
    },
},
...

その他

cd hello-world && npm run test を実行したら、 "command not found: tsc" が出ちゃうことに気がついたので、これは別で対応予定