Zheleznayaのご紹介
お世話になっております。
本日は Zheleznaya
のご紹介をさせていただきます。
よろしくお願いします。
Zheleznaya?
Reactやhyperappのような tsx
or jsx
で書けるウェブフロントエンドフレームワークです。
TypeScriptで300行くらい(前は200行くらいだった気がするのに、気付いたら増えている・・・)のシンプルなコードなので、すぐに読むことができるでしょう。
documentation?
こちらです。さっき作りました。
なお、このドキュメント自体が Zheleznaya
で実装されているので、サンプルでもあるというわけです。
さすがに、ドキュメントをプログラムで実現するのは力技すぎるので、Markdownをfetchしてきて、それをhtmlに変換して表示する感じになっています。
concept?
自分の勉強のために作っただけの完全なオレオレフレームワークです。
不要なレンダリングを減らすとかそういう最適化的なものは一切ありません。
feature?
- シンプルなAPI
- 状態管理は一つの
store
でしかできない(強制flux)- storeはreactiveになっていて、中の値を書き換えると、再レンダリングされる
- このへんはグローバルな変数をガッツリ使っている。いつかクラスで書き直す。
- Reactみたいに書ける
- 型定義つき
sample?
import { render, h, Component, createStore } from "zheleznaya"; const store = createStore({ count: 0 }); const App = () => { return ( <div> <button onClick={() => (store.count++)}></button> <h1>{store.count}</h1> </div> ); } render(<App />);
なお、Reactに対するnext、Vueに対するnuxtのように、ZheleznayaのためのSSRフレームワーク nzxt(んずくすと)
も作りましたので、次はそれを紹介します。
以上です。よろしくお願いいたします。
WebRTCでリアルタイム通信する
WebRTCとは
ということで、最近良く使うWeb会議の映像を送るAPIのことです。
WebRTCに含まれるもの
MediaStream
カメラやマイク、スピーカーを制御して音や映像をストリームで扱えるようにするAPIRTCPeerConnection
映像や音をリアルタイムに送信するAPIRTCDataChannel
テキストデータなどをリアルタイムに送信するAPI
今回は、 MediaStream
と RTCPeerConnection
でリアルタイムビデオチャットを作ります。
WebRTCを利用する流れ
WebRTCはなんらかの手段で双方の接続情報を共有しWebRTC APIに渡すことで、勝手に接続してくれる仕組みになっています。 なんらかの手段というのは、ほんとになんでもよくて、例えばチャットやらなんやらで接続情報をコピペして教え合う、なんてことも可能です。 逆にいうと、接続情報を共有する手段はWebRTCとは別に用意する必要があります。 今回は、WebSocketを使って接続情報を共有することにします。
端末Aと端末Bがいて、端末Aが端末Bに接続しようとする流れを以下に示します。 なお、WebSocketで会議室を作ってあり、そこで必要な接続情報が共有できるものとします。
- 端末Bは会議室に入室しており、会議室にIDが登録されています
- 端末Aは会議室に入室すると、会議室に入室しているIDを取得し、そのIDに対しWebSocketで Offerを送信します。
- 端末BはOfferの要求をうけると、それをWebRTCに渡し、Answerを送り返します。
- 端末AはAnswerを受け取ると、それをWebRTCに渡します。
- 双方はIceCandidateと呼ばれる経路情報をさらにやりとりして接続することになります。
よくわからないのですが、実装上、IceCandidateのやりとりの部分を書かなくても動いていて、ちょっと気になっています。ローカルで実装したから、ダメなのかな?
実装の解説
GitHub - naoki-tomita/webrtc-app
気が向いたら書きます。
以上です。よろしくお願いします。
Electronでカメラを使おう
なぜか知りませんが、全然説明がなくて、その上どハマりしたので書いておきます。 また、2020/6月時点での情報です。時代が進むにつれ、APIの形も変わると思いますので、情報が古くなっている可能性があります。
- Electron@^9.0.0
ウェブアプリでカメラを使う
ウェブアプリでカメラを使うには、WebRTCのいち機能である getUserMedia
を使います。
video
タグにカメラの映像を流し込むのは、以下のようなコードで可能です。
// constraintsは要求するデバイスの条件を指定できる。解像度とか、フレームレートとか。上限下限とか。 const constraints = { video: true, audio: false }; const stream = await navigator.mediaDevice.getUserMedia(constraints); // videoという変数にvideoタグのエレメントが入っているものとする video.srcObject = stream;
video.src = URL.createUrlObject(stream)
とかやってるやつは古いので、無視していいです。
また、ウェブカメラが複数あるなどして、ユーザーに選択させたい場合は以下のようにデバイスをリストアップすることも可能ですし、リストアップしたデバイスを指定して getUserMedia
することも可能です。
const devices = await navigator.mediaDevices.enumerateDevices(); const stream = await navigator.mediaDevice.getUserMedia({ video: { deviceId: { exact: devices[0].deviceId } } });
ここまでは、ふつうのウェブの話なので、とくに問題ありません。 問題はElectronでカメラを利用する場合です。
Electronでウェブカメラを利用する場合の問題点
Macではカメラアクセスのためにプライバシーの設定を突破する必要があるが、罠が多い。
これです。
プライバシーの設定を突破する
こんなんいるなんて、調べても全然出てこなかった! 最新のmacOSは結構厳しいので、そういうのが必要なのだろう思いながら調べてたんですが、全然それに言及してる人がいなくて、結構困りました。
const status = systemPreferences.getMediaAccessStatus("camera"); await systemPreferences.askForMediaAccess("camera");
しかし、これも罠があります。
ターミナルからアプリを起動する
みなさまにおかれましては、Electronアプリを起動するとき、 yarn electron .
みたいなコマンドを叩いていらっしゃることでしょう。VSCodeのターミナルや、Intellijのターミナルから起動してるのではないですか?
上記プライバシーの設定を有効にした上でVSCodeやIntellijからアプリを起動しようとすると、黙ってアプリがクラッシュします。 これもめちゃめちゃハマったんですが、VSCodeやIntellijから立ち上げたプロセスはVSCodeやIntellijに紐づくらしく、カメラへのアクセスを要求する者がVSCodeやIntellijになります。Electronの肩代わりをしてくれるのですね。 しかし、こいつが罠で、VSCodeやIntellijはカメラアクセスを要求可能にする設定が入ってないらしく、要求自体が不正であるということで、アプリがクラッシュするのですね。
そこで、どうするかというと、ふつうのターミナルから起動するようにします。
そうすると、きちんと ターミナルがカメラのアクセスを要求しています
みたいなダイアログが表示されます。
ということで、Electronからウェブカメラを利用する方法でした。 以上です。よろしくお願いします。
Node.jsとテスト
Node.jsとTypeScriptが好きです。また、私が勤めている会社ではTDDでの開発を行っており、ありとあらゆるテストが必須になっています。 私が個人的にNode.jsやTypeScriptで実施しているテストに関して書いてみます。
私たちのTDD
弊社のTDDはフロントエンドのE2Eテスト、API, BFFのE2Eテストに始まり、それぞれのモジュールの単体テストまで一通りのテストを書くようにしています。 特に、E2Eテストで重要なのはアサーションライブラリだと思います。フロントエンドではSeleniumなどを使った画面のテスト、API, BFFはGETなりPOSTなりをしたレスポンスが正しいこと、DBにデータが正しく確認されたことを確認すれば良いだけですが、なかなかそのようなライブラリやフレームワークがないことも多く、Kotlinを使ったE2Eになってしまうことが多いです。単体テストも普通にしています。
Node.jsでTDD
以下では、Node.jsでテストを行う際に試したライブラリやフレームワークについて述べます。
フロントエンドのE2E
Protractor
結構古くからあるフレームワークです。Selenium Webdriverのラッパーと、テストランナーの組み合わせです。 AngularJSの開発チームがAngular用に作ったものらしく、自動でAngularのロードが終わるまで待ってくれたりします。逆にAngularでない画面フレームワークを使っていると、 Angularのロードが終わるまで待ち続けるという罠があります。 ManagedPromiseという黒魔術を持っていて、一見手続き的にテストコードを書くことができ、みやすい、という利点がありました。一方、エラーが起きたときにはどこでエラーになったかを知るのに苦労することが多かったです。 今はasync/awaitを使えばいいので、あまり利用する必要もないかな、と思います。
SSRを行わないウェブアプリが多くなってきた時代ですから、よく引っかかるのが、エレメントが表示されるまで待ってくれなくて、ボタンを押そうとして死ぬ、みたいなことがよくあるので、自分で待つ処理も書く必要があります。
例えば、「ボタンを押したあと、リストにアイテムが3つ追加される」みたいな仕様があったとします。 ボタンを押したあと、リストを取得して、3つあることを確認したいですが、ボタンを押したあとリストが表示されるまでには通信が行われるため、ネットワークの速度が影響してリスト表示までに時間がかかる場合があります。そこで、考えられるのが、「3つ表示されるまで待つ」という方法ですが、アサーションライブラリと別になっているため、3つ表示されるまで待つ処理 と表示されなかったときにエラーにする処理を自力で実装する必要があります。
このへん、JavaのSelenideはアサーションライブラリがSeleniumにくっついているので、エレメントが3つ表示されていること、というアサーションが自然な感じで書けるようになっています。
Puppeteer
Chromeを作った人たちが、Chromeを操作できるようにするためのツールを作った、というライブラリです。 コードをチラッと見てみた限りだと、WebSocketで接続しているようで、Seleniumなどを使っているわけではなく、独自のプロトコルで通信している雰囲気を感じました。 async/awaitで書けるし、わりと書きやすいのですが、Chrome操作ツールなだけあって、E2Eテスト用に便利にしてくれている、みたいなことはありません。これをラップして自分好みのライブラリを作るとよいでしょう。こんな感じで。
なお、当然ですが、Chromeにしか対応していないようですので、マルチブラウザーテストをやりたい人は選ばない方がよいでしょう。
TestCafe
Seleniumを使わないテストフレームワークです。 テストしたいウェブページをTestCafeがプロキシして、中にテストコードを埋め込んでいるようです。 iframeとかで表示しているのかなーと思ったのですが、そんなことはありませんでした。
browser -> testcafe -> テストしたいページ
という通信の流れになっていて、TestCafeがテストしたいページにテストに必要な処理やコードを埋め込んでいます。
Selenium RCに似ているのかな。
これの利点としては、WebDriverが不要であり、TestCafeが建てたサーバーにアクセスするだけでテストが実行できてしまうため、スマホやWebDriverに対応していないブラウザーもテストできることが挙げられます。ただし、Android 4.2には対応しておらず(たぶんTestCafeのコード内にブラウザが対応してないメソッドを呼び出したりしている)、大変悲しい思いをしたことを覚えています。
なんとなく、ウェブアプリにJSを差し込んでテストをするというと、そのJSが邪魔をしそうなイメージがあって、ちゅうちょしてしまいます。
Cypress
E2Eテストプラットフォームという感じです。 E2Eテストの操作を記録するツールや実行結果をJenkinsのようにダッシュボードから見ることができます。 動画まで撮ってアップロードしてくれちゃって、、、なんかすごかったです。
ただし、どうやら実行を担ってくれるわけではないようなので、その辺は自力で用意する必要があります。JenkinsやDockerコンテナ 、その他の計算機リソースから実行して、実行結果をダッシュボードにアップロードする、という流れのようです。
なんかの機会に使ってみたいという気持ちになりました。
バックエンドのE2E
バックエンドのE2Eテスト、場所によっていろんな呼び方があると思いますが、要はWebAPIを叩いて、レスポンスやDBを検証するテストのことです。
私は、あんまりこれをいい感じにやってくれるフレームワークを存じ上げないのですが、結局のところWeb APIのエンドポイントをGET/POST/PUTして結果の検証をするだけ、ということで、あんまりテストフレームワークの必要性を感じておりません。
大事なのは以下の3点だと思っています。
- リクエストの結果(一般的にはJSON)を検証すること
- リクエストの結果、別のAPIにリクエストしたこととリクエストが正しいことを検証できること
- リクエストの結果、DBにデータを格納、変更、削除されたことが検証できること
それぞれをみていきましょう。
1. リクエストの結果(一般的にはJSON)を検証すること
Web APIの仕様は破壊的変更がない限り、いつでも好きなように変更して良いと思っています。つまり、あるGETレスポンスにある日突然新たなキーが増えようが問題ないと思っています。 したがって、JSONの検証において、堅牢な(本質的に関係のない変更があってもテストがこけないこと)E2Eテストを行うにはある程度Lazyなアサーションが必要だと考えます。
つまり、以下のような評価でエラーにならないことが重要です。
// APIのレスポンスJSON const actual = { "foo": "bar", "hoge": "fuga" } const expected = { "foo": "bar" } assertLazyEqual(expected, actual); // --> OK
これを実現するためのいい感じなライブラリは見つかっておらず、Chaiなどが使えそうなのですが、色々機能があって、調べるのがダルかったので自分で作りました。こちらです。
2. リクエストの結果、別のAPIにリクエストしたこととリクエストが正しいことを検証できること
これは、別のAPIの代わりにモックを立てるかプロキシを立てるくらいしか思いつきませんでした。ちなみに、モックとして超絶に優秀なのがWireMockです。是非使いましょう。 プログラムからモックを用意させることもできるので、動的にレスポンスを変化させることもできるので、超おすすめです。
3. リクエストの結果、DBにデータを格納、変更、削除されたことが検証できること
JavaだとDBUnitみたいな優秀なライブラリがあるようなのですが、JavaScriptでは見つかりませんでした。これも、結局のところDBのデータを期待するデータと比較できればよいので、DBのデータをまるっとJSのオブジェクトに変換して、それを期待するJSONやCSVと比較するだけでよいでしょう。
ただし、以下の点に注意して検証できるようにする必要があります。
- テーブル内には、テストケースに関係ないゴミデータが入っている可能性があること。
- 他のテストケースでPOSTしたデータが入っているかもしれませんし、テストを並列実行している場合ならどんなデータがいつ入ってくるかわかったものではありませんね。また、GETのテストをするならば、予めデータを投入しておく必要があります。
- つまり、結果の検証ではそのような行は無視できる必要があります。
- テーブルにはテストでは制御できないカラムがあること。
- CreatedAtやUpdatedAt、AUTO INCREMENTなidなどが挙げられます。
- 1.と同様にいらない列は無視できる必要があります。
こういう感じでJSONを比較するライブラリがパッと見つからなかったので、作りました。こちらです。
なお、テストのライブラリの話とは関係ないのですが、テストケースに使うテストデータは他のテストに依存せず独立を保つようにしましょう。並列実行できなくて、いつか泣く日が来ます。私は泣いたことがあります。
フロントエンドの単体テスト
フロントエンドに特化した単体テストというと、ReactやVue.jsのコンポーネントのテストがあげられるでしょう。単なるロジックのテストはフロントもバックも同じですから、バックエンドで書きます。
Reactだと、最近は react-test-renderer
を使うようです。Enzymeは死んだそうです。よく知りませんが。
Vue.jsも、組み込みのやつでテストできるみたいですね。これまたよく知りませんが。
コンポーネントのテストは大抵フロントエンドのE2Eで代用してしまうことが多いようです。StoryBookとかを使うときには便利なのでしょうか。
バックエンドの単体テスト
jest
を使っておけば、大抵のものが入っていて簡単にテストできます。アサーション、テストランナー、モック機能。これらがあれば、ロジックのテストを書くことには困らないでしょう。
Web APIのE2Eテストのあたりが書きたかったので後半はだいぶ雑になりました。 以上です。よろしくお願いいたします。
Reactみたいなフレームワークを作る(その1)
このような記事があることをしりました。ぶっちゃけ自分の記事よりよっぽどしっかりしてますので、そっちをみた方がよいと思います。
Reactみたいなフレームワーク
tsx
を使って、こんな感じで書けることを目指します。要は、Reactみたいに書けて動けばよいのです。
const App = () => { return ( <div> <MyComponent value="value" onOk={() => console.log("hello world.")}> </div> ); } const MyComponent = (props) => { return ( <div> <input value={props.value} /> <button onclick={() => props.onOk()}>complete</button> </div> ) }
出来上がったものがこちらです。
tsxを吐くためのjsxFactoryを実装する
まずは、tsx(jsx)が変換されたあとのコードがどのようになるか知っておく必要があります。
const App = (props) => { return ( <div> Hello world <button class="btn" onclick={() => console.log("click")}>click</button> {props.parameter} </div> ); } render(App);
上記のようなコードは以下のような形に変換されます。
なお、jsxFactory
としてh
が指定されているものとします。
const App = () => { return ( h("div", {}, "Hello world", h("button", { class: "btn", onclick: () => console.log("click") }, "click"), props.parameter ) ); } render(App);
jsxFactoryとして指定された関数 h
に対して、divやbuttonが第一引数に、classやonclickなどのアトリビュートがオブジェクトとして第二引数に、第三引数以降に子要素が挿入されるようになっています。
ふーむ、なるほど。これを画面に描画するのはそんなに難しくなさそうですね。 では、まずは静的要素のみを描画できるようにしてみましょう。
静的要素のみを描画する
第一引数がタグ名、第二引数にアトリビュート、第三引数以降をchildrenとしてまとめて配列で受け取れるようにして、描画できるようにしてみます。
まずは、h
関数を実装します。
export function h(tagName, attributes, ...children) { return {tagName, attributes, children}; }
もらったものをそのままオブジェクトに変換してるだけですね。 これによって、上のtsxをもとに以下のようなオブジェクトが構築されます。
{ tagName: "div", attributes: {}, children: [ "Hello world", { tagName: "button", attributes: { class: "btn", onclick: () => console.log("click") }, children: [ "click" ] }, props.parameter ] }
これをVirtualDomと呼びます。仮想的なDOM構造を模したオブジェクトだと思っていただければよいです。
続きまして、それを実際に画面上に描画するrender
関数を実装してみましょう。
function createElement(vdom) { if (typeof vdom === "string") { return vdom; } const el = document.createElement(vdom.tagName); Object.keys(vdom.attributes || {}).forEach(key => el.setAttribute(key, vdom.attributes[key]) ); vdom.children.forEach(child => { const childEl = createElement(child); el.append(childEl); }); return el; } export function render(component) { const virtualDom = component(); const element = createElement(virtualDom); document.body.append(element); }
VirtualDomを受け取り、それを元に HTMLElementを構築し、それをdocument.bodyにappendしています。
これをコピペしたのが以下のものになります。 ボタンをクリックした時にログが出ないこと以外は想定通りなのではないでしょうか。
それでは、今回は以上とします。 次は、ボタンをクリックした時にコンソールにログを出すところをやっていきましょう。
よろしくお願いいたします。
yewでウェブアプリを作ろう
yew
yewとは、ウェブアプリのフロントエンドを書くためのRustのフレームワークです。 RustをWebAssemblyに変換して、フロントエンドアプリケーションとして出力してくれます。
ここでは、yewでアプリを書いてみたので、引っかかったところなんかをメモしていきます。
コンポーネントの書き方
Reactのようにコンポーネントを定義し、どんどんネストしていくことができます。全部入りのコンポーネントは以下のような感じになります。
全部入りのコンポーネント
- テキスト入力できるコンポーネント
- 外部から、ラベル、ラベルを表示するかどうか、テキスト入力確定イベントを渡せる
- 確定ボタン押されたら、入力したテキストを消す
Reactだと以下のような感じで使える想定です。(Reactだとこう書ける、みたいな例を多用します) yewでも似たような感じで使えます。
<MyComponent label="label" show_label={false} on_commit={text => console.log(text)} />
さて、以下が実装です。 Reactでいうところのclass componentだと思っていただければと思います。
use yew::{html, Component, ComponentLink, Properties, Callback}; // state: コンポーネント内部で完結するパラメータのこと。 // props: コンポーネントを呼び出す側が渡すパラメータのこと。 struct MyComponent { // stateやpropsはここに書く。 // propsはstruct Propsを用意しておく。(内部ではわざわざpropsで持つ必要はないが、わかりやすいのでそうしている) text: String, props: Props, // linkは内部でイベントハンドリングを行うために必要。 // イベントハンドリングがいらない(表示だけとか)なら不要。 link: ComponentLink<Self>, } // propsはPropertiesというトレイトを継承する必要がある // <MyComponent prop1="foo".to_string() prop2=true /> みたいな感じで呼べる想定。 #[derive(Properties, Clone, Debug)] struct Props { // requiredにしておくと、呼び出す側で記述がないと怒られるようになる #[props(required)] label: String, // requiredをつけない場合は、Defaultトレイトを実装する必要がある // boolはたぶんいらないけど、MyPropとか独自のstructを用意する場合はMyPropsにDefaultトレイトを実装しましょう show_label: bool, // 外部にイベントを発火できるCallback型。イベントパラメータとしてStringを渡せる。 #[props(required)] on_commit: Callback<String>, } // コンポーネント内で発生するイベントは全て一箇所で処理されるので、そのイベントを定義する // 今回は、text inputとbuttonをおいて、テキスト入力と確定ができるようにする enum Msg { // テキスト入力があったら発生するイベント // Stringをメッセージに含めることができる Change(String) // 確定したら発生するイベント Commit, } // Componentを実装する(Rustではなんて言うのかわからない)ことで、コンポーネントとして利用できるようになる impl Component for MyComponent { // 上で用意したMsgをMessageとして利用できるようにする(おまじない感) type Message = Msg; // 上で用意したPropsをPropertiesとして(略 type Properties = Props; // コンポーネントを新規に生成するときに呼ばれる。 // コンポーネントインスタンスを新規に生成する処理を書きましょう。 fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self { MyComponent { text: "".to_string(), props, link } } // ReactのcomponentDidMountと同じなんじゃないでしょうか。 // 再描画が必要かどうかをboolで返します。 // マウント→通信→通信結果によって再描画とかするときに使うとよいでしょう。 fn mounted(&mut self) -> bool { false } // ReactではshouldComponentUpdateに近い。setStateしたら、これが呼ばれる感じ。 // 違いは自分でstateを保存するとか捨てるとかしないといけないところ。 // 内部で発生したイベントをここで一元管理する。 // イベントに応じて、stateを変更したりする。 fn update(&mut self, msg: Self::Message) -> bool { match msg { // ユーザーがテキストを一文字入力する度に発火する Msg::Change(text) => { // stateのtextを最新の入力テキストで更新する(Reactっぽい) self.text = text; // stateに持ってるテキストでinputエレメントを描画するので、再描画する true } // ユーザーが確定ボタンを押したら、発火する Msg::Commit => { // propsで渡ってきたon_commitをさらに発火する。 self.props.on_commit.emit(self.text.clone()); // 確定ボタン押したら空文字にしたいので、空文字を突っ込む。 self.text = "".to_string(); // inputを再描画したい気持ち true } } } // ReactのcomponentWillReceivePropsに似ている // show_labelとかが変わったら呼ばれるので、内部に保持する処理を書きましょう。 fn change(&mut self, props: Self::Properties) -> bool { self.props = props; // 真面目にやるなら、変わったプロパティに応じてtrue, falseを変えるべきなのでしょうが、めんどいのでやりません。 false } // Reactではrender. // html!というマクロを使うと、マジでJSXみたいに書ける。すごい。 fn view(&self) -> Html { html! { // トップレベルにはひとつのコンポーネントしか書けないので、divとかでくくる <div> // テキスト入力できるところ <input value=self.text // linkというやつでcallbackすることで、例えばoninputなんかのイベントが発火し、ラムダ(Rustではなんて言うんだろう)が呼ばれる // ラムダの戻り値をイベントメッセージにする。下のボタンも同じ。 oninput=self.link.callback(|e: InputData| Msg::Change(e.value))/> <button onclick=self.link.callback(|_| Msg::Commit)> {"Commit"} </button> </div> } } // ReactのcomponentWillUnmount。 // 使ってないからよく知らない。 // WebSocketとか使ってる場合は、ここで切断処理するとか? fn destroy(&mut self) {} }
これをReactで書くと、こんな感じでしょうか。
class MyComponent extends React.Component { constructor(props) { super(props); this.state = { text: "" }; } render() { return ( <div> <input value={this.state.text} onInput={e => this.setState({ text: e.value })} /> <button onClick={() => this.onCommit()}>Commit</button> </div> ); } onCommit() { this.props.onCommit(this.state.text); this.setState({ text: "" }); } }
Rustを勉強する
Rustを勉強している。システムプログラミングに若干興味がありますので。
ここに、色々やってみたことをメモしていく。
Rustの環境構築
だいぶ前にやったので忘れたけど、rustup
というコマンドをインストールすれば、いい感じにバージョン管理してくれる。大抵は、最新の nightly(開発中のやつ?)
を入れておけばいいと思った。
エディタは、VSCodeがよい。Intellijをあまり使わないからだけど、エラーとかもきちんと表示してくれる感じがする。RLSというプラグインをインストールしておけば、いい感じにやってくれる。
cargoというコマンドがNode.jsでいうyarnのようなものなので、色々使っていこう。
フロントエンドをRustでやる
ライブラリとして、フロントエンドの一部を書く
wasm-pack
を使う。順を追って説明する。
1. cargo install wasm-pack
wasm-pack
というコマンドをインストールする。
このコマンドで、作ったRustのプログラムをWebAssemblyにコンパイルし、その上JavaScriptから呼び出せるよう、モジュール化もしてくれる。型定義も吐かれるので、TypeScriptからも安心して呼べる。
2. プログラムを書く
wasm-bindgen
というライブラリを依存に追加し、さらに、crate-type = ["cdylib"]
とする。
よくわからんけど、wasm-bindgen
はWebAssemblyのためのライブラリで、cdylib
はライブラリとして吐き出すってことらしい。
あとは、こんな感じでライブラリを書く。
extern crate wasm_bindgen; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern { pub fn alert(s: &str); } #[wasm_bindgen] pub fn greet(name: &str) { alert(&format!("Hello, {}!", name)); }
greet
という関数を外部に露出している。内部ではJavaScriptのalert
を呼び出している。
3. ビルドして、JavaScriptから読み込む
コマンドwasm-pack build --scope your.name
を実行すると、pkg
というフォルダが生成され、JavaScriptのライブラリが出力される。
JavaScriptからは、普通にimportなどして利用することができる。が、WebAssemblyをいい感じにビルドしてくれるやつが必要。
そこで、webpackを使う。最小限の設定さえしておけば、いい感じにやってくれるので、気にしなくてもよい。Parcel2はなぜかうまくいかなかった。まだ、対応してないのかも。parcel-bundlerは対応してるっぽいが試してない。
フレームワークを使って、Rustで全部書く
cargo-web
コマンドとフレームワークyew
を使う。
- 以下のドキュメントを読めば良いでしょう。
また、色々勉強したら更新します。