GitHub Enterprise の Actions で依存関係を S3 にキャッシュする
Thursday, May 05, 2022 11:35:00 AM
GitHub の Actions で npm などの依存関係パッケージをキャッシュするのは、公式ドキュメント 依存関係をキャッシュしてワークフローのスピードを上げる にも書いてあるように、 CIを高速化するのに重要です。
公式 Example だと以下のとおりです。
name: Caching with npm
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Cache node modules
uses: actions/cache@v3
env:
cache-name: cache-node-modules
with:
# npm cache files are stored in `~/.npm` on Linux/macOS
path: ~/.npm
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-build-${{ env.cache-name }}-
${{ runner.os }}-build-
${{ runner.os }}-
- name: Install Dependencies
run: npm install
- name: Build
run: npm build
- name: Test
run: npm test
actions/cache
を利用すれば問題なく動くのですが、Enterprise 版の場合は runs-on
にセルフホストランナーを利用するので、キャッシュがホストランナーのディスクに依存してしまいます。
これだとホストランナーのディスク管理とか大変になるので、少し管理で楽をしたいな、と思ったところ S3 にキャッシュできるものがあり、現在もこれを利用しています。
actions-s3-cache
actions-s3-cache は名前のとおり依存関係のキャッシュをS3に保持してくれます。 これならディスク容量を気にすることもないし、管理が楽になりますね。
公式 Example だと、こんな感じです。
steps:
- uses: actions/checkout@v2
- uses: shonansurvivors/actions-s3-cache@v1.0.1
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: us-east-1
with:
s3-bucket: your-s3-bucket-name # required
cache-key: npm-v1-${{ hashFiles('laravel/package-lock.json') }} # required ('.zip' is unnecessary)
paths: node_modules # required
command: npm ci # required
zip-option: -ryq # optional (default: -ryq)
unzip-option: -n # optional (default: -n)
working-directory: laravel # optional (default: ./)
僕らの運用では
- S3バケットは複数リポジトリで共有
- キー名にリポジトリ名を入れる
みたいな決め事を作ったので、こんな感じにしてみました。
- uses: actions/checkout@v2
- id: get-repository-name
run: |
IFS=/
REPO=${{ github.repository }}
set -- ${REPO}
echo "::set-output name=name::$2"
- uses: shonansurvivors/actions-s3-cache@v1.0.1
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: ap-northeast-1
with:
s3-bucket: ci-actions-cache
cache-key: ${{steps.get-repository-name.outputs.name}}-cache-${{ hashFiles('package-lock.json') }}
paths: node_modules
command: npm ci
get-repository-name
でリポジトリ名を抜き出してきて、 cache-key
で使っています。
cache-key
に一致するものがあれば、S3からダウンロードしてzipを展開、一致するものがなければ command
を実行して paths
にあるファイルを zip アーカイブして S3 に保持といったことをやってくれるので、便利ですね。
あとは S3 のバケットにファイルの有効期限を設定しておくようにすれば、古いキャッシュがいつまでも残ることもなく、とても便利です。
TypeORM でプライマリーキーや外部キーの名前を変更する
Wednesday, May 04, 2022 05:17:00 PM
TypeORM では、主キーやインデックスはエンティティにデコレータを記述します。 たとえば以下のような感じです。
@Entity('テーブル名')
export class UserEntity {
@PrimaryGenerationColumn()
id!: string;
}
@PrimaryGenerationColumn
にはインデックス名を指定できないので、この状況で migration:generate
を実行すると、
TypeORM 独自のルールを使ったインデックス名で SQL が生成されます。
一方でインデックスの場合は、たとえば email
にユニーク制約を入れるとすると以下のように、インデックス名を指定できます。
@Entity('テーブル名')
export class UserEntity {
@PrimaryGenerationColumn()
id!: string;
@Index('UQ_User_email', {unique: true})
@Column()
email!: string;
}
また関連に関してもそうで、 User - hasMany -> Post のような関連があったとき、外部キー制約の名前も TypeORM ルールになります。
@Entity('テーブル名')
export class UserEntity {
@PrimaryGenerationColumn()
id!: string;
@Index('UQ_User_email', {unique: true})
@Column()
email!: string;
@OneToMany(() => PostEntity, (post) => post.user)
posts?: PostEntity[];
}
@Entity('テーブル名')
export class PostEntity {
@PrimaryGenerationColumn()
id!: string;
@ManyToOne(() => UserEntity, (user) => user.posts)
user!: UserEntity;
}
そこで、 @Index
以外で生成されるインデックスの名前をカスタマイズしたい!という場合は NamingStrategy
を使います。
この NamingStrategy
ですが、ドキュメントには詳しい解説がないので、この記事を書くことにしました。
NamingStrategy の作り方
まずはインデックス名をカスタムできるように、 DefaultNamingStrategy
を継承し、 NamingStrategyInterface
を実装するクラスを作ります。
class CustomNamingStrategy extends DefaultNamingStrategy implements NamingStrategyInterface {
}
DefaultNamingStrategyのコード を参考にカスタマイズすると良いでしょう。
たとえばプライマリーキーの場合は以下のようになっています。
primaryKeyName(tableOrName: Table | string, columnNames: string[]): string {
// sort incoming column names to avoid issue when ["id", "name"] and ["name", "id"] arrays
const clonedColumnNames = [...columnNames]
clonedColumnNames.sort()
const tableName = this.getTableName(tableOrName)
const replacedTableName = tableName.replace(".", "_")
const key = `${replacedTableName}_${clonedColumnNames.join("_")}`
return "PK_" + RandomGenerator.sha1(key).substr(0, 27)
}
プライマリーキーに設定されているカラム名をソートしてから _
で結合して sha1 に変換したものを利用しています。
たとえばプロジェクトのルールで、プライマリーキーは PK_テーブル名
で良い場合は、以下のようなコードでオーバーライドして変更します。
class CustomNamingStrategy extends DefaultNamingStrategy implements NamingStrategyInterface {
primaryKeyName(tableOrName: Table | string, columnNames: string[]): string {
const table = this.getTableName(tableOrName);
return `PK_${pascalCase(table)}`;
}
}
pascalCase
は change-case のようなライブラリ利用を想定しています。
NamingStrategy で変更できる名前
NamingStrategyInterfaceのコードを見るとわかります。 よく使いそうなものは、以下のあたりでしょうか。
- primaryKeyName 主キー名。デフォルトでは
PK_{sha1した値}
- foreignKeyName 外部キー名。デフォルトでは
FK_{sha1した値}
- indexName 複合キー名。デフォルトでは
IDX_{sha1した値}
もちろんこれ以外にもたくさんカスタマイズできる名前はあるので、マイグレーションファイルに生成された名称がプロジェクトルールに沿わない場合に 遭遇したら、インターフェースやデフォルトの実装を見てみると良いでしょう。
カスタム NamingStrategy の指定方法
マイグレーションコマンドから利用する DataSource のオプションに指定します。
export const AppDataSource = new DataSource{
// データベース接続オプション
migrations: ['db/migrations/*.ts'],
namingStrategy: new CustomNamingStrategy()
});
namingStrategy
を指定して、 npm run migration:generate
するとプロジェクトルールに沿った名前でマイグレーションファイルを生成できます。
package.json
のスクリプト例
"migration:generate": "ts-node ./node_modules/.bin/typeorm migration:generate -d db/datasouce.ts db/migrations",
さいごに
公式ドキュメントの情報が不足しているところなので、この記事が少しでも役立てば幸いです。
TypeORM を 0.3 系にアップグレードする
Wednesday, May 04, 2022 11:34:00 AM
TypeORM 0.2 系をずっと使ってきて、 2022/03/23 の Dependabot 更新で、 0.3 系へのアップデートを確認しました。 CIのテストが失敗していたので、何か大きな変更があるのか Release notes を確認してみました。
すると、 0.3.0 と 0.3.1 の2度に BREAKING CHANGES があることがわかりました。
ただし後者 0.3.1
の BREAKING CHANGES については、 0.3.2 で元に戻されているので 0.3.0
の変更点を見ていきましょう。
0.3 系での変更点
- ormconfig のような接続オブションファイル利用は非推奨になった
- 非推奨の migrations:* コマンドが削除された
- CLI コマンドの前面見直し
- 未実行のマイグレーションとスキーマ同期の両方がある場合、マイグレーションがスキーマ同期の前に実行されるようになった
- aurora-data-api が aurora-mysql に変更
- aurora-data-api-pg が aurora-postgres に変更
- EntityManager.connection が EntityManager.dataSource に変更
- Repository のコンストラクタが新しくなった。カスタムクラスリポジトリは使えなくなった
- @TransactionRepository、@TransactionManager、@Transaction デコレータが削除された
- ジャンクションテーブル名だけが短縮されるようになった(@Entityデコレータでカスタム名を指定するのが理想的
- パラメータなしの findOne() は廃止
- findOne(id) は削除
- findOne, findOneOrFail, find, count, findAndCount メソッドは FindOptions パラメータのみに変更。その代わり FindOptions を使わずに where 条件を直接与えるために、新しいメソッドが追加された: findOneBy, findOneByOrFail, findBy, countBy, findAndCountBy
- findByIdsは非推奨となり、代わりにIn演算子と組み合わせてfindByメソッドを使用することが推奨となる
- findOne と QueryBuilder.getOne() は、データベースで何も見つからなかった場合、undefined ではなく null を返すようになった
- find* メソッドで使用される where の値として null はサポートされず、IsNull() 演算子を明示的に使用するようになった
- すべてのCLIコマンドはormconfigをサポートしなくなった
- DataSourceOptions 内のエンティティ、マイグレーション、サブスクライバのオプションにディレクトリ文字列を指定するのは非推奨になった
- すべてのコンテナ関連の機能 (UseContainerOptions, ContainedType, ContainerInterface, defaultContainer, useContainer, getFromContainer) は非推奨となった
- トランザクション内で使用される EntityManager の getCustomRepository は、非推奨となった。代わりに withRepository メソッドを使用する
- Connection.isConnected は非推奨になった。代わりに .isInitialized を使用する
- FindOptions (find* メソッドで使用) の select や relations でプロパティ名の配列指定は非推奨となった。代わりにオブジェクトリテラル表記を使用する
- FindOptions の join (find* メソッドで使用されます) は非推奨になった。結合を含むクエリを作成するには、QueryBuilder を使用する
- Connection、ConnectionOptions は非推奨になった。DataSource と DataSourceOptions を使用する
- createConnection(), createConnections() は非推奨になった。Connection は DataSource になった
- getConnection() は非推奨になった。グローバルな接続を持つには、データソースをエクスポートして必要な場所でそれを使用する
- getManager(), getMongoManager(), getSqljsManager(), getRepository(), getTreeRepository(), getMongoRepository(), createQueryBuilder() はすべて非推奨になった。これらは DataSource から取得する
- getConnectionManager() と ConnectionManager は非推奨になった。Connection は DataSource になり、各データソースは変数としてエクスポートできる
- getConnectionOptions() は非推奨になった
- AbstractRepository は非推奨になった
- Connection.name と BaseConnectionOptions.name は非推奨になった
めっちゃ、いっぱいありますね。
修正箇所の確認
修正箇所に関して、型が変わったものに関しては、 tsc
でコンパイルすればエラーになるので、対応箇所は明確です。
findオプションが変わったり、メソッド自体が無くなったりしたものが中心です。
コンパイルエラーが修正できたら、あとは単体テストでの失敗箇所の確認ですね。
非推奨になった部分にも目を向ける
最新のドキュメントは非推奨になった記述がすべてなくなり(そりゃそうですが)、新しい記述方法に変わっているのと、いつ 0.4.0
になっても良いように、
どのように修正したら対応できるかを考えておく必要があります。
また、最近新しいAPIサーバーを作ることになったので、その実装は 0.3
系で始めるようにしました(あえて古いバージョンを使う必要はないですしね)。
で、そこで問題になるのが、周辺ライブラリの対応状況です。
TypeORM に対応したテストデータ作成のライブラリはいくつかあるのですが、そういったものが 0.3
系に追従してくるまで待つか、そもそも依存ライブラリを使わずに faker-ts のような TypeScript のモックデータを作れるライブラリと TypeORM の create/save を組み合わせて使うかの選択になります。
今回の新しいAPIサーバーでは、後者のやり方で進めることにしました。いずれライブラリの対応が進んだとして切り替えるか、そのままにするかは検討の余地がありますが、それほど手間は変わっていない印象です。
ここからは、昨日の記事でもふれたのですが、私が関わっているプロジェクトでは DI フレームワークとして tsyringe を、 ORM で typeorm を使っていますので、その範囲でどのように記述しているかを紹介していきます。
Connection から DataSource への変更に対応する
TypeORM 0.2 系では以下の書き方が一般的でした。
if (getConnectionManager().has('default')) {
const conn = getConnectionManager().get('default');
if (conn.isConnected === false) {
await conn.connect();
}
} else {
await createConnection({ /* 接続オプションの指定 */ });
}
これが 0.3 系では以下のようになります。
let ds: DataSource;
if (container.isRegistered('DataSource')) {
ds = container.resolve('DataSource');
} else {
ds = new DataSource({ /* 接続オプションの指定 */});
container.register('DataSource', { useValue: ds });
}
if (!ds.isInitialized) {
await ds.initialize();
}
データソースのオブジェクトは tsyringe の DI コンテナに登録して利用できるようにしておきます。 さらに DB(リポジトリ)を、ユースケースクラスで DI できるようにします。
TypeORM 0.2 系では以下のようにしていました。
container.register('UserRepository', {
useFactory: instanceCachingFactory(() => getCustomRepository(UserDatabase))
})
TypeORM がグローバルにアクセスできる getCustomRepository
を用意してくれていたので、カスタムリポジトリクラスを指定するだけで良かったのですが、
ここでは getCustomRepository
が非推奨になったのと、クラスベースのカスタムリポジトリが非推奨になった、という2つの非推奨の影響を受け、書き方がだいぶ変わりました。
0.3 系では以下のようにしてみました。データソースを DI コンテナから取得して新しいカスタムリポジトリの書き方 extend
を使っています。
container.register('UserRepository', {
useFactory: instanceCachingFactory(() => {
const ds = container.resolve('DataSource');
return ds.getRepository(UserEntity).extend(UserDatabase);
}
})
クラスベースのカスタムリポジトリからオブジェクトベースへの変更に対応する
で、 0.2 系でのカスタムリポジトリは以下のようにクラスベースになっていたのです。
@EntityRepository(UserEntity)
export class UserDatabase extends Repository<UserEntity> implements UserRepository {
async findByEmail(email: string): Promise<UserEntity | undefined> {
// 検索処理
}
}
これを 0.3系では、クラスベースでなくオブジェクトベースに変更します。
export const UserDatabase: UserRepository & ThisType<Repository<UserEntity> & UserRepository> =
{
async findByEmail(email: string): Promise<UserEntity | null> {
// 検索処理
},
}
クラスベースからオブジェクトベースになって、とても困ったことがあります。 上記の変更では簡略化しましたが、実は find 系メソッドにはデコレータをつけていて、ロギングできるような仕組みを入れていました。
@EntityRepository(UserEntity)
export class UserDatabase extends Repository<UserEntity> implements UserRepository {
@Logging()
async findByEmail(email: string): Promise<UserEntity | undefined> {
// 検索処理
}
}
こんな感じです。 これがオブジェクトベースになると、デコレータが使えなくなります。 いい感じの解決策はあまりなかったので、AOPのライブラリ ts-aspect を使うことにしました。 AOPのライブラリはnpmレジストリにたくさんあるので、どれか自分の好みにあうものを利用すれば良いと思います。
で、0.3 系ではデコレータを使わず、AOPで以下のように対応しました。
container.register('UserRepository', {
useFactory: instanceCachingFactory(() => {
const ds = container.resolve('DataSource');
const repository = ds.getRepository(UserEntity).extend(UserDatabase);
addAspect(repository, 'findByEmail', Advice.Around, new LoggingAspect());
return repository;
}
})
カスタムリポジトリをインスタンス化するときに、いったん変数 repository
にしてから ts-aspect
を使って、メソッド単位で仕込んでいきます。
マイグレーションやCLIの変更に対応する
CLIオプションが変更になっているので、マイグレーションの書き方も変更が必要です。
0.2 系では package.json
の scripts
にこんな感じで書いていたと思います。
"migration:generate": "ts-node ./node_modules/.bin/typeorm migration:generate -f db/ormconfig.ts -n Migration",
ormconfig.ts の中身は以下のような感じでした。
module.exports = {
// データベース接続オプション
migrations: ['db/migrations/*.ts']
cli: {
entitiesDir: 'domain/entities',
migrationsDir: 'db/migrations',
subscribersDir: 'db/subscribers',
}
};
0.3 系では、まずスクリプトが以下のように変更になります。
"migration:generate": "ts-node ./node_modules/.bin/typeorm migration:generate -d db/datasouce.ts db/migrations",
-f
オプションがデータソース -d
指定となり、マイグレーションファイルの出力先を最後に指定するようになりました。
datasource.ts
は以下のようになります。データソースに変わった関係で、 cli
オプションはなくなっています。
export const AppDataSource = new DataSource{
// データベース接続オプション
migrations: ['db/migrations/*.ts']
});
migration:run や migration:show は、データソースに指定した migrations
のパスからファイルを探索してくれるので、 -f
が -d
に変わったぐらいの影響範囲で大丈夫です。
さいごに
typeorm は 0.2 -> 0.3 -> 0.4 と 0.x 系なので 0.1 ごとに破壊的変更が実施されてきます。 バージョン 2.0 -> 3.0 -> 4.0 にすれば良いのに… とか思いますが。
次の 0.4 系に備えて、修正の方針を見つけていきたいですね。 もしこの記事が参考になれば幸いです。
DependabotをGHEのActionsとPackagesで利用する
Tuesday, May 03, 2022 02:40:00 PM
以前に DependabotをGHEのプロジェクトに適用する という記事を書きましたが それから状況は変わり CI は drone.io から GHE の Actions を使うように、プロジェクト内のプライベートパッケージは Packages に入れるようになりました。
すべて GHE のプラットフォームに寄せられたのは良かったのですが、現時点まだ Dependabot はβ版のようなので、ひとまずセルフホストランナーを使って実行できるようにしてみました。
name: dependabot
on:
workflow_dispatch:
schedule:
- cron: 'お好みのスケジュールで'
jobs:
dependabot:
runs-on: self-hosted
env:
PROJECT_PATH: ${{ github.repository }}
BRANCH: develop
PACKAGE_MANAGER: npm_and_yarn
GITHUB_ACCESS_TOKEN: ${{ secrets.GITHUB_ACCESS_TOKEN }}
GITHUB_ENTERPRISE_ACCESS_TOKEN: ${{ secrets.GITHUB_ENTERPRISE_ACCESS_TOKEN }}
GITHUB_ENTERPRISE_HOSTNAME: ${{ secrets.GITHUB_ENTERPRISE_HOSTNAME }}
NPM_REGISTRY_URL: npm.github.hoge.com
NPM_TOKEN: ${{ secrets.PACKAGES_DOWNLOAD_TOKEN }}
steps:
- run: docker pull dependabot/dependabot-core
- name: run dependabot
run: |
rm -rf dependabot-script
git clone https://foo:${GITHUB_ACCESS_TOKEN}@github.com/sizuhiko/dependabot-script.git
cd dependabot-script
docker run -v "$(pwd):/home/dependabot/dependabot-script" \
-w /home/dependabot/dependabot-script dependabot/dependabot-core bundle install -j 3 --path vendor
docker run --rm -v "$(pwd):/home/dependabot/dependabot-script" \
-w /home/dependabot/dependabot-script \
-e PROJECT_PATH=${PROJECT_PATH} \
-e BRANCH=${BRANCH} \
-e PACKAGE_MANAGER=${PACKAGE_MANAGER} \
-e GITHUB_ACCESS_TOKEN=${GITHUB_ACCESS_TOKEN} \
-e GITHUB_ENTERPRISE_ACCESS_TOKEN=${GITHUB_ENTERPRISE_ACCESS_TOKEN} \
-e GITHUB_ENTERPRISE_HOSTNAME=${GITHUB_ENTERPRISE_HOSTNAME} \
-e NPM_REGISTRY_URL=${NPM_REGISTRY_URL} \
-e NPM_TOKEN=${NPM_TOKEN} \
dependabot/dependabot-core bundle exec ruby ./generic-update-script.rb
GHE では self-hosted
ランナーを使うのが一般的なのですが、そこに dependabot を入れるのではなく、 dependabot-core
の Docker イメージを使って実行できるようにします。
これは以前の記事と同じ実行方法ですね。
で、drone.io の場合と異なり Actions で該当のリポジトリで cron ビルドするようにしています。 cron のスケジュールは日次なり、週次なりで良いでしょう。
設定する環境変数
以下の環境変数でコントロールしています。
- BRANCH PRの向き先のブランチ名。git flow の場合は develop
- PACKAGEMANAGER パッケージマネージャ名。npmやyarnの場合は npmand_yarn
- GITHUBACCESSTOKEN GitHubのアクセストークン。GHEのシークレットに入れておくと良いです
- GITHUBENTERPRISEACCESS_TOKEN GHEのアクセストークン。GHEのシークレットに入れておくと良いです
- GITHUBENTERPRISEHOSTNAME GHEのホスト名。GHEのシークレットに入れておくと良いです
- NPMREGISTRYURL プライベートレジストリのURL
- NPM_TOKEN パッケージダウンロード権限のあるPAT(GHEのPersonal Access Token)。GHEのシークレットに入れておくと良いです
dependabot-script を fork してみた
さて、あとは dependabot-core
のイメージ内で dependabot-script
を動かせば良いのですが、 dependabot-script
が Github Pakages への参照ができないため、 fork して少し修正をしています。
dependabot-core
はプライベートレジストリの認証に対応しているので、 scriptを少し修正して対応しています。
なお、 dependabot-script
にも同様の対応のPR Read private repository credentials from environment が出ているので、いずれ公式に対応されるかもしれないです。
とくに GitHub Packages みたいなプライベートレジストリを使っていないのであれば、公式の dependabot-script
を使ってもらえば良いと思います。
@swc-node/jest を使ってテストを高速化する
Tuesday, May 03, 2022 11:07:00 AM
TypeScriptを使ってサーバーサイドのAPIサーバーなどを作っていて、テスティングフレームワークとして Jest を使っているとき まずは ts-jest を使いはじめると思います。
ts-jestはTypeScriptのコードをJestでテストするのに大変便利で、とても使いやすいのですが、コード量が多くなってきたときCI/CDにおける テスト時間が問題になってきます。
Jest + TypeScript のテストを高速化するノウハウ
このあたりはすでに多くのブログ記事が出ているように、ググるとたくさんの情報が出てきます。
などなど…
これらで書かれているもので、共通してくるワードが
です。 どちらも esbuild(Goで書かれている) や swc(Rustで書かれている) といった高速のトランスパイラを使って実行するので、 Jestのテストが速くなります。
TypeScript も様々
では実際に esbuild や swc を簡単に導入できるかということです。 TypeScript で、特にサーバーサイドの実装の場合、いろいろな機能を使っている場合があります。 たとえば DI であったり、 ORM であったり。 私が関わっているプロジェクトでも DI フレームワークとして tsyringe を、 ORM で typeorm を使っています。 これらのライブラリは TypeScript の DI や ORM としては一番に思いつく有名どことで特にマニアックなライブラリではないと思います。
で、これらのライブラリを使う上では、 tsconfig に以下の設定を入れる必要があります。
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
さて、そこで問題になるのが esbuild です。 esbuild ではデコレータのサポートがないので、まず候補から外しました (もちろん esbuild-decorators という拡張があるのも知ってはいますが、公式ではないうえ、これ自体がTypeScriptで書かれているので実行速度的に結局美味しくないというのがあります)。
そうすると、 swc を使うことになるのです。 swc はデコレータにも対応しており、理想的です。
@swc/jest の使い勝手
@swc/jest
を使って速くなったという記事のソースコードは単純なサンプルであることが多く、実際に単純に導入するだけではうまくいきません。
もちろんうまく動作するプロジェクトもそれなりにはあるでしょうが。
swc では tsconfig を解釈してくれるわけではないので、 .swcrc でコンフィグを書く必要があります。 これが微妙に tsconfig と違うので、ビルドでは swc を使わず webback と serverless framework で AWS Lambda にデプロイするような APIサーバーでは設定を合わせるのが面倒です。
@swc-node/jest という解決策
そこで調べていくと、 @swc-node という swc をより node.js 環境に特化したラッパーがあることを知りましt。
で、 @swc-node
には @swc-node/jest というパッケージがあり、
これは @swc/jest
と対になっている関係ですね。
@swc-node
も tsconfig を解釈してくれるわけではないのですが、オプションの項目は tsconfig を一致しているので特に迷いなく設定できます。
ちなみに 1.5系からは tsconfig を読み込むように変更されているのですが、現時点私たちのプロジェクトではビルドが失敗するので、まだ 1.4 系を利用しています。 問題はすでに issue TypeScript path mapping is not working. になっており、 パスエイリアスを使っているときに、うまくファイルが import できないところなのですが、これが解決されれば transformer の設定も不要になるので、 とても便利になるはずです。
ところで、この @swc-node
ですが、当初(私たちが採用を決めたとき)は @Brooooooklyn さんの個人リポジトリにあったと思うのですが、現在は @swc-project
配下に移動しています。
プロジェクトの一部になったというのは、一つ安心材料ですね。
@swc-node
も TypeScript で書かれていて、 esbuild-decorators
と同じじゃないのか?という話はあるかもしれませんが、 @swc-node
に関していうと、これは tsconfig の設定値を swcrc にマッピングするラッパーという感じなので、動作時は swc 相当となります(実際に測定値もGitHubに出ている)。
もし、サーバーサイドのTypeScriptプロジェクトで同様の課題を持っている人がいたら、 @swc-node
の利用を試してもらえると良いかなと思っています。
私たちはCIの時間を劇的に改善することができました。
Recent Articles
- マルチプルレポをモノレポへコミットログを残しながら移行する 2023/09/27
- tsyringe を TypeScript 5 で使う方法 2023/05/02
- LocalStack を使って aws-sdk の Integration Test を実行する 2023/04/19
- AWS SDK v3 のモジュールと利用方法 2023/04/18
- ts-jest が esbuild/swc をトランスフォーマーに使って高速化していた 2023/04/13
- aws-sdk v3 を使うライブラリを作ったときは、なるべく peerDependencies に設定しよう 2023/04/11
- aws-sdk v2 が 2023 年中にメンテナンスモードになる 2023/04/06
- Node.js v18 / aws-sdk v3 の Lambda アプリが突然動かなくなる 2023/04/05
- aws-sdk v3 でコンパイルエラーになる - その2 2023/04/04
- aws-sdk v3 で TS2345 が出てコンパイルエラーになる 2023/04/03