⛏️

【Windows】AmplifyのLambda function(+Layer)をTypeScriptで書きたい(ローカルでTS→JSする)

2022/04/22に公開

実現したいこと

前回の記事では、
Amplify CLIを使ってLambda関数をTypeScriptでコーディングする方法について書きました。

しかし、以下のような課題がありました。

  • Lambdaのソースコードをzip化する際に、TSファイル本体・tsconfig.json等の余計なファイルも含まれてしまう
  • GitHubと接続してCI/CD環境を構築する場合、ソースコードをPushしたときに自動でバックエンドのビルドも走ってしまうので、Amplify Console側でのTSコンパイルの考慮が必要

そこで、以下の方針を検討します。

  • TSファイルは関数の「src」フォルダーとは別で管理する
  • TS→JSのコンパイルはローカルでやる(これはこれで別の問題がありそうだけど。。)

今回の記事ではローカルでTSファイルからJSファイルにコンパイルする方法について手順を残しておきます。

環境

本記事では、以下の環境で実施しています。

  • バージョン
    • OS: Windows 11
    • Node.js: v14.16.1
    • npm: 6.14.12
    • AWS CLI: 2.1.38
    • Amplify CLI: 8.0.0
    • Lambdaランタイム: nodejs14.X
  • ネーミング
    • Amplify Project Name: app
    • Lambda Layer Name: layer
      ※識別用としてAmplify Project Name + Lambda Layer Nameのapplayerになります
    • Lambda Function Name: func
  • エディタ
    • Visual Studio Code(以下、VSCode)
  • ターミナル
    • PowerShell

手順

Step1. Lambda Layerを作成する

1. amplify add functionで追加

Powershell
$ amplify add function
? Select which capability you want to add: Lambda layer (shared code & resource used across functions)
? Provide a name for your Lambda layer: layer
? Choose the runtime that you want to use: NodeJS
? The current AWS account will always have access to this layer.
Optionally, configure who else can access this layer. (Hit <Enter> to skip) Specific AWS accounts
? Provide a list of comma-separated AWS account IDs: <AWSアカウントID>
✅ Lambda layer folders & files created:
amplify\backend\function\applayer

2. TSファイル群を管理するフォルダーを作成

次のフォルダーに移動

$ cd amplify\backend\function\applayer

tsフォルダーを作成し、その中にtsconfig.jsonを作成します

$ New-Item ts -ItemType Directory
$ cd ts
$ tsc --init

以下のように書き換えます。

tsconfig.json
{
  "compilerOptions": {
    "target": "es2017",
    "noImplicitAny": false,
    "allowJs": true,
    "noImplicitUseStrict": true,
    "types": ["node"],
    "moduleResolution": "node",
    "module": "CommonJS"
  },
  "include": ["."],
  "exclude": ["node_modules", "**/*.spec.ts"]
}

次に、(layerのフォルダー)/lib/nodejsの下にある「package.json」をコピーして
上記で作成した「ts」フォルダーの中に貼り付けします。
PowerShellのコマンドで実行する場合は以下のようになります。

Copy-Item -Path ..\lib\nodejs\package.json -Destination .\

コピーしたpackage.jsonを開いてmainを適宜書き換えます。
この後に「layer.ts」ファイルを作成するので、ここでは「layer.js」としておきます。

package.json
{
  "version": "1.0.0",
  "description": "",
  "main": "layer.js",
  "dependencies": {},
  "devDependencies": {}
}

続けて、(tsフォルダ内で)共通利用するパッケージをインストールします。
今回は「date-fns」をインストールしてみます

$ npm install date-fns

次に、(tsフォルダ内で)tsファイルを新規に作成します。

$ New-Item layer.ts

TSで好きに記述します。

layer.ts
import { format as _format } from "date-fns";
import ja from "date-fns/locale/ja";

export const formatJa = (date: Date, format: string): string => {
  return _format(date, format, { locale: ja });
};

3. 中間フォルダ構成

ここまでのフォルダ構成

<プロジェクルート>\amplify\backend\function\applayer
│  applayer-awscloudformation-template.json
│  layer-configuration.json
│  parameters.json
│  
├─lib
│  └─nodejs
│          package.json
│          README.txt
│          
├─opt
└─ts
    │  layer.ts
    │  package-lock.json
    │  package.json
    │  tsconfig.json
    │  
    └─node_modules
        └─date-fns

Step2. Lambda Functionを作成する

1. amplify add functionで追加

? Provide existing layers or select layers in this project to access from this functionのところで、Step1で作成したLambda Layer(ここではapplayer)を選択する

