`deno bundle`が非推奨になったので代替手段（swc、esbuild）

## `deno bundle`コマンドは非推奨になった

`deno bundle`コマンドはTypeScriptファイルをJavaScriptファイルに変換するコマンドです。

Deno v1.31以降、**`deno bundle`コマンドは非推奨になりました**。

https://deno.com/blog/v1.31#deno-bundle-deprecation

:::note warn
非推奨になった理由としては、[こちら](https://github.com/denoland/deno/issues/11073)や[こちらのissue](https://github.com/denoland/deno/issues/15463)で解説されています。

要約すると、

- `deno bundle`は元々サーバーサイド向けで、依存関係を1ファイルにまとめるだけの単純なバンドラーだった
- フロントエンド向けのバンドラーとして使うにはminifyやtree shaking、プラグインシステム、npm互換性、top-level-await対応などの多くの機能が足りない
- `deno bundle`にそのような機能を実装するよりも、既存のesbuildやswcなどの優れたツールを使うことをおすすめする

という事でした。
:::

この記事では`deno bundle`コマンドの代替手段である2つの方法について解説します。

## 方法1. deno_emitを使う

[deno_emit](https://github.com/denoland/deno_emit)はDeno社が公式に管理・配布しているライブラリです。

https://github.com/denoland/deno_emit

こちらのライブラリはRust製バンドラーである[**swc**](https://swc.rs/)をWeb Assembly化してJavaScriptから使えるようにしたものです。

https://swc.rs/

:::note
このライブラリ自体は現在もDeno CLIの内部で使用されているため、この先もメンテナンスが継続される可能性が高いです。
:::

使い方は以下のようになります。

```ts:バンドルする方法
import { bundle } from "https://deno.land/x/emit/mod.ts";

const url = new URL(import.meta.resolve("./path/to/file.ts"));
const { code } = await bundle(url);
console.log(code);

// Deno.writeTextFile("./path/to/file.js", code);
```

また、バンドルせずに1ファイルずつトランスパイルするには、以下のようになります。

```ts:バンドルせずトランスパイルする方法
import { emit } from "https://deno.land/x/emit/mod.ts";

const url = new URL(import.meta.resolve("./path/to/file.ts"));
const result = await emit(url);
```

上記の方法だと、下の画像のように依存関係も含めた全てのファイルがトランスパイルされます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/678625/bf80e49b-9485-a534-feae-a45eb4c8e2ad.png)


対象のファイルだけトランスパイルしたい時には、`emit`関数に対して`load`オプションを渡して制御します。

```ts:1ファイルだけトランスパイルする方法
const targetUrl = new URL(import.meta.resolve("./path/to/file.ts"));
const content = await Deno.readTextFile(url);

const targetUrlStr = targetUrl.toString();
const result = await emit(targetUrl, {
  load(specifier) {
    if (specifier !== targetUrlStr) {
      return Promise.resolve({
        kind: "module",
        specifier,
        content: "",
        headers: { "content-type": "application/javascript; charset=utf-8" },
      });
    }
    return Promise.resolve({
      kind: "module",
      specifier,
      content,
      headers: {
        // （http://esm.sh/react など）対象ファイルのURLに拡張子が無い場合は必要に応じて追加↓
        // "content-type": "text/typescript; charset=utf-8",
        // "content-type": "text/jsx; charset=utf-8",
        // "content-type": "text/tsx; charset=utf-8",
      },
    });
  },
});

console.log(result[targetUrlStr]);
```

## 方法2：esbuildを使う方法

[esbuild](https://deno.land/x/esbuild)を使うこともできます。

https://deno.land/x/esbuild

esbuildには[Deno向けプラグインが用意されている](https://deno.land/x/esbuild_deno_loader)ため、これを利用してDenoのコードをトランスパイルすることができます！


https://deno.land/x/esbuild_deno_loader

:::note
こちらのプラグインはDenoコアチームのlucaさんによって管理されています。
また、Deno公式フレームワークである[Fresh](https://fresh.deno.dev/)の内部でも使用されているため、今後もメンテナンスが継続される可能性が高いです。
:::

コード例は以下のようになります。

```ts
import * as esbuild from "https://deno.land/x/esbuild@v0.15.10/wasm.js";

import { denoPlugin } from "https://deno.land/x/esbuild_deno_loader@0.6.0/mod.ts";

const result = await esbuild.build({
  plugins: [denoPlugin()],
  // 対象ファイル名
  entryPoints: ["https://deno.land/std@0.173.0/async/mod.ts"],
  outfile: "./test.js",
  // バンドルするかどうか。
  // trueにすると依存関係も含めて1ファイルにまとまる。
  // falseにすると対象ファイルの内容のみがトランスパイルされる。
  bundle: true,
  format: "esm",
});

if (result.outputFiles) {
  for (const { path, contents, text } of result.outputFiles) {
    console.log(path);
    console.log(contents); // トランスパイル結果（Uint8Arrayのバイナリ形式）
    console.log(text); // トランスパイル結果（文字列）
    // await Deno.writeFile(path, contents);
  }
}

esbuild.stop();
```

:::note
上記はWasm版のesbuildを使用していますが、バイナリ版のesbuildを使う方法もあるので、[公式ドキュメント](https://github.com/lucacasonato/esbuild_deno_loader)を参考にしてみてください。

バイナリ版はwasm版より高速である一方、バイナリがローカルにDLされるのでディレクトリが汚れる、`--allow-run`パーミッションが必要になる、deno deploy非対応などのデメリットがあるため、速度を追い求める場面でなければwasm版で十分だと思われます。
:::

※一つ注意点として、プログラム側からimportするesbuildのバージョンと、プラグイン内で使っているesbuildのバージョンが一致しない場合、エディタで型エラーが発生することがあります。実行するにあたって問題はありませんが、気になる場合は適宜バージョンを調整してみてください。

## まとめ

- `deno bundle`はDeno v1.31以降非推奨
  -  おそらく2.0で削除される
- 代替手段は [deno_emit](https://github.com/denoland/deno_emit) か [esbuild](https://deno.land/x/esbuild) + [esbuild_deno_loader](https://deno.land/x/esbuild_deno_loader)
  - deno_emit
    - swc使用（Rust製）
    - wasm
    - deno_deploy対応
    - Denoコアチームから配布されている
  - esbuild + esbuild_deno_loader
    - Go言語製
    - wasmとバイナリのどちらを使用するか選べる
    - deno_deploy対応
    - esbuild_deno_loaderはDenoコアチームメンバーから配布されている

実質、Rust製バンドラーであるswcとGo言語製バンドラーであるesbuildの2択になっています。
とはいえどちらも充分高速に実行されるため、**どちらを選ぶかは本当に好み**になります。

筆者はesbuildのAPIの方がイケてるような気がしますが、DenoとswcはどちらもRust製で親和性が高いかなと思ったので、普段はdeno_emitの方を使うことが多いです。