Technote

by sizuhiko

ts-jest が esbuild/swc をトランスフォーマーに使って高速化していた

昨年 @swc-node/jest を使ってテストを高速化する という記事を書きました。 その時点で ts-jest でのテストが遅くて、 @swc-node/jest に切り替えていました。

その後 @swc-node/jest もなんやかんやあって、たまに動かなくなったりして issue 投稿して直してもらったりいろいろあったのですが、最近 ts-jest の状況を見てみたら、こんな記述がありました。

Starting from v28.0.0, ts-jest will gradually opt in adoption of esbuild/swc more to improve the performance. To make the transition smoothly, we introduce legacy presets as a fallback when the new codes don’t work yet.

from https://kulshekhar.github.io/ts-jest/docs/getting-started/presets

なんと、高速化のために esbuild/swc を使うようになったって。まじかー

早速 ts-jest に変えてみた

@swc-node/jest から ts-jest に変更して、 jest.config.jspreset に変更。 テストを実行してみると、確かに速くなってる!

手元のプロジェクトだと、 ts-jest にかかる時間は、 @swc-node/jest + tsc の時間とほぼ一致していました。 ts-jest ではコンパイルエラーも検知されるので、つまりそういうことでしょう。

また ts-jest で大丈夫!

ということで、 jest の実行に swc や esbuild を使うことや、tsconfig と違う設定を考慮したりとかなく、TypeScript のコードを jest でテストできるようになりました。

私たちのプロジェクトは、早速すべて ts-jest@29 に切り替えました。 もし問題がある場合は、 Preset にレガシーモードを指定すると esbuild や swc を使わないようにできるようですが、そもそも一度 swc に切り替えてテストできていれば、問題なく ts-jest に戻れるはずです。

今後は TypeScript + jest での開発環境に ts-jest のご利用をお勧めします。

aws-sdk v3 を使うライブラリを作ったときは、なるべく peerDependencies に設定しよう

先日 aws-sdk v2 が 2023 年中にメンテナンスモードになる という記事を書いて、実際自分たちで作っていた aws-sdk v2 でできていた CLI ツールも aws-sdk v3 に切り替えました。

CLIツールを v3 に移行したら、利用するアプリ側がコンパイルエラーになる

で、それを Lambda にデプロイするアプリの devDependencies に反映してみたところ、また型エラーが出るようになりました。

CLIツールでは @aws-sdk/client-lambda@3.310.0 に依存していて、 Lambda アプリ側は @aws-sdk/client-lambda@3.254.0 を使っていたためです。

error TS2345: Argument of type ‘typeof LambdaClient’ is not assignable to parameter of type ‘InstanceOrClassType>’. Type ‘typeof LambdaClient’ is not assignable to type ‘ClassType>’.

で、それぞれの型定義への依存は "@aws-sdk/types": "*" になっているので、lock ファイルの状態次第でそれぞれでバージョンが異なるものが入る場合があります。 そこで型が一致しない問題が発生してきます。

CLI ツールでは実行時に aws-sdk が必要になるので、 dependencies に設定していて、CLIツールを使う方のアプリは devDependencies に設定しているわけですが、それぞれの dependencies からインストールされる @aws-sdk/types がドッチ?問題になってしまうという…

解決策

今回は自分たちで作ったライブラリだったので、CLIツール側の aws-sdk を devDependencies に変更して、CLIツールを使うアプリ側の aws-sdk 利用状況に依存するように変更しました。

  "peerDependencies": {
    "@aws-sdk/client-lambda": "^3.0.0"
  },

これでアプリ側が @aws-sdk/client-lambda を使っていればそのバージョンに依存するようになるし、使っていなければ 3系の最新が入るようになります。

ここで、この aws-sdk v3 シリーズを読んでいただいた懸命な皆様はお気づきだと思いますが Node.js v18 / aws-sdk v3 の Lambda アプリが突然動かなくなる でも書いた

peerDependencies が指定されていたら、バージョンを揃えるために自分でインストールする

これが重要になってきます。 なので、自分で作ったライブラリで peerDependencies に指定したら、結局アプリ側で使ってなくてもバージョンを揃えるのに追加でインストールした方が良いということです。

