以下の内容はhttps://makky12.hatenablog.com/entry/2024/05/17/120500より取得しました。


【Bun.js】Bun がメジャーリリースされたけど、本当にBun はNode.js に取って代わるのか?の最新状況

今回のお題

Bun.js v1.0.12時点で出来なかった一部機能について、Bun.jsの最新版ではどうなったか...の検証記事

はじめに&宣伝

いきなり宣伝になってしまうのですが、本日(2024/05/19(金))発売の「Software Design 2024年6月号」誌において、「第2特集:[実証]Bun 次世代JavaScriptランタイムの実体に迫る」の記事を担当させて頂きました。

※ちなみに、担当したのは第1章と第3章です。

gihyo.jp

また記事内に記載の通り、「第3章:BunとNode.jsの徹底比較」の内容は、私が2023/11/19(日) に開催された、JSConf JP 2023 において発表した「Bun がメジャーリリースされたけど、本当にBun はNode.js に取って代わるのか?をAWS Lambda で検証してみた」の内容がベースになっています。(資料は以下)

speakerdeck.com

なおこの資料の中で「困った点」として、当時のBun.jsでは出来なかった点を挙げています。(当時のバージョンはv1.0.12)

しかしあれからBun.jsは頻繁にアップデートが行われ、日本時間で今年の4/1(月)に(マイナーアップデートバージョンである)v1.1.0がリリースされました。*1 *2

そこで今回は「Software Design記事のおまけ」的な感じで、上記の「困った点」について『最新のBun.jsではどうなっているか』の検証結果を記事にしようと思います。

アジェンダ

上記「困った点」の3つについての検証です。

前提

Bun.jsはv1.1.2で検証しています。(最新版でも同じ結果になるはず)

最新版が使えない(packages/bun-lambdaが動かない)

どんな現象?

Bun.js公式リポジトリ には、packages/bun-lambdaという、Bun.jsをAWS Lambdaで動かす際に必要なパッケージがあるのですが*3、これがエラーになってしまい動かない、という現象です。

最新版では?

最新版では上記現象は改修されており、問題なく動作します。(なお権限系のエラーは別途対応が必要です。Software Designの記事にも対応方法を記載しています)

ビルドファイルが動かない

どんな現象?

ビルド時(bun build) にnpmモジュールをバンドルすると、ビルド後のjsファイル実行時にエラーが発生してしまい、動かないというものです。

正常に動かすためには、ビルド後のjsファイルの先頭に以下2行を明示的に追加する必要がありました。

