ほかほかの肉まんのイラスト

Bun のマクロを使ってフィーチャートグルを実装する

Bun にはマクロはビルド時に実行される関数です。関数が返す値がインラインにバンドルファイルに埋め込まれます。マクロには、実行してインライン化した後に、デッドコードを削除するという特徴があります。この機能を使ってフィーチャートグルを実装してみましょう。

Bun にはマクロと呼ばれる機能があります。マクロはビルド時に実行される関数です。関数が返す値がインラインにバンドルファイルに埋め込まれます。

例として、ランダムな値を返す関数をマクロとして使用してみましょう。関数の定義は通常の関数と全く同じです。

random.ts
export const random = () => Math.random();

関数を使用する際に、import attributes を使用して、import する関数がマクロであることを指定します。

index.ts
import { random } from "./random" with { type: "macro" };
 
console.log(random());

このコードをビルドすると、random 関数を実行した結果がインラインに埋め込まれます。

bun build index.ts --outfile=dist/index.js
dist/index.js
// index.ts
console.log(0.6952389499101538);

確かに、バンドル後のファイルには random 関数が存在しません。

ユースケース

マクロの使い所を考えてみましょう。マクロは実行してインライン化した後に、デッドコードを削除するという特徴があります。この機能を使ってフィーチャートグルを実現ができます。フィーチャートグルとは、新機能をリリースする際に、新機能を有効にするかどうかを切り替えるフラグのことです。フィーチャートグルを利用することで、開発中の未完成の機能であっても、どんどんメインブランチにマージしていくことができます。

素朴に条件分岐で機能を出し分けると、新機能を有効にしない場合でも、新機能のコードがバンドルされてしまうので、バンドルサイズが大きくなってしまいます。マクロを使うことで、新機能を有効にしない場合は、新機能のコードがバンドルされないようにできます。

まずは、フィーチャートグルを実現するためのマクロを実装してみましょう。この実装は通常の関数の定義方法と何ら変わりません。

features.ts
import { aiSuggestion } from "./features.json"
 
export const isEnableAiSuggestion = () => aiSuggestion;

有効にする機能のフラグを定義するための JSON ファイルで用意されていることを想定しています。Bun では JSON ファイルを直接 import して JavaScript のオブジェクトとして扱うことができます。以下のような JSON ファイルを用意しておきましょう。

features.json
{
  "aiSuggestion": false
}

次に、フィーチャートグルを利用する側のコードの実装です。import する際に、type: "macro" を指定して、マクロとして import します。

TitleInput.tsx
import { isEnableAiSuggestion } from "./features" with { type: "macro" };
 
export const TitleInput = () => {
  return (
    <div>
      <label>Title</label>
      <input type="text" />
      {isEnableAiSuggestion() && <button>AI Suggestion</button>}
    </div>
  );
};

このコードでは、isEnableAiSuggestion 関数が true を返す場合に、<button>AI Suggestion</button> がレンダリングされます。Bun で jsx をビルドする時、tsconfig.json または jsconfig.json の設定を読み取ります。"jsx": "react" と設定しておきましょう。

tsconfig.json
{
  "compilerOptions": {
    "jsx": "react"
  }
}

それでは、ビルドしてみましょう。

bun build TitleInput.tsx --outfile=dist/TitleInput.js

ビルド後のファイルを見ると、確かに <button>AI Suggestion</button> が含まれていません。

dist/TitleInput.js
// TitleInput.tsx
var TitleInput = () => {
  return React.createElement("div", null, React.createElement("label", null, "Title"), React.createElement("input", {
    type: "text"
  }), false);
};
export {
  TitleInput
};

type: "macro" の指定を取り除いてビルドした結果と比較すると、確かに結果が異なることがわかります。

dist/TitleInput.js
// features.json
var aiSuggestion = false;
 
// features.ts
var isEnableAiSuggestion = () => aiSuggestion;
 
// TitleInput.tsx
var TitleInput = () => {
  return React.createElement("div", null, React.createElement("label", null, "Title"), React.createElement("input", {
    type: "text"
  }), isEnableAiSuggestion() && React.createElement("button", null, "AI Suggestion"));
};
export {
  TitleInput
};

まとめ

  • Bun のマクロはビルド時に実行される関数で、関数の返り値がインラインにバンドルファイルに埋め込まれる
  • マクロは実行してインライン化した後に、デッドコードを削除するという特徴がある

Contributors

> GitHub で修正を提案する
この記事をシェアする
はてなブックマークに追加

関連記事