Bun のオンザフライinstallと、キャッシュ。

Qiita の Advent Calendar 2023 にこの可愛いアイコンでお馴染みの Bun で何か書こうと決めたのだけど、今回は使ってみて意外に便利かなと思ったので Bun>Auto-install あたりをやってみようと思います。

<img width=300 src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7103/e2a7294a-f82b-9e4b-7fa7-ca0206c880a9.png>

Node.js/npm で、パッケージモジュールがディスク内のあちこちに分散し、無数に同じモジュールを重複して落としまくり、将来のひっ迫を心配しながら管理したり、挙句には、どの階層の node_modules を参照してるのかもわからなくなったりするのは、正直気持ち悪いですよね。

でもまぁ、Node.js が好きなので、やむを得ず我慢してたのですが、Bun を使って頭を切り替えると、そのあたりはすっきりしてかつ高速になり気持ち良いかも？と思う今日この頃です。

#### 注意
Bun はまだ2023/9/8にv1.0がリリースされたばかりで、まさに発展途上にあります。また、この記事は、私が2023/11現在のDocを元に勝手に推測した事柄も含まれています。したがって、内容に完全な補償のできるものではないことをご了承ください。

#### 今回の環境
クラウド： Azure VM (これは何でも良い)
OS：　Ubuntu 20.04.6 LTS (GNU/Linux 5.15.0-1050-azure x86_64)
Bun：　v1.0.11
Node.js： v20.9.0

#### Bunのサイト
ホームページ： https://bun.sh/
ドキュメント： https://bun.sh/docs
ガイド： https://bun.sh/guides
ブログ(主な更新履歴)： https://bun.sh/blog
Github： https://github.com/oven-sh/bun
Twitter：　https://twitter.com/jarredsumner

Bun は、上記の説明などをなどをいろいろ読んでると、なにげに速いことが判ります。

<img src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7103/513416bd-13c3-c3dd-1eb1-c30221cf35b6.png>

上の画像は WebSocket の1秒当たりメッセージ送信数の比較ということですが、Node.js のあの速い ws モジュールより 6倍速いんだぜっ、へへっ。と言ってるわけです。

更に、HTTP server では Node.js より4.7倍、sqlite ではbetter-sqlite3 より3.8倍速いと主張しています。凄いです。

まぁ、もちろんベンチなので、他の条件が加わると変わります。そして、最後には自分のシステム環境でどうなんだ？という事が大事なので、妄信せずに「ほー」と眺めておくことにしましょう。

それはそれとして、Bun のモジュールインストールも npm などに比べて圧倒的に速いことが判ります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7103/9b7e6e30-5255-d5c9-7b47-767a647f5f30.png)

いつもの僕らの Node.js/npm で 10.58秒かかるところを Bun なら29倍速い 00.36秒す。というのです。

このあたりは体験するとすぐにわかりますが、ネットを探すと既に他の方々がしっかりと試されていますので、リンクを置いておきますね。

https://qiita.com/watany/items/c0397822f351d4b3812b 

# オンザフライで取りに行く

更に、何はともあれと色々試してたら、どうやら Bun は、
npm install <パッケージ名> 的なこれ
```
$ bun install <パッケージ名>
$ bun i <パッケージ名>
```
などをしなくても、オンザフライで取りに行くらしいことも判りました。そこで今日は、このあたりを確かめようと思います。※オニオンフライではないです＜肉まんではないのか？

# とりあえず試してみよう

これは、モジュール crypto-js で sha3 のハッシュを取得するコードです。
```js:例 show-sha3.js
const CryptoJS = require("crypto-js");
let msg='test'
const tokenHash = CryptoJS.SHA3(
        msg,
        { outputLength: 256 }
    ).toString(CryptoJS.enc.Hex)
console.log(tokenHash)
```
これを、いきなりNode.jsで実行すると、
```sh
$ node show-sha3.js
```
crypto-js モジュールが install されてないので、
```
Error: Cannot find module 'crypto-js'
```
となるけど、Bunで実行すると、
```
$ bun show-sha3.js
```
ちゃんと動いて一瞬で CryptoJS.SHA3 のこんなハッシュが返るのです。
```
9c22ff5f21f0b81b113e63f7db6da94fedef11b2119b4088b89664fb9a3cb658
```
crypto-js モジュールを明示的に別途npm i や bun i などでインストールしていないのに (いないから) です。

