Karax

~Nimで描くUI~
Nimで仮想DOMベースのWebフロントエンドやSPAを作るための軽量フレームワーク

カテゴリー:Web
インストール方法:nimble install karax
対応Nimバージョン:1.6.0以降
公式レポジトリ:https://github.com/karaxnim/karax




Karaxとは?


概要

Karaxは、NimでWebブラウザ上の画面を作るためのフロントエンドフレームワークである。
Nimは通常、C言語などへ変換して実行ファイルを作る言語として使われることが多いが、JavaScriptへ変換するバックエンドも持っている。
Karaxはその仕組みを利用し、Nimで書いたコードをJavaScriptに変換して、ブラウザ上で動くWebアプリケーションを作れるようにする。

Karaxの中心にある考え方は、仮想DOMである。
DOMとは、HTMLの構造をブラウザが内部で扱うための木構造のことである。たとえば、div の中に button があり、その中に文字がある場合、ブラウザはそれを単なる文字列ではなく、親子関係を持つ構造として扱う。
仮想DOMは、このDOMに似た構造をプログラム側で一度作り、変更があった部分だけを実際の画面へ反映するための考え方である。
Reactでもよく知られている方式だが、Karaxも同じように仮想DOMを使う。
公式READMEでも、KaraxはReactのような仮想DOMを使うが、より小さく、NimのためにNimで書かれていると説明されている。

初心者にとって重要なのは、Karaxが「HTMLをNimの文法で組み立てる道具」だという点である。
通常のWeb制作では、HTML、CSS、JavaScriptを別々に書くことが多い。
Karaxでは、画面の構造をNimのコードとして書き、ボタンを押したときの処理や表示内容の変更もNimで記述できる。
つまり、Nimを中心にしてWeb UIを作りたい場合に便利なパッケージである。

ただし、KaraxはLaravelのような巨大なWebアプリケーションフレームワークではない。
データベース、認証、管理画面、ORM、ルーティング全体、テンプレートエンジン、APIサーバー機能などを全部まとめて提供するものではない。
Karaxの主戦場は、ブラウザ側の画面、つまりフロントエンドである。
サーバー側を作る場合は、Jester、Prologue、標準ライブラリの asynchttpserver、あるいは別のバックエンド技術と組み合わせることになる。

Karaxを一言で表すなら、「NimでReact風の画面構築を行うための軽量フロントエンドフレームワーク」である。

インストール方法

Karaxを使うには、まずNim本体とNimbleが必要である。
NimbleはNimのパッケージ管理ツールであり、Pythonにおけるpip、Node.jsにおけるnpmに近い役割を持つ。
Karaxは外部パッケージなので、Nimを入れただけでは使えない。
公式READMEでも、Karaxを使うにはNimをインストールしたうえで、Nimbleから nimble install karax を実行すると説明されている。

基本のインストールコマンドは次のとおりである。

                    
nimble install karax
                    
                

このコマンドを実行すると、NimbleがKarax本体と必要な依存パッケージを取得し、現在のNim環境で使えるようにする。
Windowsの場合は、コマンドプロンプト、PowerShell、Windows Terminalなどから実行する。
VSCodeを使っている場合でも、最終的にはターミナルでコマンドを打つ必要がある。

インストールできたか確認したい場合は、Karaxを読み込む簡単なファイルを作るとよい。
たとえば、main.nim というファイルを作り、次のように書く。

                    
include karax / prelude

proc createDom(): VNode =
  result = buildHtml(tdiv):
    text "Hello Karax!"

setRenderer createDom
                    
                

このコードは、Karaxで最小限の画面を作る例である。
ただし、ここで注意しなければならないのは、通常のNimプログラムのように nim r main.nim で実行するわけではない点である。
Karaxはブラウザ上で動くJavaScriptを生成するため、基本的には nim js main.nim を使う。

                    
nim js main.nim
                    
                

このコマンドを実行すると、main.js のようなJavaScriptファイルが生成される。
ブラウザで表示するには、このJavaScriptを読み込むHTMLファイルも必要になる。
Karaxには karun という小さなビルド補助ツールが用意されており、公式READMEでは karun -r helloworld.nim のような形でHTMLのひな型生成と実行を行う例が示されている。

Karaxのサンプルを試す場合は、公式READMEにあるように、リポジトリを開発用に取得して examples/todoappexamples/mediaplayernim js でコンパイルする方法がある。
公式READMEでは nim js todoapp.nim のあとに todoapp.html を開く例が示されている。

特徴

