/var/log/jsoizo

メモ帳 技術とか趣味とか

AWS SAMで管理するTypeScript on Lambda用のesbuild設定を細かくやりたい

AWS Lambda上で実行するNode.jsアプリケーションをTypeScriptで開発しているとき、TypeScriptのトランスパイルをどのように行うかがポイントであるが、AWS SAMを利用しているならば template.yml ファイル内にesbuildの設定を記述することで sam build 時に自動的にトランスパイルしてくれる。

設定方法は以下のドキュメントを参照する。

https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-using-build-typescript.html

このドキュメントには

The BuildProperties object supports the following properties for esbuild. All of the properties are optional. By default, AWS SAM uses your Lambda function handler for the entry point.

とあるが、esbuildのすべての設定項目を網羅しているわけではない。
だが、 template.yml の書き方次第でどのような項目も設定可能であるためその方法について記す。

SAMでTypeScriptをビルドするときに何が起きているか

本題に入る前にそもそもSAM CLIがどのようにesbuildを叩いているのかという部分を確認。
SAM CLIAWS Labmda用コードのビルドにaws-lambda-buildersを利用している。
これがpythonで実装されており、 template.yml の内容に応じて子プロセスとしてesbuildを実行するのである。

その時のesbuldコマンドの実行内容は sam build --debugデバッグオプションを付けるとわかる。
template.yml を空にしてビルドすることで、いくつかのオプションはデフォルト値が決まっているということもわかるはずである。
※ コマンドとオプションを整形して出力する

/{省略}/node_modules/.bin/esbuild
app.ts
--bundle
--platform=node
--outdir=/private/tmp/sam-app/.aws-sam/build/HelloWorldFunction
--target=es2020
--format=cjs
--minify

ここで、 template.yml に対して設定を追加すると、

デフォルトでesbuildコマンドにオプションがつく設定項目( FormatMinify など)はオプションが上書きされ、そうでないものはオプションが付与されるようだ。 以下はFormatとOutExtensionを指定した場合の template.yml

Metadata:
  BuildMethod: esbuild
  BuildProperties:
    Format: esm
    OutExtention:
      - .js=.mjs

と、その時のSAM CLIが実行するesbulidコマンド。
--format オプションの値が esm に変わり、 --out-extention;.js=.mjs が追加されている。

/{省略}/node_modules/.bin/esbuild
app.ts
--bundle
--platform=node
--outdir=/private/tmp/sam-app/.aws-sam/build/HelloWorldFunction
--target=es2020
--minify
--format=esm
--out-extention:.js=.mjs

細かいesbuildビルド設定の書き方

じゃあSAM CLIのドキュメントに書かれていない設定項目以外は template.yml に書けないのかというとそうでもなく。
esbuildのコマンドオプションのケバブケースをパスカルケース置き換えてあげればよい

このことに言及しているissue

github.com

Boolean値を設定する例: Tree shaking
→ 最終的には --tree-shaking オプションがesbuildに渡される

Metadata:
  BuildMethod: esbuild
  BuildProperties:
    TreeShaking: true

複数文字列値を設定する例: Main fields
→ 最終的には --main-fields=main,module オプションがesbuildに渡される

Metadata:
  BuildMethod: esbuild
  BuildProperties:
    MainFields: main,module

だがこれにも例外がある。

esbuildはコマンドに --tsconfig オプションを渡すと tsconfig.json ファイルのパスを指定することができるが、 tsconfigの設定項目だがSAM CLIがデフォルトでesbuildコマンドに渡しているオプション(例: Format, Minify) についてはtsconfigの内容が無視される。esbuildコマンド内でtsconfigファイルよりもコマンドラインオプションを優先する事になっているため、tsconfigファイルに書いても設定が効かないのである。

例えばこのような tsconfig.jsontemplate.yml があったとすると、以下のようなesbuildコマンドが実行される。

{
  "compilerOptions": {
    "target": "ES2018",
    "module": "esnext",
    "moduleResolution": "Node",
    "esModuleInterop": true,
    "resolveJsonModule": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "allowJs": true,
    "sourceMap": true,
    "rootDir": "src"
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}
Metadata:
  BuildMethod: esbuild

ここで、sam build時にtemplate.ymlに書かれていない限りデフォルトでCommonJSで出力することから、 出力されるのはCommonJSなファイルである。

このことはSAMを利用しているかどうかに関係なくesbuildを使っているならば出会う可能性のある仕様だが、SAM経由でesbuildするとデフォルトでオプションがいくつかつくのと実行コマンドが隠匿されるので注意が必要である。

参考

build時にtemplate.ymlに書かれた任意の設定からesbuildのオプションを生成している箇所のPR

github.com