# 何が起きたのか？

Bun には Auto-install(自動インストール) という仕組みがあります。

#### Bun>Auto-install
https://bun.sh/docs/runtime/autoimport

スクリプトを初めて実行したときに、<b>その作業ディレクトリと、それ以上のディレクトリに node_modules がみつからないと、</b> Bun は Node.js スタイルのモジュール解決を放棄し、 Bun モジュール解決アルゴリズムを使用します。

すなわちこの時、Bun はそのスクリプトに必要なモジュールを自動で高速にインストールしキャッシュします。　

そして、次回スクリプトを実行するときは、キャッシュされたバージョンが使用されます。

先ほどのスクリプトの crypto-js モジュールは、この Auto-Install 機能によりインストールされエラーも無く実行されたわけです。


この時、どのバージョンをインストールするかなどのアルゴリズムは、Auto-installの [Document](https://bun.sh/docs/runtime/autoimport) にあります。 

<b>注意：　</b>この Auto-Install 機能と Bun のパッケージ管理システムの恩恵に浴するためには、「その作業ディレクトリと、それ以上のディレクトリに node_modules がみつからない」ことが必要です。つまり、もし node_modules があるなら、rm -rf node_modules や winなら rmdir /s /q node_modules などで、上の階層まで node_modules を削除してクリーンアップしてから Bun を試してみることをお勧めします。


# これはつまり
bun run ＜スクリプト＞だけで良いということです。

いままで、コツコツやっていた npm install ＜パッケージ＞ を繰り返す作業が全て不要になるわけです。

一般的に普通のプログラムは1回は動作テストしますよね。Bunでは、そこでパッケージモジュールが高速(上で見たように npm の29倍もの速度で)ダウンロードされてキャッシュされます。そして次回以降は更に高速になるのです。



# どこにインストールされた？
Bun 自身 は、下記のコマンドなどでインストールできますが
```sh
$ curl -fsSL https://bun.sh/install | bash 
```
#### Bun>Installation
https://bun.sh/docs/installation

このときBunはここにあります
```sh
$ which bun
~/.bun/bin/bun 
```
したがって、上で書いた bun show-sha3.js は
```
$ ~/.bun/bin/bun  show-sha3.js
```
でも良いわけです。
さて、それでは、先ほどオンザフライでインストールされた crypto-js モジュールはどこにあるでしょう？

ここです。

```
$ ls -l ~/.bun/install/cache
total 16
drwxr-xr-x 2 tato tato 4096 Nov 25 20:59 crypto-js
drwxr-xr-x 3 tato tato 4096 Nov 25 20:59 crypto-js@4.2.0
-rw-rw-r-- 1 tato tato 7008 Nov 25 20:59 eb9aba24b08d6c30.npm
```
簡単に説明すると、この場合 crypto-js@4.2.0 が通常の node_modules に入ってるのと同じモジュールです。 crypto-js は crypto-js@4.2.0 へのハードリンク。eb9aba24b08d6c30.npmはその設定データです。

~/.bun/install/cache というディレクトリに、すべてのモジュールがダウンロードされます。

# cache と node_modules のクリーンアップ

因みに、Bunをいろいろ試してる時にはこのディレクトリをクリアするとすっきりリセットできて便利です。（万が一他のプロジェクトの cache を消しても 1回動かせばまた高速にキャッシュされます＾＾；）
```sh
rm -rf ~/.bun/install/cache/*
```
ついでに、プロジェクト内の node_modules があるならそれも削除します。
```sh
rm -rf node_modules
```
各プロジェクト内の node_modules に重複して無数のモジュールを抱え込み肥大化していく Node.js/npm とはその辺が大きく異なっています。

# 依存関係の各バージョンがディスク上の 1 つの場所にのみ存在するのは合理的

Bunでダウンロードされたモジュールは、~/.bun/install/cache　の一か所だけにまとめてキャッシュされます。バージョンもハードリンクでまとめられているのでほぼディスクを圧迫することがありません。

これに対して、Node.js/npm は ファイルを各プロジェクトごとにファイルを全てダウンロードするので、同じモジュールを何度でも重複して落します。その結果、ディスクスペースの無駄使いと逼迫を避けられません。また同じものを繰り返し落とす作業時間も全くの無駄です。

また、Node.jsは、node_modules を順次上のデイレクトリ階層に自動で辿り、スクリプトが使うモジュールを探す仕組みにもなっています。その結果、スクリプトがどの node_modules を参照してるのかもわからなくることもあり、探索の時間も無駄になります。

まぁ、とはいえ、Node.js好きではありますので、無数に同じモジュールを重複して管理しているのは、正直気持ち悪くても我慢してました。でも、Bun を使って頭を切り替えると、そのあたりはすっきりしてかつ高速になり気持ち良いかもしれません。
# 気になることは？

Auto-install は、node_modulesだけでなく、package.json すらなくても動作します。となると、今までプロジェクトで package.json にバージョン書いて固定してしてたのをどうするのかとか script のコマンドどうすんの？とか気になってきます。

まぁ、バージョン固定は、スクリプト内で
```
import axios from 'axios@1.6.0';
```
などと固定すれば、package.json に2重に書く必要もないと言えますが、試してみると(v1.0.11現在)、ひとつのバージョンを落とした後に、別のスクリプトで別のバージョンを指定すると Auto-install をしてくれませんでした。

まぁ、そんな場合は、
```
$ bun i <パッケージ名> 
```
すれば、そのプロジェクト内に npm i  <パッケージ名> したのと同様のインストールが行われて、同時に ~/.bun/install/cache へもダウンロードされるので、それ以降は使える事になります。

因みに、HTTPクライアントライブラリ axios を bun i <パッケージ名> で複数落としたときの ~/.bun/install/cache/axios は次のようにハードリンクされており、import axios from 'axios＜@バージョン＞' などで呼び出せます。

```sh
$ ls -l ~/.bun/install/cache/axios
total 0
lrwxrwxrwx 1 tato tato 41 Nov 26 12:26 1.4.0 -> /home/tato/.bun/install/cache/axios@1.4.0
lrwxrwxrwx 1 tato tato 41 Nov 26 12:25 1.5.1 -> /home/tato/.bun/install/cache/axios@1.5.1
lrwxrwxrwx 1 tato tato 41 Nov 26 12:12 1.6.0 -> /home/tato/.bun/install/cache/axios@1.6.0
lrwxrwxrwx 1 tato tato 41 Nov 26 12:23 1.6.1 -> /home/tato/.bun/install/cache/axios@1.6.1
lrwxrwxrwx 1 tato tato 41 Nov 26 12:06 1.6.2 -> /home/tato/.bun/install/cache/axios@1.6.2
```


# 自動インストールは、次のような理由で役立ちます。
この Auto-install のメリットが本家に箇条書きされていますので見てみましょう。

><b>スペース効率—</b> 依存関係の各バージョンは、ディスク上の 1 つの場所にのみ存在します。これは、プロジェクトごとに(高橋注：重複もする)冗長なインストールと比較して、スペースと時間を大幅に節約できます。

><b>利便性—</b> ファイルやスクリプトを実行する前に npm install または bun install を実行する必要はありません。bun run だけで良いのです。 

><b>下位互換性—</b> Bun は package.json に指定されたバージョンが存在する場合はそれを尊重するため、次の 1 つのコマンドで Bun-style を切り替えることができます rm -rf node_modules 

><b>移植性—</b> 単純なスクリプトと要点を共有するために、ソース ファイルは自己完結型になります。コードと構成ファイルを含むディレクトリを圧縮する必要はありません。バージョン指定子を使用した場合、import や package.json さえ必要ありません。(高橋注：圧縮というのはyarnがinstall 時に.yarn/cache へ node_modules の実体の zip を保存することなどを言っているのでしょうか？この辺かな？ https://github.com/oven-sh/bun/issues/56#issuecomment-964613900 )

一応、あまり長く書いても読むのが大変なので、今回の記事はここまでで終わります。Bun の魅力が少しは伝わったでしょうか。また、日々開発の進んでいるBun の未来が楽しみです。



# 最近書いたBun関連の記事
https://qiita.com/toshirot/items/a81f2073e373b0351493

https://qiita.com/toshirot/items/06a992af8cf8aca9ff95

https://qiita.com/toshirot/items/3af83303f95ac7baa546