Karaxの第一の特徴は、Nimの文法でHTML風の画面構造を書けることである。
Karaxでは buildHtml というDSLを使って、HTMLのタグに近い形で仮想DOMを組み立てる。
DSLとは、特定の目的に合わせた書き方のことである。Karaxの場合は、Web画面を作るためのNim内DSLと考えるとよい。
公式READMEでは、buildHtml(tdiv): text "Hello World!" のように仮想DOMを構築する最小例が示されている。

第二の特徴は、仮想DOMを使って画面更新を行うことである。
Karaxでは、イベントが発生するとレンダラーが呼び出され、新しい仮想DOMが作られる。
そして、前回の仮想DOMと比較され、差分が実際のDOMへ反映される。
公式READMEでは、この処理を「virtual DOM diffing」と説明しており、Reactと似た考え方だと述べている。

第三の特徴は、Nimの制御構文をそのまま画面構築に使えることである。
ifforprocvarseq など、Nimで普通に使う構文を使いながら画面を組み立てられる。
たとえば、配列の中身を for で回して一覧表示したり、条件によって表示する文章を変えたりできる。
HTMLテンプレートに独自構文を混ぜるのではなく、Nimのコードとして画面を作る点がKaraxらしさである。

第四の特徴は、イベント処理をNimで書けることである。
ボタンのクリック、入力欄の変更、キー入力などのイベントをNimの proc やクロージャで扱える。
公式READMEでは、イベントハンドラの型として proc (ev: Event; n: VNode) または proc () を使うことが説明されている。

第五の特徴は、軽量さを重視していることである。
Karaxの公式READMEでは、Nimのマクロシステムを活用してボイラープレートを少なくし、小さく、速く、柔軟に保つことが目標として挙げられている。

一方で、KaraxはReact、Vue、Svelteのように巨大なエコシステムを持つフレームワークではない。
UIコンポーネント集、状態管理ライブラリ、ルーター、フォームライブラリ、テスト支援、デザインシステムなどを含めた総合的な開発体験では、JavaScript/TypeScript系の主要フレームワークに及ばない場面がある。
Karaxの強みは、Nimでフロントエンドを書けること、Nimの型や構文を活かせること、小規模から中規模のNim中心プロジェクトに組み込みやすいことである。

使いどころ

Karaxが向いている第一のケースは、Nimで作ったWebアプリケーションに、Nim製のフロントエンドを組み合わせたい場合である。
たとえば、JesterやPrologueでAPIサーバーを作り、画面側をKaraxで作る構成が考えられる。

第二のケースは、検索、絞り込み、タブ切り替え、簡単な管理画面など、ブラウザ上で少し動きのあるUIを作りたい場合である。
たとえば、パッケージ一覧を表示し、カテゴリーで絞り込み、検索欄に文字を入力すると表示が変わるような画面は、Karaxの得意分野に入る。
単なる静的HTMLだけでも実装できるが、状態を持つUIになるほど、Karaxのような仕組みが便利になる。

第三のケースは、NimのJavaScriptバックエンドを学びたい場合である。
NimはCやC++だけでなくJavaScriptへも変換できるが、素のJavaScriptバックエンドを直接扱うと、DOM操作やJavaScript連携の理解が必要になる。
Karaxを使うと、Nimらしい書き方でブラウザUIを作れるため、NimからWebフロントエンドへ入る教材として使いやすい。

第四のケースは、小規模なSPAを作りたい場合である。
SPAとは、ページ遷移のたびにHTML全体をサーバーから読み直すのではなく、ひとつのページ内で表示を切り替えるWebアプリケーションのことである。
公式READMEでも、KaraxはSPA開発のためのフレームワークと説明されている。

第五のケースは、静的サイト生成やサーバー側HTML生成の一部にKaraxのDSLを使いたい場合である。
Karaxはブラウザ側SPAだけでなく、制限付きではあるがサーバー側でHTML文字列を生成する用途にも使える。
公式READMEでは、karax / [karaxdsl, vdom] を使ってHTMLを文字列として出力する例が示されている。
ただし、サーバー側にはJavaScript実行環境やブラウザDOMがないため、使える機能は一部に限られる。

反対に、Karaxがあまり向かないケースもある。
大規模な商用フロントエンドをチーム開発し、豊富なUIコンポーネント、ルーティング、状態管理、フォーム管理、国際化、テストツール、デザイナーとの連携などが必要な場合は、React、Vue、Svelte、Next.js、Nuxtなどのほうが情報量と周辺ツールは多い。
Karaxは「NimでWeb UIを作る」ことに価値を見出す場合に選ぶべき道具である。

