5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Bunを駆使して学ぶNode.jsモジュール開発

Posted at

目次

  • はじめに
  • Bunとは
  • 開発手順
    • 構成と環境
    • Bunインストール
    • 関数作成
    • クラスの作成
    • テスト実行
    • 型定義とbuildファイル作成
    • モジュールの利用
  • 終わりに
  • 参考文献

はじめに

Nodeモジュールを開発する方法はいくつかありますが、Bunを使ってモジュール開発をしたので今回はそれを紹介します。(パッケージの公開は行いません)

Bun とは

Bunは、特に「速度」と「効率性」を重視して設計された新しいランタイムです。

Nodeの場合、パッケージマネージャはnpmやyarnを使い、TypeScriptのトランスパイルはtypescriptパッケージを使います。

Bunの場合、自身がパッケージマネージャ(npm互換)やバンドラーの機能をもっています。
さらにTypeScriptのトランスパイルもデフォルトで可能で、トップレベルでawaitが使えます。
タスクランナーとしての使い方もできます。

{
  "name": "testapp",
  "scripts": {
    "clean": "rm -rf ./build"
  }
}

bun run <コマンド> で実行できます。
そのほか各種詳細についてはGithubのREADMEで確認できます。

開発手順

構成と環境

Bunの開発環境はVSCodeのRemote-Containerを使用しています。
以降説明するサンプルはこちらのGithubリポジトリをから参照できます。参考までに。

リポジトリ構成
├── .devcontainer
│   ├── devcontainer.json
│   ├── docker
│   │   └── bun
│   │       └── Dockerfile
│   └── docker-compose.yml
├── build
│   ├── index.d.ts
│   ├── index.js
│   └── packages
│       ├── calcs
│       │   ├── index.d.ts
│       │   └── index.js
│       └── clients
│           ├── index.d.ts
│           └── index.js
├── bunfig.toml
├── bun.lockb
├── package.json
├── README.md
├── src
│   ├── index.ts
│   ├── packages
│   │   ├── calcs
│   │   │   └── index.ts
│   │   └── clients
│   │       └── index.ts
│   └── tests
│       ├── calcs.test.ts
│       └── clients.test.ts
└── tsconfig.json

Bunインストール

Bunインストール
$ curl https://bun.sh/install | bash

$ bun --version
1.0.x

なお前述した開発環境サンプルではDockerfileで環境を定義しているので、左下の「><」押下後、「コンテナで再度開く」を選択すると数分で開発環境が立ち上がります。

今回使用する基本的なコマンドは以下の通りです。

$ bun run <指定>
スクリプト実行コマンド
js/tsファイルの実行、またはpackage.jsonに記述したスクリプトを実行

$ bun add <パッケージ名>
パッケージを指定してインストール

$ bun remove <パッケージ名>
インストールしたパッケージを削除

$ bun test <ファイルパス>
テスト実行

今回は使いませんが、テンプレートを指定して新規プロジェクトを作成するbun create <指定>もあるので、Bunでプロジェクト環境をたちあげることもできます。詳しくは公式ドキュメントを参照ください。

関数作成

はじめにbun installを行い、開発環境を整えます。

$ bun install
bun install v1.0.19 (906f86d6)

Checked 6 installs across 7 packages (no changes) [119.00ms]

関数を作成します。

/workspace/src/packages/calcs/index.ts
interface Addition {
  /**
   * first number
   */
  first: number;

  /**
   * second number
   */
  second: number;
}

/**
 * Addition numbers
 */
export const addition = (props: Addition) => {
  const { first, second } = props;
  return first + second;
};

テストを作成します。

/workspace/src/tests/calcs.test.ts
import { expect, test } from "bun:test";
import { addition } from "../packages/calcs";

test("addition test", () => {
  const result = addition({
    first: 3,
    second: 25,
  });
  expect(result).toEqual(28);

  const failResult = addition({
    first: 10,
    second: 20,
  });
  expect(failResult).not.toBe(10);
});

クラスの作成

クラスを作成します。

/workspace/src/packages/clients/index.ts
type Options = {
  /**
   * suffix for name
   */
  suffix?: string;
};

/**
 * Test Client class
 */
export class TestClient {
  _options?: Options;
  name: string;
  /**
   * TestClient constructor.
   *
   * @example
   * ```js
   * import {TestClient} from 'bun-sample'
   * const testClient = new TestClient();
   * ```
   *
   * @param options - Configuration options.
   */
  constructor(name: string, options?: Options) {
    this.options(options);
    this.name = name;
  }

  /**
   * Set options.
   *
   * @param options - Configuration options.
   */
  options(options?: Options): void {
    this._options = options;
  }

  /**
   * Get name
   */
  getName(): string {
    const name = this.name;
    const suffix = this._options?.suffix;
    return `${name}${suffix ?? ""}`;
  }
}

テストを作成します。

/workspace/src/tests/clients.test.ts
import { expect, test } from "bun:test";
import { TestClient } from "../packages/clients";