aws-sdk v3 を安心して使うためには(追記)

  • 基本的にバージョンは最新版、もしくは最新に近い同じバージョンに揃える
  • peerDependencies が指定されていたら、バージョンを揃えるために自分でインストールする
  • 関数/クラス以外、たとえば enum の値などは基本的に利用しない
  • 自作ライブラリで aws-sdk v3 に依存する場合は peerDependencies に指定する

大事なこと4つ目を追加しました。

aws-sdk@v3 のパッケージ管理に日々不安が募りますが、同様の問題に遭遇した人の解決に役立てれば幸いです。

aws-sdk v2 が 2023 年中にメンテナンスモードになる

aws-sdk v2 を使っている CLI ツールで aws 環境を操作していたら、見慣れないメッセージが出てきました。

(node:9129) NOTE: The AWS SDK for JavaScript (v2) will be put into maintenance mode in 2023.

AWS SDK v2 ってメンテナンスモードに入るんだ…

Twitter でも書いている人がいました

これまでも複数回にわたって aws-sdk v3 の記事を書いてきましたが、これは Lambda にデプロイするアプリだから v3 に移行したのであって、CLIツールとかなら v3 に移行するモチベがあるかな….

ぶっちゃけ Lambda とかだとランタイムに追従すると sdk も上げないといけないので対応したけど、ツールなら対応しないですよね。

公式のリポジトリにも、サポートについて書かれています。

https://github.com/aws/aws-sdk-js#version-2x-support

そこにリンクされている メンテナンスポリシーページ にいくと、以下のような内容が記入されていました。

メンテナンス (フェーズ 3) -メンテナンス モードの間、AWS は SDK リリースを制限して、重大なバグ修正とセキュリティ問題のみに対処します。SDK は、新規または既存のサービスの API 更新を受け取ることも、新しいリージョンをサポートするために更新されることもありません。特に指定がない限り、メンテナンス モードのデフォルト期間は 12 か月です。

サポート終了 (フェーズ 4) - SDK がサポート終了に達すると、更新またはリリースを受け取ることができなくなります。以前に公開されたリリースは引き続きパブリック パッケージ マネージャーから入手でき、コードは GitHub に残ります。GitHub リポジトリはアーカイブされる場合があります。サポートが終了した SDK の使用は、ユーザーの判断で行われます。ユーザーが新しいメジャー バージョンにアップグレードすることをお勧めします。

v2 自体がリポジトリから無くなることはなさそうですが、膨大にありそうな v2 を使った CLI ツール群はどうするのかなぁ…

ちなみに Serverless Framerwork は PR Suppress AWS SDK v2 deprecation message でいったんメッセージ出力を OFF にしていますが、 v3 移行は大変だろうなぁ、という感想しかないです。

AWS SDK v3 は、これまでの記事でも書いてますが、結構パッケージ管理が残念な感じだったりするのと、書き方もめっちゃ変わっているので Serverless Framework 規模になると書き換えもかなり工数かかりそうだなぁと(いつかはやるんだろうけど)。

AWS さんが簡易にコンバートできるツールを作ってくれると良いんだろうけど、オプションの書き方も変わっているところがあったりするので、単純な変換も大変そうですね。

上記の引用ツイートのレスの中にもあるけど、v3 のドキュメントが不親切だというのは事実だし、良い解決策が 2023 年中に見つかると良いですね。 なんか感想ブログになっちゃいましたが、僕らのプロジェクトも v2 依存している CLI ツールは移行対象にしてなかったので、これから移行計画作らないとな、と思っています。

Node.js v18 / aws-sdk v3 の Lambda アプリが突然動かなくなる

「Lambda アプリが突然動かなくなる」なんて、どうせバグなんでしょーというのが当然のリアクションですが、これは本当にバグなのか…

本当にあった怖い話をします。

ある日、Lambda アプリが突然動かなくなる

Node.js v18 のライタイムで動く Lambda にデプロイされているアプリで、ある日突然エラーが出るようになりました。

original: TypeError: Cannot read properties of undefined (reading ‘RUNNING’)

この部分ですが TypeScript のコードでは以下のようになっていました。

import {
  DescribeExecutionCommand,
  ExecutionDoesNotExist,
  ExecutionStatus,
  SFNClient,
  StartExecutionCommand,
  paginateListExecutions,
} from '@aws-sdk/client-sfn';

// 省略

