###既に作ったserverlessプロジェクトにTypescriptを入れたい
現在AWSでSlack Botを動かすために、開発環境を作れるserverless絶賛勉強中です。
プロジェクトを作る時にtypescript対応するのは見つかったのですが、
もう作っちゃったプロジェクトに追加する方法は見当たらなかったのでまとめます。
プロジェクト作成時の場合はcreateするときに-t aws-nodejs-typescript
というテンプレートを指定すればいいらしい。次はそっちでやってみます。
参考:ServerlessでTypeScriptの開発環境を作る
#serverlessプラグインを追加
https://www.serverless.com/plugins/serverless-plugin-typescript/
serverless-plugin-typescriptという素敵なプラグインを見つけたので使います。
Features
・Zero-config: Works out of the box without the need to install any other compiler or plugins
・Supports ES2015 syntax + features (export, import, async, await, Promise, ...)
・Supports sls package, sls deploy and sls deploy function
・Supports sls invoke local + --watch mode
・Integrates nicely with serverless-offline
ローカル環境構築に使うserverless-offlineとの連携に自信ありとのことで、良いですね。
実際この後serverless-offlineも導入しましたが大体問題なく変換されています。
##インストール
npm install -D serverless-plugin-typescript typescript
した後に、お約束のserverless.yml
のpluginsに下記を追加
plugins:
- serverless-plugin-typescript
##tsconfig.json
minimum exampleを見る限り、デフォルトで良ければtsconfig.json作らなくても動くっぽい。
{
"compilerOptions": {
"preserveConstEnums": true,
"strictNullChecks": true,
"sourceMap": true,
"allowJs": true,
"target": "es5",
"outDir": ".build",
"moduleResolution": "node",
"lib": ["es2015"],
"rootDir": "./"
}
}
これがデフォルトで、outDir
とrootDir
以外は上書きしていいよ!とのことです。
私はTypescript、細かいとこまで気が付くデキるやつだなぁ…くらいのノリで使ってるので、今のところ特に何も変更してません。
あとはsls deploy時に勝手にトランスパイルしてくれます。
(ディレクトリ含むファイル分割しても勝手に上手いことしてくれるのかはこれから検証…)
##serverless-offlineと使う
時はserverless-plugin-typescriptが先に来るようにserverless.yml
に記述しないとダメらしいです。
plugins:
...
- serverless-plugin-typescript
...
- serverless-offline
...
serverless-dynamodb-local
を使う時もなんか順番があるみたいですが、私はまだ使ってないのでよくわかりません。各自ご確認ください
##aws-sdkの型定義ファイルを使う
他で使われていたので@types/aws-sdkというパッケージを見に行ったら、もう公式が提供してるから使わなくていいよと言われてしまったので公式を見に行きます。
https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/#Usage_with_TypeScript
まずは、というか実際@types/node
をインストールするだけです。
npm install --save-dev @types/node
あとは.tsファイルからの読み込みを書き換えて完了!
import AWS from 'aws-sdk';
##なぜかSlack Botが動かなくなった…
動かなくなりました。公式の言う通りimportした後、こんな感じでAWS
オブジェクトの中のLambdaメソッドを呼び出していたのですが、
Lambdaはundifinedを参照していますみたいなエラーが出て、なぜかAWS
の中身がからっぽということに…どうして、さっきまでこれで動いていたのに。
import AWS from 'aws-sdk';
const lambda = new AWS.Lambda()
serverless-offlineを使うと?distフォルダにコンパイル後のjsファイルが生成されているので、そこを見てみるとこんなことになってました。
勝手に入ってきた.default
君、何?
var aws_sdk_1 = require("aws-sdk");
var lambda = new aws_sdk_1.default.Lambda();
.default
君がどこから何のために来たのか全然分からないんですが、とりあえずimport時に直接Lambdaメソッドを分割代入したら無事動いてくれました。
→12/02追記
あっ分かりました、分割代入ではなくimportするとdefault exportされた結果のdeaultというオブジェクトとしてimportされるんですね~…
import { Lambda } from 'aws-sdk';
const lambda = new Lambda()
##型定義を付けた値にserverless.ymlの環境変数を入れる時の注意
Lambda.invoke()に渡すパラメーターを設定する変数に型定義AWS.Lambda.InvocationRequest
を付けた時のことです。
const params: AWS.Lambda.InvocationRequest = {
FunctionName: process.env.INVOKE_FUNCTION,
InvocationType: "RequestResponse",
Payload: JSON.stringify({ msg: "Hello" }),
};
process.env.INVOKE_FUNCTION
のとこで下記のような、エラーがコンソールに湧き出ました。
api-lambda.ts (13,17): Argument of type '{ FunctionName: string | undefined; InvocationType: string; Payload: string; }' is not assignable to parameter of type 'InvocationRequest'.
Types of property 'FunctionName' are incompatible.
Type 'string | undefined' is not assignable to type 'string'.
Type 'undefined' is not assignable to type 'string'.
ちなみにinvokeする関数のFunctionNameは、serverless.yml
で定義している「サービス名-ステージ名-functionsで定義した関数名」で生成されるので、serverless.yml
でファイル内参照しながら書く方が楽なんですよね。こんな感じで。
"${self:service}-${self:provider.stage}-<functions内で定義した関数名>"
まぁそれはともかく、エラーは「string型で定義したところに'string | undefined'の値は入れられません」とか言ってます。
どうやら.yml内から持ってきた値はtypescriptの方で中身がある無しを判断できないので自動的に'string | undefined'型と判定されるようです。
しかしそんなときの為にTypescriptさんが解決策を用意してくれていました
const params: AWS.Lambda.InvocationRequest = {
FunctionName: process.env.INVOKE_FUNCTION!,
...
};
これで解決です。!
つけるだけ。
Non-null assertion operator
要するにこれは末尾に付けた変数がnullでもundefinedでもありませんよというのを明示的に指定できる演算子のようです。
###?を付ければOptional chaining
そういえば?
をオブジェクトの末尾に付けると、途中でundifinedの中身を参照しようとしてもエラーを出さずにnullが帰ってくるというめちゃくちゃ良い感じの機能がありますね。
slackのレスポンスはオブジェクトがネストしてることも多いので、私が今のところそこまでよく分かってないtypescriptを入れたい理由の一つです。
https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#optional-chaining
以上です。