使い方

Karaxの基本は、VNode を返す関数を作り、それを setRenderer に渡すことである。
VNode は仮想DOMのノードを表す型である。
HTMLの divbutton に相当するものを、Nimの値として組み立てていると考えるとよい。

たとえば、次のようなコードがKaraxの最小限の例である。

                    
include karax / prelude

proc createDom(): VNode =
  result = buildHtml(tdiv):
    text "Hello Karax!"

setRenderer createDom
                    
                

このコードの include karax / prelude は、Karaxを使うために必要な基本的な型や関数、DSLをまとめて読み込む行である。
通常のNimでは import を使うことが多いが、Karaxの入門例では include karax / prelude がよく使われる。
prelude は「よく使うものをまとめて読み込む入口」と考えるとよい。

proc createDom(): VNode = は、画面を作る関数である。
この関数は VNode を返す。
VNode は実際のHTMLそのものではなく、Karaxが内部で扱う仮想的なHTML構造である。

result = buildHtml(tdiv): は、KaraxのDSLで仮想DOMを作る部分である。
buildHtml の中では、HTMLタグに近い名前を使って画面を組み立てる。
ここで tdiv と書いているのは、Nimでは div が整数除算に関係する予約語・演算子として扱われるため、そのままHTMLの div という名前を使えないからである。
公式ドキュメントでも、div はNimのキーワードなのでKaraxでは tdiv を使うと説明されている。

text "Hello Karax!" は、画面に文字を置く処理である。HTMLで書くなら、だいたい次のような内容に近い。

                    
<div>Hello Karax!</div>
                    
                

最後の setRenderer createDom は、Karaxに「この関数を使って画面を描画せよ」と登録する行である。
これがないと、createDom を定義してもKaraxはどの関数を画面描画に使えばよいか分からない。


次に、ボタンを押すと表示が変わる例を見る。

                    
include karax / prelude

var count = 0

proc createDom(): VNode =
  result = buildHtml(tdiv):
    h1:
      text "Karax Counter"

    p:
      text "現在の数値: " & $count

    button:
      text "増やす"
      proc onclick(ev: Event; n: VNode) =
        count += 1

setRenderer createDom
                    
                

このコードでは、count という変数を用意している。
var count = 0 は、数値を保存する変数である。
ボタンが押されると onclick が実行され、count += 1 によって数値が1増える。

text "現在の数値: " & $count の部分では、文字列と数値をつなげている。
Nimでは、数値をそのまま文字列と連結することはできない。
$count と書くことで、数値の count を文字列へ変換している。
初心者が詰まりやすい点なので、ここは丁寧に押さえる必要がある。& は文字列結合の演算子である。

proc onclick(ev: Event; n: VNode) = は、ボタンがクリックされたときの処理である。
ev はクリックイベントそのものを表し、n はイベントが発生した仮想DOMノードを表す。
初心者向けの記事では、最初からこの2つを深掘りしすぎる必要はない。
「ボタンが押されたときに実行される処理を書く場所」と説明すれば十分である。


次に、入力欄の内容を画面に表示する例を見る。

                    
include karax / prelude

var name = kstring""

proc createDom(): VNode =
  result = buildHtml(tdiv):
    h1:
      text "名前入力"

    input(value = name):
      proc oninput(ev: Event; n: VNode) =
        name = n.getInputText

    p:
      text "入力された名前: " & name

setRenderer createDom
                    
                

この例では、name に入力欄の内容を保存している。
kstring はKaraxでよく使われる文字列型である。
公式READMEでは、kstring はJavaScriptターゲットでは cstring の別名として扱われ、ネイティブターゲットでは string に対応すると説明されている。

input(value = name): は、入力欄を作る部分である。
value = name によって、入力欄の初期値や現在値を指定している。
oninput は、入力内容が変わったときに呼ばれるイベントである。
n.getInputText によって入力欄の文字列を取り出し、name に保存している。


次に、配列の中身を一覧表示する例である。

                    
include karax / prelude

var packages = @[
  kstring"Karax",
  kstring"Jester",
  kstring"Prologue",
  kstring"Nimble"
]

proc createDom(): VNode =
  result = buildHtml(tdiv):
    h1:
      text "Nimパッケージ一覧"

    ul:
      for pkg in packages:
        li:
          text pkg

setRenderer createDom
                    
                

このコードでは、packages という配列にパッケージ名を入れている。
for pkg in packages: によって、配列の中身をひとつずつ取り出し、li として表示している。
HTMLで書くなら、ul の中に複数の li が並ぶ形である。

