はじめに
この記事は、Bun Advent Calendar 2023 4日目の記事です。
JSConf JP 2023について
先月の11/19(日) に「JSConf JP」という、JavaScriptの一大Festivalが開催されました。
そしてその中で「Bunがメジャーリリースされたけど、本当にBunはNode.jsに取って代るほどすごいのか?をAWS Lambdaで検証してみた」という(長いタイトルの)内容で登壇させて頂きました。
※聞いてくださった方、ありがとうございました!
今回のJSConf JPのセッションではかなりレア(もしかしたら唯一?)のAWS & 100% バックエンドの内容だったので、どうかなあという感じでしたが、結果的には多くの方が聞きに来てくれて、良かったという感じでした。
あとは本番の魔物(接続トラブル)さえなければ...
で、今回はそのJSConf JPのセッションで話しきれなかったことについての話になります。
ちなみに、発表資料はこちらになります。
アジェンダ
- 結局、Bunって実際の実行速度はそうでもないの?
- 「Bun(Node.jsビルド)」って、何?
- 「TypeScriptで動かす」の補足
- 「ビルドファイルが動かない現象を回避する方法」の補足
結局、Bunって実際の実行速度はそうでもないの?
資料の中で「AWS Lambda(以下Lambda)ではBunは思ったほど早くなかった」という検証結果を話しました。
それについて、発表後に「Webサーバーとかではどうですか」とか「Webサーバーとしては早いですよ」という意見を頂きました。
そこで調べたところ、確かにnode.js ネイティブやExpressではBunの方が速いという結果が出ているようです。
- https://medium.com/deno-the-complete-reference/node-js-vs-deno-vs-bun-express-hello-world-server-benchmarking-7bf44c197d2b
- https://medium.com/deno-the-complete-reference/node-js-vs-deno-vs-bun-native-http-hello-world-server-benchmarking-f48edd514513
ただし、Fastifyではnode.jsの方が速いようです。
また他にも「Bunの方が速い」という結果を出していた海外の比較サイトもありました。(サイトは失念...)
なので、結論から言うと「環境次第」ということなんでしょう。(自分がフロントは専門外なので、あまり突っ込んで調査はしていない)
ただBun自体、まだ9/8にメジャーリリースされたばかりですし、Bun公式サイト でトップページのトップでサーバーサイドレンダリングのベンチマーク結果を表示していることからも、Webサーバーとしての速度は今後どんどん速くなっていくんでしょうね。
「Bun(Node.jsビルド)」って、何?
上記「検証結果」の項目の中で「Bun(Node.jsビルド)」という項目があります。
それについて「『Bun(Node.jsビルド)』って、何?」という質問がありましたので、改めてここで記載します。
Bunにはビルドターゲットとして以下の3つがあり、それぞれビルド結果が異なります。(browserは今回は検証から除外)
- Bun: Bunランタイムに最適な形式でビルドされる
- node: Node.jsに最適な形式でビルドされる
- browser:Webブラウザで動作させるのに最適な形式でビルドされる
参考:Bun.Build
そして「Bun(Node.jsビルド)」は上記のターゲット:nodeでビルドした場合の結果になります。
ちなみにBun公式のbun-lambdaパッケージにも記載がある通り、BunをLambdaで動かす場合、API GatewayのeventはRequest形式に変換されます。
「TypeScriptで動かす」の補足
BunでLambdaを動かすことのメリットとして「TypeScriptのままLambdaにアップロードできる」という事を挙げました。
これに関する補足です。
具体的には、下記の手順を踏むことでBunでTypeScriptのままLambdaを実行する事が出来ます。
- Bun用のLambda Layerを作成する(これは先述のbun-lambdaパッケージで作成可能)
- Lambdaで使用するnpmモジュールを別途Lambda Layerとして作成する
- TypeScriptでLambdaを記載する
- 該当のLambda関数の設定で、1~2のLambda Layerを使用する設定を行う
ただし、下記のデメリットが発生するので注意です。
- Lambdaの実行時間が長くなる(これは資料内でも説明した通り)
- これだけでLambda Layerを2つ使う
- Lambda Layerは最大5つまで。(あまり意識する必要はないかも?)
「ビルドファイルが動かない現象の回避方法」の補足
また発表内で、BunをLambdaで動かす際の注意点として、下記を挙げました。
- node_modulesを使用すると、ビルド後のjs ファイル実行時に「(intermediate value).require is not a function 」エラーが発生する
- ビルド後のjsファイルの先頭に下記2行を追加することで回避可能
import { createRequire as createImportMetaRequire } from "module"; import.meta.require ||= (id) => createImportMetaRequire(import.meta.url)(id);
これについて、具体的には下記の手順で実行します。
- Bunのビルド設定ファイル (
build.ts
) を用意する build.ts
に下記ソースを記載する- Bunのビルド時に、下記コマンドのように
build.ts
を使用してビルドするようにする。
bun run build.ts
// build.tsの内容 import path from "node:path"; import process from "node:process"; import fs from "node:fs"; import { Transform, TransformCallback } from 'stream' // ビルドするファイルの設定。 // 最終的にビルドファイルのパスが取れればOK // ここでは1ファイルのみだが、もちろん複数ファイルでもOK const projectBaseDir = process.cwd(); const input_bun = path.resolve( projectBaseDir, "lambda/index_bun.ts" ); // ビルドファイルの出力先&拡張子設定 const output = path.resolve(projectBaseDir, "dist"); const ext = "mjs"; // targetはbunまたはnodeを指定 await Bun.build({ entrypoints: [input_bun], outdir: output, target: "bun", format: "esm", naming: `[dir]/[name].${ext}` }); // ビルドされたファイルを1つずつ読み込む。 // ビルドファイル名.bakという一時ファイルを作成する // Stream形式にしているのは、ビルドされたファイルのサイズが // 大きくてもOOMで落ちないようにするため const pathToBuildedFileBun = path.resolve(projectBaseDir, `dist/index_bun.${ext}`); const files = [pathToBuildedFileBun] for (const file of files) { const inputFile = fs.createReadStream(file, { encoding: "utf-8" }); const outputFile = fs.createWriteStream(`${file}.bak`, { encoding: "utf-8" }); // .bakファイルの先頭に回避用コードを記載 outputFile.write('import { createRequire as createImportMetaRequire } from "module"; import.meta.require ||= (id) => createImportMetaRequire(import.meta.url)(id);\n\n') // あとはビルドファイルのコードをそのままコピー const decoder = new TextDecoder(); const transformer = new Transform({ transform( chunk: Uint8Array, encoding: string, done: TransformCallback ): void { let chunkString = decoder.decode(chunk); this.push(chunkString) // 加工処理 done() }, }) // https://qiita.com/suin/items/8bf63cd457d75b709530 // https://qiita.com/masakura/items/5683e8e3e655bfda6756 inputFile.pipe(transformer).pipe(outputFile); // 最後に元のビルドファイルを削除して、.bakファイルの // ファイル名を元のビルドファイル名にリネーム fs.unlinkSync(file); fs.renameSync(`${file}.bak`, file); }
まとめ
以上、JSConf JPで説明しきれなかった点の補足でした。
今回の発表で、始めてBunを本格的に触ってみましたが、パッケージインストール・ビルド・テストなど、ローカル作業の速度に関してはかなり速く、とても魅力的でした。
また本番稼働させるには色々と壁がありますが、先述の通り、まだまだメジャーリリースされたばかりなので、今後の進化に期待ですね。
それでは、今回はこの辺で。
明日の Bun Advent Calendar 2023 もお楽しみに!