test("client test", () => {
  const name = "太郎";
  const testClient = new TestClient(name);
  const result = testClient.getName();
  expect(result).toBe(name);

  const suffix = "さん";
  const expectResultWithSuffix = `${name}${suffix}`;
  testClient.options({ suffix });
  expect(testClient.getName()).toBe(expectResultWithSuffix);
});

テスト

テストを実行します。
実行ファイルを指定するときはbun test <テストファイルパス>で実行できます。
BunではテストをJestライクに定義しています。すべてJest仕様にしているわけではありませんが、サポート可否はこちらのドキュメントを参照してください。

テストを実行すると以下のように出力されます。

$ bun test
bun test v1.0.19 (906f86d6)

src/tests/calcs.test.ts:
✓ addition test [7.17ms]

src/tests/clients.test.ts:
✓ client test [56.44ms]

 2 pass
 0 fail
 4 expect() calls
Ran 2 tests across 2 files. [2.51s]

bun test --cavarageを実行するとカバレッジを表示することができます。また、bunfig.tomlにデフォルトの設定を行うこともできるので今回はbunfig.tomlを作成してデフォルトでカバレッジを表示してテストを実行するように設定します。

bunfig.toml
[test]

# always enable coverage
coverage = true
$ bun test
bun test v1.0.19 (906f86d6)

src/tests/calcs.test.ts:
✓ addition test [15.51ms]

src/tests/clients.test.ts:
✓ client test [31.61ms]
-------------------------------|---------|---------|-------------------
File                           | % Funcs | % Lines | Uncovered Line #s
-------------------------------|---------|---------|-------------------
All files                      |  100.00 |  100.00 |
 src/packages/calcs/index.ts   |  100.00 |  100.00 | 
 src/packages/clients/index.ts |  100.00 |  100.00 | 
-------------------------------|---------|---------|-------------------

 2 pass
 0 fail
 4 expect() calls
Ran 2 tests across 2 files. [3.77s]

buildファイル作成

モジュールを作成するあたり、コンパイルの設定を行います。
こちらの「tsconfig.jsonの主要オプションを理解する」がそれぞれのオプションがどのように機能するかをわかりやすくまとめているので参考にしてみてください。今回のサンプルでは以下のように設定しました。

tsconfig.json
{
  "compilerOptions": {
    "esModuleInterop": true,
    "declaration": true,
    "declarationDir": "build",
    "outDir": "build",
    "baseUrl": "./",
    "paths": {
      "*": ["build/*"]
    },
    "types": ["bun-types"]
  },
  "include": ["src/**/*.ts"],
  "exclude": ["node_modules", ".devcontainer", "src/tests"]
}
package.json
{
  "name": "bun-sample",
  "description": "bun sample module",
  "module": "index.ts",
  "license": "UNLICENSED",
  "main": "./build/index.js",
  "types": "./build/index.d.ts",
  "scripts": {
    "test": "bun test --timeout 10000",
    "build": "bunx tsc",
    "clear:build": "rm -rf ./build"
  },
  "files": [
    "build"
  ],
  "devDependencies": {
    "bun-types": "latest"
  },
  "peerDependencies": {
    "typescript": "^5.0.0"
  }
}

scriptsにタスクランナーとしてbuildを設定しました。型定義を出力してみます。

$ bun run build

実行すると プロジェクトルートディレクトリにbuildフォルダと型定義ファイルが作成されます。

モジュールの利用

作成したモジュールを外部で利用するには

  • githubリポジトリを直接指定する
  • パッケージを公開する

の2つがあります。
今回は直接リポジトリを指定してプロジェクトにインストールして確認します。

使用したいプロジェクトのpackage.json > devDependenciesに以下のように登録します。

{
  "name": "testproject",
  "version": "0.1.0",
  "type": "module",
  .
  .
  .
  "devDependencies": {
    "bun-sample": "git+ssh://git@github.com:xxxxxx/xxxxx.git#main"
  },
}

npm installを実行してnode_modules以下にbun-sampleがインストールされます。
最後に、テストで記述したようにモジュールを呼び出して利用することができます。

index.ts
import {TestClient} from 'bun-sample'

const testClient = new TestClient("test")
.
.
.

終わりに

他の検証記事やコミュニティの議論を見ると、場合によってNodeやDenoが速かったりするので常にBunが最速というわけではないようです。また、古くからあるサービスや依存モジュールの解決などはまだ課題としてあるようです。(参考文献に紹介のLambda検証は特に)
とはいえ、トランスパイルがデフォルトで可能な点はTSでプロジェクトを作成したり、検証環境をサクッと整える分には有効な感じもします。

BunのコンセプトはAll-In-One-Toolkitにある通り、JavaScript の優れた点をすべて捨てずに、遅さと複雑さを排除することです。既存ライブラリやフレームワークは引き続き動作させつつ、現在Node.jsの課題である複雑に積み重ねられた選択肢をよりシンプルにすることがBunの使命としているので、その点を丸く解決できることがBunの魅力でもあり今後も注目に値する点かなと感じます。

以上、Bunを使ってモジュール開発を行いました。
もっと多くに人に利用してもらいコミュニティ規模が大きくなるといいですね。

参考文献

5
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?