タイトル通りです。
ぜ〜んぶ、Rubyから触れます!
続きを読むリリースしたよ。
変えたところをいくつか。
* でマッチ可能にしたclass App < Uzumibi::Router get '/hello' do |req, res| ... end get '/*' do |req, res| # catchallな処理を書く puts req.params[:*] #=> catchallしたslugが入ってる end end
こうですね。ただ、そういえばSinatraは *.jpg とか *.* もできると気づいた。こっからっす!
何言いよんねって感じの日本語になってしまった。
今、 uzumibi new -t serviceworker hoge みたいなコマンドを打つとServiceWorkerのプロジェクトが生成される。
make wasm && make serve するとWebサーバが立ち上がる。ブラウザでアクセスすると、Uzumibiアプリに対してリクエストを投げるためのフォームが表示されるので、色々操作するとリクエストが飛んでレスポンスが帰ってくる。404もわかる。

このUzumibiアプリが ServiceWorker のなかで動いているという塩梅。
RailsでやっているこれをUzumibiでやってみたという感じで、 lib/app.rb にあるDSLでルーティングを追加して make wasm しなおせば反映される*1
ただ、ServiceWorker固有の制限(スーパーリロードすると動作しないとか)が面倒だなと思って*2、fetch() リクエストをただの WebWorker に送ってUzumibiからレスポンスを返すような実装も作った。それが webworker テンプレート。WebWorkerはただの非同期実行なので、ServiceWorkerでできるワーカの保持やfetch()の自動乗っ取りなどをしてくれないけど、「別途サーバを起動せずにRubyでAPIを用意する」みたいな用途はWebWorkerが合ってるかも。用途に応じて使い分けてください。
とはいえブラウザ標準技術は勉強したばかりで何もわからずなのです...。
今度、動的に Uzumibi App をServiceWorkerにデプロイするようなサンプルを作ってみたいところ。
uzumibi new コマンドからの開発の流れは、基本的に以下のようなことを想定している。
uzumibi new で作られたファイルは何もしなくてもこれが動いて欲しいが、それなりの数サポートしているプラットフォームがあり、毎回手動で確認するのはアッ、ウッだった。
k1LoWさんが作っている runn は完全にこういうことの自動化にピッタリそうなので、AIの助けを借りながらCIに組み込んでみた。
Uzumibi-cli、PRとマージでrunnベースの受け入れテストが走るようになった。テンプレートがうっかり壊れる不安がなくなった〜https://t.co/DJsMUrfr5X
— Uchio Kondo💥 (@udzura) 2026年2月26日
始めは記法が難しかったがClaude Codeさんがググってくれたので徐々に理解できるようになった。
runn の良いところは、一つは上記の「サーバを立ち上げる」操作がバックグラウンド化できる(さらに、終了時の後始末も自動でやってくれる)点、もう一つは cdp ランナーがファーストクラスでサポートされている点だなと感じている。
例えば、 WebWorker テンプレートの場合こういう操作が想定されており、その性質上何かが表示されるだけではなく、「ブラウザでちゃんと動くか」が重要になる。
/ へリクエストを送るcdpを使えるため、ブラウザからfetchを送って結果を確認するという操作も自動化できる。まるでUzumibiのcliを検査するために作られたかのようなツールだなと感じている。
今後、例えば Cloudflare テンプレートでプロジェクトを作ったあと Durable Object を使ったコードを起動し、実際動くか試す...。みたいなテストも自動化できるのでは?と思っており、品質面で夢が広がる。
というかrunn、絶対仕事でも使おうと思った。肌感Rubyコミュニティの人にはあまり知られていなくて不思議。
という感じでUzumibiの方もじわじわ欲しい機能を作っているところ。
*1:ServiceWorkerはこの辺むずくて、unregisterしないとダメか? 動的にキャッシュパス変えるとかしても良いかもな...
*2:ServiceWorker の用途を見ると、例えば事前に画像をfetchしてキャッシュして返すみたいなのを想定してそうで、スーパーリロードで動かないのはそういう関係っぽい?
進捗はない... 本質的な課題からも逃げがち... ではありますが、最近mruby/edgeでやってることを殴り書きします。
しました。正直まだそれなりに機能も足りず、バグもあるでしょうが、遊んでやってください。
1.1.0 で mruby/c と同じぐらいの数のメソッドを実装した(つもりな)ので、Rubyっぽいコードを試しやすくなってる気がします。気持ちの問題かもしれない。なおこの記事を書いてる時点では 1.1.5 が最新ということになっています。
mruby/edgeとしてはあとは math, json, regexp, random そして mruby-compiler2 と、 mruby-compiler2 の動作に必要なemscriptenの関数を含んでいます。
これだけ詰め込んでもまだ容量はギリ 1 MB を切っている。
$ ls -lh docs/*.wasm -rwxr-xr-x 1 udzura staff 922K 2月 15 15:15 docs/playground.wasm*
regexp がRustのregexベースで、でかいことがわかっているので若干工夫したいと思っています。このgem欲しい!とかあったら教えてください。
あとはPlaygroundなどを支える技術を...。
続きを読むUzumibi 0.3 を出した。やっと
— Uchio Kondo💥 (@udzura) 2026年1月24日
- post, put, delete をサポート...
- クエリとrequest bodyをサポート...
- "/users/:id" って書けるようにした...
これでもう少しまともに使えるかなhttps://t.co/h8C5SWXXKt
リリースした。往生際悪く(?)ブログを書く。
開発メモが流行ってるらしいので...、茶飲み話でも残すか...。
実装方針はこうしている:
Hash トレートを実装してそれを使わせればいいじゃない...ところでRustのHashMapはデフォルトでSipHashというものを使う。良い説明記事があるのでそちらにデリゲートしますね...。
記事の通り、秘密鍵(ランダムネス)を用いるので推測されづらく、あと、アルゴリズムもシンプルで高速。
これでいいじゃん、なのだが、 FNV というハッシュアルゴリズムもあり、もっと小さいアプリケーションならこっちも使えないかと思って軽く試したというメモです。
FNV (実装は FNV-1a になってそう)はservoが作ってるやつがあって、小さいし、HashMapやHashSetの実装も添付していて、そのインタフェースは set(&K: Hash, V) とかで一緒になっているのでこれをそのまま使えばよし。
こんな感じでfeature flagで丸ごと別名を切り替えればそのまま使える(FnvHashMap::new() はないので、 FnvHashMap::default() を使う)
#[cfg(feature = "mruby-hash-fnv")] pub type RHashMap<K, V> = fnv::FnvHashMap<K, V>; #[cfg(feature = "mruby-hash-fnv")] pub type RHashSet<K> = fnv::FnvHashSet<K>; #[cfg(feature = "mruby-hash-fnv")] pub type RHash = fnv::FnvHashMap<ValueHasher, (Rc<RObject>, Rc<RObject>)>; #[cfg(not(feature = "mruby-hash-fnv"))] pub type RHashMap<K, V> = std::collections::HashMap<K, V>; #[cfg(not(feature = "mruby-hash-fnv"))] pub type RHashSet<K> = std::collections::HashSet<K>; #[cfg(not(feature = "mruby-hash-fnv"))] pub type RHash = std::collections::HashMap<ValueHasher, (Rc<RObject>, Rc<RObject>)>;
コードはpushしてある。ログは以下に抜粋:
default HashMap 10000 entries (1 char keys)
time: [390.10 µs 390.54 µs 391.07 µs]
FNV HashMap 10000 entries (1 char keys)
time: [338.16 µs 338.67 µs 339.22 µs]
default HashMap 10000 entries (5 char keys)
time: [677.13 µs 677.60 µs 678.25 µs]
FNV HashMap 10000 entries (5 char keys)
time: [550.01 µs 550.58 µs 551.34 µs]
default HashMap 10000 entries (10 char keys)
time: [687.46 µs 688.55 µs 689.99 µs]
FNV HashMap 10000 entries (10 char keys)
time: [604.11 µs 604.66 µs 605.48 µs]
default HashMap 10000 entries (20 char keys)
time: [785.55 µs 790.05 µs 796.93 µs]
FNV HashMap 10000 entries (20 char keys)
time: [806.96 µs 807.76 µs 808.73 µs]
default HashMap 10000 entries (50 char keys)
time: [906.43 µs 911.02 µs 919.24 µs]
Benchmarking FNV HashMap 10000 entries (50 char keys): Warming up for 3.0000 s
FNV HashMap 10000 entries (50 char keys)
time: [1.3509 ms 1.3520 ms 1.3536 ms]
Benchmarking default HashMap 10000 entries (100 char keys): Warming up for 3.0000 s
default HashMap 10000 entries (100 char keys)
time: [1.1861 ms 1.2035 ms 1.2243 ms]
FNV HashMap 10000 entries (100 char keys)
time: [2.4353 ms 2.4458 ms 2.4575 ms]
グラフです:

※ mruby/edgeの中で使っているHashMapは全部FNVに変えています

結局、一応gem(feature flag)としては使い分けられるようにした。結構短いHashのキーしか使わないアプリケーションもあるかもしれない。
しかし、ランタイムも自分で作っていると、こういうふうにアルゴリズムで遊べるのはいい話か。
Rustのwasm32-unknown-unknownのような環境ではそもそもランダムネスを取得できないよなと思って軽くコードを見たら、以下の感じで:
システムコールが呼べないtargetではアドレスをソースに使うらしい。そうか〜という気持ちです。
また、キーのハッシュ関数にFNVを使う場合は、現実的にはsaltでも含めるといいのかなと思われる。そういうインタフェースを持たせたほうがいいかもではある。