Powershell
$ amplify add function
? Select which capability you want to add: Lambda function (serverless function)
? Provide an AWS Lambda function name: func
? Choose the runtime that you want to use: NodeJS
? Choose the function template that you want to use: Hello World

Available advanced settings:
- Resource access permissions
- Scheduled recurring invocation
- Lambda layers configuration
- Environment variables configuration
- Secret values configuration

? Do you want to configure advanced settings? Yes
? Do you want to access other resources in this project from your Lambda function? No
? Do you want to invoke this function on a recurring schedule? No
? Do you want to enable Lambda layers for this function? Yes
? Provide existing layers or select layers in this project to access from this function (pick up to 5): applayer
? Do you want to configure environment variables for this function? No
? Do you want to configure secret values this function can access? No
? Do you want to edit the local lambda function now? Yes
Edit the file in your editor: <プロジェクトルート>amplify\backend\function\func\src\index.js
? Press enter to continue 
Successfully added resource func locally.

2. TSファイル群を管理するフォルダーを作成

次のフォルダーに移動

$ cd amplify\backend\function\func

tsフォルダーを作成し、その中にtsconfig.jsonを作成します

$ New-Item ts -ItemType Directory
$ cd ts
$ tsc --init

後でTSファイルを作成するので、先にtsconfig.jsonを作成しておく

$ tsc --init

中身は適宜書き換え

tsconfig.json
{
  "compilerOptions": {
    "target": "es2017",
    "noImplicitAny": false,
    "allowJs": true,
    "noImplicitUseStrict": true,
    "types": ["node"],
    "module": "CommonJS",
    "baseUrl": ".",
    "paths": {
      "/opt/layer": ["../../applayer/ts/layer"],
      "*": [
        "../../applayer/ts/node_modules/@types/*",
        "../../applayer/ts/node_modules/*"
      ]
    }
  },
  "include": ["."],
  "exclude": ["node_modules", "**/*.spec.ts"]
}

次に、(lambda関数のフォルダー)/srcの下にある「package.json」をコピーして
上記で作成した「ts」フォルダーの中に貼り付けします。
PowerShellのコマンドで実行する場合は以下のようになります。

Copy-Item -Path ..\src\package.json -Destination .\

3. index.tsを作成

次に、TSファイルを作成(ここではPowershellのコマンドで新規作成しています)

$ New-Item index.ts

index.tsの処理を書きます

index.ts
import { formatJa } from "/opt/layer";
import { Callback, Context, Handler } from "aws-lambda";

interface IEvent {
  arguments: {
    input: {
      message: string;
    };
  };
}

interface IResult {
  statusCode: number;
  body: string;
}

/**
 * @type {import('@types/aws-lambda').APIGatewayProxyHandler}
 */
export const handler: Handler<IEvent, IResult> = async (
  event: IEvent,
  context: Context,
  callback: Callback<IResult>
) => {
  const { message } = event.arguments.input;
  return {
    statusCode: 200,
    body: `${message} ${formatJa(new Date(), "yyyy-MM-dd")}`,
  };
};

aws-lambdaでTSエラーが出ると思いますが、(tsフォルダ内で)npm installすることで解消出来るかと思います。

ここまでのフォルダ構成

<プロジェクトルート>\amplify\backend\function\func
│  custom-policies.json
│  func-cloudformation-template.json
│  parameters.json
│  
├─src
│      event.json
│      index.js
│      package.json
│      
└─ts
    │  index.ts
    │  package-lock.json
    │  package.json
    │  tsconfig.json
    │  
    └─node_modules

Step3. ローカルでTSファイルをコンパイルする

最後に、上記で作成したTSファイルをJSファイルにコンパイルします。
プロジェクトルートにある「package.json」に以下のようなスクリプトを追加しておきます。

package.json
...
  "scripts": {
    ...
    "layer-tsc": "cd amplify\\backend\\function\\%npm_config_folder%\\ts && tsc && move /Y %npm_config_file%.js ..\\opt && xcopy package.json ..\\lib\\nodejs /Y && cd ..\\lib\\nodejs && del package-lock.json && npm install --production && npm prune --production",
    "lambda-tsc": "cd amplify\\backend\\function\\%npm_config_name%\\ts && tsc && move /Y index.js ..\\src && xcopy package.json ..\\src /Y && cd ..\\src && del package-lock.json && npm install --production && npm prune --production"
  },
...

上記のようにしておくことで、プロジェクトルートから各TSファイルをコンパイル出来ます。

layerをコンパイルする場合

npm run layer-tsc -folder=applayer -file=layer

lambda関数をコンパイルする場合

npm run lambda-tsc -name=func

あとはGitHub等と連携すれば、ソースコードをPushしたタイミングで関数の作成を自動でやってくれます。

Discussion