Karaxの良いところは、このようにNimの for をそのまま画面構築に使える点である。
テンプレート用の特殊な繰り返し構文を覚えるのではなく、Nimの文法で考えられる。


条件分岐も使える。

                    
include karax / prelude

var loggedIn = false

proc createDom(): VNode =
  result = buildHtml(tdiv):
    if loggedIn:
      p:
        text "ログイン済みである。"
      button:
        text "ログアウト"
        proc onclick(ev: Event; n: VNode) =
          loggedIn = false
    else:
      p:
        text "ログインしていない。"
      button:
        text "ログイン"
        proc onclick(ev: Event; n: VNode) =
          loggedIn = true

setRenderer createDom
                    
                

この例では、loggedIn が true ならログイン済みの表示を行い、false なら未ログインの表示を行う。
ボタンを押すと状態が変わり、画面表示も変わる。
Karaxでは、イベントハンドラのあとに再描画が行われ、仮想DOMの差分が実DOMに反映される。
これがKaraxの画面更新の基本である。


実際にブラウザで動かすには、NimコードをJavaScriptへ変換する必要がある。

                    
nim js main.nim
                    
                

このコマンドで main.js が生成される。次に、HTMLファイルを作って読み込む。

                    
<!doctype html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <title>Karax Sample</title>
</head>
<body>
  <script src="main.js"></script>
</body>
</html>
                    
                

このHTMLをブラウザで開けば、生成されたJavaScriptが読み込まれ、Karaxの画面が表示される。
もし何も表示されない場合は、main.js のファイル名が合っているか、HTMLとJavaScriptが同じフォルダにあるか、ブラウザの開発者ツールにエラーが出ていないかを確認する。

注意点

Karaxで最初に注意すべき点は、通常のNimプログラムとは実行方法が違うことである。
初心者は nim r main.nim と打ちたくなるが、Karaxの基本用途はブラウザ上で動くJavaScriptを生成することなので、通常は nim js main.nim を使う。
nim r はコンパイルして実行まで行うコマンドだが、ブラウザ用JavaScriptは生成後にHTMLから読み込む必要がある。
ここを混同すると、「コンパイルは通ったのに何も起きない」「実行ファイルができない」と感じてしまう。

次に注意すべき点は、div ではなく tdiv を使うことである。
HTMLでは div と書くが、Nimでは div が予約語・演算子として扱われるため、Karaxでは tdiv を使う。
公式READMEとドキュメントでも、tdiv はHTMLの <div> に対応する仮想DOMノードであると説明されている。

たとえば、次のように書くとエラーになる可能性が高い。

                    
result = buildHtml(div):
  text "Hello"
                    
                

正しくは次のように書く。

                    
result = buildHtml(tdiv):
  text "Hello"
                    
                

また、ibsu のような短いHTMLタグにも注意が必要である。
Karaxのドキュメントでは、HTMLタグ名とNim側の列挙値が異なる例として、italiciboldbstrikethroughsunderlinedu に対応すると示されている。

次に、文字列型の扱いにも注意が必要である。
Karaxでは kstring がよく出てくる。
通常のNimでは string を使うことが多いが、KaraxはJavaScriptバックエンドを意識しているため、kstring が使われる場面がある。
text に渡す値、入力欄から取った値、イベントで扱う文字列などで型が合わない場合は、stringcstringkstring の違いを疑う必要がある。

数値を文字列として表示するときも注意する。次のコードは初心者がよく書きがちな間違いである。

                    
text "count: " & count
                    
                

count が整数の場合、文字列と整数をそのまま & で連結することはできない。
正しくは次のように $ で文字列へ変換する。

                    
text "count: " & $count
                    
                

次に、イベントで自動再描画されないケースに注意する。
Karaxは、通常のDOMイベントや karax/kajax のAjax呼び出しでは再描画を扱ってくれるが、WebSocket、タイマー、外部JavaScriptからのコールバックなど、Karaxの外側で状態が変わる場合には、自分で redraw を呼ぶ必要がある場合がある。
公式READMEでも、Karaxのリアクティブモデルはイベントに反応する方式であり、setInterval のようなブラウザAPIはそのままでは再描画されないため、必要に応じて redraw(kxi) を呼ぶ例が示されている。

ありがちな症状は、「変数の値は変わっているはずなのに画面が変わらない」というものだ。
この場合、状態変更がKaraxのイベント処理の外で起きていないかを確認する。
ボタンの onclick で変えているなら再描画されやすいが、setIntervalWebSocket、外部ライブラリのコールバックなどで変えている場合は、明示的な再描画が必要になることがある。