if (latest?.status === ExecutionStatus.RUNNING) {

ExecutionStatus は StepFunctions のクライアントで export されている値です。 StepFuntions のジョブが実行中かどうか判定している箇所で。少し前まで普通に動いていたのに….

Lambda アプリの構成

AWS Lambda は aws-sdk がランタイムにグローバルインストールされているので、最新版に追従して問題なければパッケージに含める必要はありません。 Lambda にデプロイするアプリケーションのサイズを少しでも減らすために、 aws-sdk は入れないようにしています。

node コンソールで確認してみる

手元では devDependencies に @aws-sdk/client-sfn が入っているので、 node コンソールに入って require を実行してみました。

> const { ExecutionStatus} = require('@aws-sdk/client-sfn')
undefined
> ExecutionStatus
{
  ABORTED: 'ABORTED',
  FAILED: 'FAILED',
  RUNNING: 'RUNNING',
  SUCCEEDED: 'SUCCEEDED',
  TIMED_OUT: 'TIMED_OUT'
}

ふむ、問題なさそうだが?

最新版の aws-sdk で変わったのだろうか? 一時的に最新版にバージョンアップをしてみて、確認してみます

> const { ExecutionStatus} = require('@aws-sdk/client-sfn')
undefined
> ExecutionStatus
undefined

えええええええーーー

aws-sdk の該当箇所の修正履歴をチェックする

hore(codegen): export enums as const

元のコード

/**
 * @public
 */
export enum ExecutionStatus {
  ABORTED = "ABORTED",
  FAILED = "FAILED",
  RUNNING = "RUNNING",
  SUCCEEDED = "SUCCEEDED",
  TIMED_OUT = "TIMED_OUT",
}

変更後のコード

/**
 * @public
 * @enum
 */
export const ExecutionStatus = {
  ABORTED: "ABORTED",
  FAILED: "FAILED",
  RUNNING: "RUNNING",
  SUCCEEDED: "SUCCEEDED",
  TIMED_OUT: "TIMED_OUT",
} as const;

おいおい、これって @public なのに後方互換性なくなっとるだろ….

なぜ undefined になるのか

enum はコンパイルされて JavaScript になると以下のようなコードになります。

var ExecutionStatus;
(function (ExecutionStatus) {
    ExecutionStatus["ABORTED"] = "ABORTED";
    ExecutionStatus["FAILED"] = "FAILED";
    ExecutionStatus["RUNNING"] = "RUNNING";
    ExecutionStatus["SUCCEEDED"] = "SUCCEEDED";
    ExecutionStatus["TIMED_OUT"] = "TIMED_OUT";
})(ExecutionStatus = exports.ExecutionStatus || (exports.ExecutionStatus = {}));

でも export const as const になると、JavaScript にはコンパイルされることなく、変数の利用箇所に埋め込みの形になります。

最初にも書きましたが、 aws-sdk v2 では普通だった

AWS Lambda は aws-sdk がランタイムにグローバルインストールされているので、最新版に追従して問題なければパッケージに含める必要はありません。 Lambda にデプロイするアプリケーションのサイズを少しでも減らすために、 aws-sdk は入れないようにしています。

こういう運用はすでにオワコンなんでしょうか? いやでも、v3 になってもランタイムに aws-sdk は入っています。

少なくとも ExecutionStatus が内部変数で export されてない private な値だったら良いのですが、これはさすがにマズイんじゃないですかね?awsさん。

この問題が発生するケース

  • AWS Lambda でランタイムの aws-sdk を使っていて、TypeScript から enum の値を参照していた場合(同一の変更ですべての enum が const に書き変わっています)

amplify とか Lambda でなくコンパイルされているケースでは問題は起きませんが、いやこういうのサクッと変更しちゃダメだと思うんですが… しかもランタイム側に入っているので、動作している Lambda アプリケーションが突然動作しなくなります。

aws-sdk v3 を安心して使うためには(追記)

  • 基本的にバージョンは最新版、もしくは最新に近い同じバージョンに揃える
  • peerDependencies が指定されていたら、バージョンを揃えるために自分でインストールする
  • 関数/クラス以外、たとえば enum の値などは基本的に利用しない

大事なこと3つ目を追加しました。

aws-sdk@v3 のパッケージ管理に日々不安が募りますが、同様の問題に遭遇した人の解決に役立てれば幸いです。

aws-sdk v3 でコンパイルエラーになる - その2

先日の aws-sdk v3 で TS2345 が出てコンパイルエラーになる という記事でも書いたとおり、 @aws-sdk/client-xxxxx を追加するときは

新しく @aws-sdk/client-xxxx を追加するときは、既存のクライアントも含めてすべて同じバージョンに変更する

のがオススメなのは変わりがないのですが、これだけだとうまく対応できないことがあったので、別記事として書いておきます。

ある日の変更前依存パッケージ

  "devDependencies": {
    "@aws-sdk/client-dynamodb": "^3.264.0",
    "@aws-sdk/client-s3": "^3.264.0",
    "@aws-sdk/client-s3-control": "^3.264.0",
    "@aws-sdk/client-secrets-manager": "^3.264.0",
    "@aws-sdk/lib-dynamodb": "^3.264.0",
  }

DynamoDB, S3, Secret Manager を利用するようになっていました。

パッケージを追加したらエラーになる

で、そこに @aws-sdk/lib-storage というパッケージを追加しようとしたところ、また先日と同様に TS2345 でコンパイルエラーになります。 ちゃんとバージョンをすべて最新にしてみたのですが、ダメでした。

ふたたび issue を探る

そうすると、issue ではなく discussions に変更されてしまったものを発見しました。

Peer dependencies not pinned for lib-dynamodb

なぜこんなことになるのか

やはりパッケージ管理が崩壊しているとしか思えないのですが、何でこうなっているのかというのを解説していきます。

以下は、今回追加しようとした @aws-sdk/lib-storagepackage.json の一部です。 追加時点の lib-storage のバージョンが 3.272.0 だったので、上記の変更前依存パッケージでインストール済みだったものも 3.272.0 に更新済みです。

  "dependencies": {
    "@aws-sdk/middleware-endpoint": "3.272.0",
    "@aws-sdk/smithy-client": "3.272.0",
    "buffer": "5.6.0",
    "events": "3.3.0",
    "stream-browserify": "3.0.0",
    "tslib": "^2.3.1"
  },
  "peerDependencies": {
    "@aws-sdk/abort-controller": "^3.0.0",
    "@aws-sdk/client-s3": "^3.0.0"
  },

peerDependencies の依存がありますね。

で、すでにインストール済みだった @aws-sdk/client-dymanodbpackage.json の一部も見てみましょう。

  "dependencies": {
    "@aws-crypto/sha256-browser": "3.0.0",
    // 省略
    "@aws-sdk/smithy-client": "3.272.0",
    "@aws-sdk/types": "3.272.0",
    // 省略
    "uuid": "^8.3.2"
  },

依存が多いのでちょっと省略しましたが、注目したいところだけ書きました。 こちらには peerDependencies の依存はありません。

@aws-sdk/client-s3 は上記のとおりインストール済みだったので、 @aws-sdk/abort-controller を見てみましょう。

  "dependencies": {
    "@aws-sdk/types": "3.289.0",
    "tslib": "^2.3.1"
  },

おや? @aws-sdk/types のバージョンが違うじゃん…

“@aws-sdk/abort-controller”: “^3.0.0”,

なぜ、ここは ^3.0.0 なのだ?? というのが、最初のディスカッションでもふれられている訳ですが、本当にパッケージ管理が (ry

対応案

使っている aws-sdk のパッケージで peerDependencies になっているのを調べて、すべてバージョン指定でインストールします。

ということで変更後の依存は以下のとおりにします。

    "@aws-sdk/abort-controller": "3.272.0",
    "@aws-sdk/client-dynamodb": "3.272.0",
    "@aws-sdk/client-s3": "3.272.0",
    "@aws-sdk/client-s3-control": "3.272.0",
    "@aws-sdk/client-secrets-manager": "3.272.0",
    "@aws-sdk/lib-dynamodb": "3.272.0",
    "@aws-sdk/lib-storage": "3.272.0",
    "@aws-sdk/smithy-client": "3.272.0",
    "@aws-sdk/types": "3.272.0",

abort-controller, smithy-client, types が利用しているパッケージの中で peerDependencies にあったので、すべてバージョンを揃えてコンパイルエラーは解消されました。

aws-sdk v3 を安心して使うためには

  • 基本的にバージョンは最新版、もしくは最新に近い同じバージョンに揃える
  • peerDependencies が指定されていたら、バージョンを揃えるために自分でインストールする

この2つとても大事です。

こんなの discussions に変更した上で放置していて良いのか?…

とても悲しい状況ですが、同様の問題に遭遇した人の解決に役立てれば幸いです。