📦

Rust で初めての npm package 開発

2024/11/11に公開

はじめに

筆者は最近 Rust の学習を始めました。
その中で、実際に何か動くものを作ってみたいと思い npm package 作ってみました。

作ったもの

日付を表示するライブラリです。

<script setup lang="ts">
import { today } from 'date-rs-t6'
</script>

<template>
  <!--Today: 2024/11/11(月) -->
  <p>Today: {{ today() }}</p>
</template>

https://www.npmjs.com/package/date-rs-t6

ながれ

全体的なながれは以下のようになっています。

  • Rust のコードをコンパイルし、WebAssembly や TS, JS, package.json を生成
  • それらを npm に publish
  • 実際に、Vite + Vue のプロジェクトで publish したパッケージをインストールして使用

詳細

WebAssembly

  • ウェブブラウザで実行できるコード
    • HTML, CSS, JavaScript, WebAssembly
  • .wasm という拡張子のバイナリファイル
  • C, C++, Rust などのコンパイル先とすることが可能

プロジェクトの作成

  • cargo を使ってプロジェクトを作成
    • cargo は Rust のビルドシステム兼パッケージマネージャ
  • 今回はライブラリを作るため、cargo new --lib を実行
  • 初期のディレクトリ構成は以下
    • Cargo.toml
    • src 配下に lib.rs
$ cargo new --lib sample-project
.
├── Cargo.toml
└──  src
    └── lib.rs

クレート

chrono

  • グレゴリオ暦の日付、時刻を操作するクレート
  • 今回はこちらを使って日付や曜日を取得

https://github.com/chronotope/chrono

src/lib.rs
use chrono::prelude::*;

fn today() -> String {
  let now: DateTime<Local> = Local::now();
  let day_of_week = match now.weekday() {
    Weekday::Mon => "月",
    Weekday::Tue => "火",
    Weekday::Wed => "水",
    Weekday::Thu => "木",
    Weekday::Fri => "金",
    Weekday::Sat => "土",
    Weekday::Sun => "日",
  };

  format!("{}({})", now.format("%Y/%m/%d").to_string(), day_of_week)
}

wasm-bindgen

  • Rust で実装した関数を JS が呼び出したり、Rust が JS の API を実行できる
  • #[wasm_bindgen] という属性を付与して使う

https://github.com/rustwasm/wasm-bindgen

src/lib.rs
use chrono::prelude::*;
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn today() -> String {
    let now: DateTime<Local> = Local::now();
    let day_of_week = match now.weekday() {
        Weekday::Mon => "月",
        Weekday::Tue => "火",
        Weekday::Wed => "水",
        Weekday::Thu => "木",
        Weekday::Fri => "金",
        Weekday::Sat => "土",
        Weekday::Sun => "日",
    };

    format!("{}({})", now.format("%Y/%m/%d").to_string(), day_of_week)
}

wasm-pack

  • WebAssembly をパッケージとして、npm レジストリに公開可能
  • 今回は、build, login, publish しか使っていないが、他にも以下のようなコマンドがある
  • より詳細な内容は以下の記事を参照
  • build は、target オプションで bundler を指定
Commands:
  build    🏗️  build your npm package!
  pack     🍱  create a tar of your npm package but don't publish!
  new      🐑 create a new project with a template
  publish  🎆  pack up your npm package and publish!
  login    👤  Add an npm registry user account! (aliases: adduser, add-user)
  test     👩‍🔬  test your wasm!
  help     Print this message or the help of the given subcommand(s)

https://github.com/rustwasm/wasm-pack

$ wasm-pack build --target bundler
pkg
├── README.md
├── sample-project.d.ts
├── sample-project.js
├── sample-project_bg.js
├── sample-project_bg.wasm
├── sample-project_bg.wasm.d.ts
└── package.json
$ wasm-pack publish

Vite + Vue のプロジェクトで使用する

  • create vite で作成したプロジェクトで実際に動作確認を行う
  • 動作確認環境は以下
    • "vue": "3.5.12"
    • "typescript": "5.6.2"
    • "vite": "5.4.9"
$ npm i sample-project
$ npm run dev

実際に開発サーバーを起動すると以下のようなエラーになってしまいました。

これは公式のドキュメントにも記載があり、エラーを解決するには別途プラグインの導入が必要でした。

WebAssembly の ES モジュール統合の提案は現時点ではサポートしていません。 vite-plugin-wasm か、もしくは他のコミュニティーのプラグインを使用して対処してください。

https://ja.vite.dev/guide/features#webassembly

よって、最終的な vite.config.ts は以下のようになります。

vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import wasm from 'vite-plugin-wasm'
import topLevelAwait from "vite-plugin-top-level-await";


export default defineConfig({
  plugins: [
    vue(),
    wasm(),
    topLevelAwait()
  ]
})

これらの対応をすることで画面が正常に表示されました。

参考

GitHubで編集を提案

Discussion