次に、HTMLファイルからJavaScriptを読み込んでいないミスがある。
nim js main.nimmain.js が生成されても、HTML側でそれを読み込まなければ画面には何も出ない。


次に、インデントミスに注意する。Nimはインデントでブロック構造を表す言語である。
Karaxの buildHtml ではHTMLの親子関係もインデントで表すため、インデントがずれると意味が変わる。
たとえば、次のコードでは textbutton の中にある。

                    
button:
  text "押す"
                    
                

一方、インデントがずれると、意図した親子関係にならない。

                    
button:
text "押す"
                    
                

このようなコードは構文エラーになるか、意図しない構造になる。
Karaxの学習では、HTMLタグの親子関係とNimのインデントを同時に見る必要がある。
初心者には少し厳しいが、ここを雑に扱うと画面構築で必ず詰まる。


次に、サーバー側レンダリングとブラウザ側レンダリングを混同しないことが重要である。
Karaxはサーバー側でHTML文字列を生成する用途にも使えるが、すべての機能が使えるわけではない。
公式READMEでは、サーバー側HTMLレンダリングではJavaScriptインタプリタがないため、一部モジュールだけが使えると説明されている。

つまり、ブラウザのDOM、クリックイベント、入力欄、window、document のような要素を前提にするコードは、サーバー側ではそのまま動かない可能性がある。
初心者向けには、最初はブラウザ側SPAとして学ぶほうが混乱しにくい。


最後に、情報量の少なさにも注意する必要がある。
Karaxは面白いライブラリだが、ReactやVueと比べると日本語記事、サンプル、エラー解決情報が少ない。
エラーが出たときは、Karaxの問題なのか、Nimの型エラーなのか、JavaScriptバックエンドの制限なのか、HTML読み込みの問題なのかを切り分ける必要がある。
これは初心者には少し難しい。

型・関数・API

Karaxを理解するうえで最初に押さえるべき型は VNode である。
VNode は仮想DOMノードを表す型であり、Karaxで画面を構築する基本単位である。
HTMLの <div><p><button><input> などに相当する要素を、Karax内部では VNode として扱う。
公式ドキュメントでは、仮想DOMは vdom モジュールにあり、VNodeKind がHTML5の各タグやコンポーネント用の拡張を表すと説明されている。

buildHtml は、Karaxの画面構築で中心になるDSLである。
HTMLのような構造をNimコードとして書き、最終的に VNode を作る。
初心者は、buildHtml を「HTMLをNimの構文で作るためのブロック」と理解するとよい。

setRenderer は、Karaxアプリケーションの描画関数を登録するために使う。
Karaxでは、VNode を返す関数を作り、その関数を setRenderer に渡す。
これにより、Karaxは「どの関数を使って画面を作ればよいか」を知ることができる。

イベント処理では、onclickoninputonchange などが重要である。
KaraxはDOMのイベントモデルを大きく抽象化しすぎない設計になっており、イベントハンドラは proc (ev: Event; n: VNode) または proc () の形で書ける。

入力欄を扱うときは、入力された文字列を取り出す関数が重要になる。Karaxの例では getInputText が使われる。
これは、入力欄の現在の文字列を取得するために使う。

Karaxでは、HTML属性もNimの引数のように書く。
たとえば、button(disabled = true): のように書けば、ボタンを無効化できる。
公式READMEでは、disabledcontenteditable のような真偽値的なHTML属性を、Karaxではbool値で設定・解除できると説明されている。

ルーティングでは RouterData が登場する。
Karaxでは、setRendererRouterData を受け取る描画関数を渡すことで、URLのハッシュ部分に応じて表示を切り替える構成を作れる。
公式READMEでは、Todoアプリの例として data.hashPart を見て #/completed#/active を判定するコードが示されている。

デバッグ用のコンパイルフラグも覚えておくと役立つ。
特に -d:debugKaraxDsl は、buildHtml がどのようなNimコードへ展開されるかを確認できるため、Karaxの内部動作を学びたいときに便利である。
公式READMEでは、nim js -d:debugKaraxDsl myapp.nim の例とともに、複数のデバッグフラグが紹介されている。

これらの型や関数を押さえることで、Karaxの基本的な使い方と構造を理解できる。
もちろん、実際にコードを書いてみることが最も効果的な学習方法である。

参考文献

以下は本記事を執筆するにあたり参考にした文献や資料である。

トップへ戻る


お問い合わせ