outputFile.write('import { createRequire as createImportMetaRequire } from "module";   
import.meta.require ||= (id) => createImportMetaRequire(import.meta.url)(id);\n\n');  

最新版では?

こちらも最新版では上記現象は改修されており、上記2行を追加しなくても問題なく動作します。

モジュールモック未対応

どんな現象?

Bun.jsのテスト用モジュール(bun:test)で、npmモジュールなどのモジュールモックは未対応だった。

最新版では?

最新版ではモジュールモックにも対応しており、npmモジュールのモックも可能です。(詳しくは公式ドキュメント を参照。またサンプルソースを末尾に記載しておきます)

またマッチャーのJest互換性について、かなりの数のマッチャーが対応済ですが、まだ未対応のマッチャーも存在します。

ちなみに、bun:test でLambda関数のテストを実施する際の注意点について、以下に記載しておきます。

レスポンスをtoEqual() するだけではダメ

単体テスト時のレスポンスについて、await expect(response).toEqual(expected) みたいに、単に戻り値をtoEqual()しただけでは正しく判定できません。(おそらく、レスポンスがResponseクラス(のインスタンス)であることに関係していると思われます。) *4

とりあえずの回避策として、APIGatewayProxyResultなど、別の形式に変換することで対応できます。(末尾のサンプルソースを参照)

aws-sdk-client-mock-jest のマッチャーが使えない

Lambda関数内でAWS SDKを使用している場合、単体テストaws-sdk-client-mock-jest を使用するケースも多いと思いますが、aws-sdk-client-mock-jest の一部マッチャーが使えません。*5

これはBunとJestで、expect関数の戻り値の型が違うのが原因です。

  • Bun:Expect<T>型
  • Jest:JestMatchers<T>型

また、そもそもBun読み込んだ時点でJestのexpect(declare const expect)が上書きされてしまい、aws-sdk-client-mock-jestとの整合性が取れなくなってしまうようです。

これについては今のところ有効な対策がなさそうで、強いて言えば「bun:test を使わない(Jestを使う)」くらいしかありません。(もしわかる人がいましたら教えてください)

まとめ

以上、最新のBun.jsによる検証結果でした。
基本的に「困ったこと」の現象は全て改修されており、最新版では問題なく使えることが確認できました。

実際Bun.jsは今でも頻繁にアップデートが実施されているので、これからもどんどん使い勝手が良くなっていくでしょうね。
今後のBun.jsの進化に期待です。

最後に繰り返しになりますが、Software Design 2024年6月号、よろしくお願いいたします。

それでは、今回はこの辺で。

参考:npmモジュールモック&レスポンス変換のサンプルソース

import { expect, test, describe, mock, jest as bun_jest } from 'bun:test';
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import { DynamoDBDocumentClient, GetCommand } from '@aws-sdk/lib-dynamodb';
import { mockClient } from 'aws-sdk-client-mock';
import { handler } from '../lambda/index_bun';
import { APIGatewayEvent, APIGatewayProxyResult } from "aws-lambda";
  
// npmモジュールをモックする例。  
// ここでは「uuid」をモックしています。
mock.module('uuid', () => {
  return {
    v4: bun_jest.fn(() => '1111-2222-3333-4444'),
  };
});
  
const dynamoDbOutput = {
  Type: 'treasure',
  Floor: '100'
};
  
// aws-sdk-client-mock やRequest、Responseなどで必要になる設定  
const testUrl = 'https://dummy.example.com';
const ddbMock = mockClient(DynamoDBDocumentClient);
const dynamodb = new DynamoDBClient({});
DynamoDBDocumentClient.from(dynamodb);
  
const eventData: APIGatewayEvent = {
  queryStringParameters: {
    "floor": "3"
  } 
} as unknown as APIGatewayEvent;
  
const headers = new Headers({
  'Content-Type': 'application/json'
});
  
const responseOptions = {
  status: 200,
  headers
};
  
const responseString = JSON.stringify({
    uuid: '1111-2222-3333-4444',
    item: dynamoDbOutput,
});
  
const expectedResponse = new Response(responseString, responseOptions);
  
describe('index_bun.handlerのテスト', () => {
  test('レスポンスが正しい事', async () => {
    ddbMock.on(GetCommand).resolves({
      Item: dynamoDbOutput,
    });
    
    const res = await handler(new Request(testUrl));
    
    // ResponseインスタンスをAPIGatewayProxyResult型に変換
    const [resResult, expectedResult] = await Promise.all([createApiGatewayProxyResultResponse(res), createApiGatewayProxyResultResponse(expectedResponse)]);
    
    expect(resResult).toEqual(expectedResult);
  });
});
  
// ResponseインスタンスをAPIGatewayProxyResultに変換する関数
async function createApiGatewayProxyResultResponse(res: Response): Promise<APIGatewayProxyResult> {
  const contextType = res.headers.get('Content-Type');
  const body = await res.json();
  const result = {
    statusCode: res.status,
    headers: {
      "Content-Type": contextType,
    },
    body: JSON.stringify(body),
  } as APIGatewayProxyResult;
  
  return result;
}

*1:なお執筆時点での最新版はv1.1.8です。

*2:4/1という日付から「エイプリルフールネタなんじゃね?」という噂も流れました

*3:正確には「Bun.jsをAWS Lambdaで動かす際に必要になるLambda Layerを作成する」処理です

*4:レスポンスの内容が異なっていてもtoEqual()の結果がtrueになる。

*5:toHaveReceivedCommandWith()で確認




以上の内容はhttps://makky12.hatenablog.com/entry/2024/05/17/120500より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14