Wecty: フロントエンドツールキット for Go and TinyGo
- Wecty は Vecty(github.com/gopherjs/vecty) のアイディアを元にしています
- Wecty は Vecty よりも単純に実装されました
- Wecty は WASM アーキテクチャのみをサポートします
- 基本的な機能を一通り含みます
- リッチな機能や冗長な機能は採用しません
- それにより出力される WASM サイズを小さく保ちます
- 描画の最速を目指したりはしない
- SetTitle: ドキュメントタイトルの変更
- AddStylesheet: スタイルシートの追加
- AddScript: スクリプトの追加
- AddMeta: メタヘッダの追加
- RenderBody/Rerender: ボディコンポーネントの描画とコンポーネントの再描画
- ネストされたコンポーネントのサポート
- Mount/Unmount: マウントとアンマウントタイミングのフック
- Tag: HTML タグのマークアップ
- Attr: タグの属性をマークアップ
- Class: タグのクラスをマークアップ
- Event: タグのイベントをマークアップ
- Router: シンプルな SPA ルーター(URL のハッシュ利用)
- Utilities:
- wecty generate: go-generate 用ツール、HTML 記述から Go のコードを生成
- wecty server: 開発用サーバー
go get github.com/nobonobo/wecty/cmd/wecty
- 以下のファイル(後述)を作成する
- top.go
- top.html
- main.go
- go mod init sample
top.html
<form @submit="{{c.OnSubmit}}">
<button>Submit</button>
</form>
以下のコマンドを実行した場合、
wecty generate -c TopView top.html
top_gen.go が生成されます
package main
import (
"github.com/nobonobo/wecty"
)
func (c *TopView) Render() wecty.HTML {
return wecty.Tag("form",
wecty.Event("submit", c.OnSubmit),
wecty.Tag("button", vecty.Text("Submit")),
)
}
以下のような Go コードを手書きで書いておくことでコンポーネントとして利用可能になります。
top.go
package main
import (
"github.com/nobonobo/wecty"
)
type TopView struct {
wecty.Core
}
func (c *TopView) OnSubmit(ev js.Value) interface{} {
println("submit!")
return nil
}
また以下の記述を top.go に加えておくと go generate で wecty generate が自動的に走るようになります。
//go:generate wecty generate -c TopView -p main top.html
main.go
package main
import (
"github.com/nobonobo/wecty"
)
func main() {
wecty.RenderBody(wecty.Tag("body", &TopView{}))
select {}
}
あとはwecty server
を起動しておけば、
http://localhost:8080をブラウザで開くだけで
top_gen.go が生成され WASM がビルド&サーブされブラウザで動作を開始する
- 上記のセットアップが完了したならば
- top.html の編集ー>ブラウザリロードで反映結果を確認できます
- top.go や main.go を修正後、ブラウザリロードで修正後の wasm モジュールの挙動を確認できます
-
Q: なぜ Vecty とは別に作ったの?
A: GopherJS の開発が停滞しつつあること、Vecty は GopherJS と Go 両対応により複雑な実装になっている。
-
Q: Router はなぜハッシュベース?
A: URL を書き換えるスタイルはプロキシサーバーの URL 割り当てと整合をとる必要がある。ハッシュベースは単一の URL を振り向けるだけで動作する。つまり、SPA コンテンツを S3 に置いた場合でも動作する。
-
Q: Vecty のように prop や event パッケージを設けないのはなぜ?
A: 基本のマークアップは wecty generate の出力に任せるのでマークアップの容易さは無用だった。それにそれらのパッケージが WASM サイズの肥大化を招いていた。
-
Q: wecty generate のマークアップ機能が足りないのはなぜ?
A: 頑張っても Go の手書きの自由度を超えることはできない。ユーザーは wecty generate で済ますか Go のコードで細かく書いてコンポーネントを実装するかを使い分けてもらいたい。
-
Q: コンポーネントより細かい単位の最適な DOM ツリーの更新をしないのはなぜ?
A: 軽量な実装で仮装 DOM を実装した。賢くすることで描画更新は早くなるかもしれないが、WASM サイズが膨れてしまうことは避けたかった。速度を極力落とさない作りはユーザーがチャレンジできる。それはコンポーネントの粒度を小さく保つこと。
- wecty.Core を埋め込みした構造体定義
Render() wecty.HTML
メソッドを持つこと
コンポーネント.Render() HTML
メソッドを実装するには
単一の wecty.Tag(...)の戻り値を return するように記述する。
func (c *コンポーネント) Render() HTML {
return wecty.Tag(...)
}
wecty.Tag 関数の定義は以下の通り
wecty.Tag(tagName, markups ...wecty.Markup) *Node
tagName には HTML タグ名を渡す。markups には以下の記述を書く。
Markup になれるもの一覧
- wecty.Attr(...)の戻り値(親 Tag の属性値になる)
- wecty.Class{}値(親 Tag の classList 値になる)
- wecty.Event(...)の戻り値(親 Tag にイベントリスナーを追加)
- wecty.Text(...)の戻り値(子ノードを追加)
- wecty.Tag(...)の戻り値(子ノードを追加)
- Component を満たすオブジェクト(子ノードを追加)
wecty いくつかのサブコマンドをもつユーティリティ
- wecty generate: HTML 記述から Wecty 用の Go コードを生成するツール
- wecty server: 開発用簡易 HTTP サーバー
Usage of generate:
-c string
component name
-o string
output filename
-p string
output package name (default "main")
- default
output filename
=basename_gen.go
- default
package name
=main
- require
component name
<body></body>
-> wecty.Tag("body")
<h1 id="title">Title</h1>
:
wecty.Tag("h1",
wecty.Attr("id", "title"),
vecty.Text("Title"),
)
<h1 class="title large">Title</h1>
:
wecty.Tag("h1",
wecty.Class{
"title": true,
"large": true,
},
vecty.Text("Title"),
)
<form @submit="{{c.OnSubmit}}">
<button>Submit</button>
</form>
wecty.Tag("form",
wecty.Event("submit", c.OnSubmit),
wecty.Tag("button", vecty.Text("Submit")),
)
<import>github.com/nobonobo/examples/todo/components</import>
<div><raw>&components.Item{}</raw></div>
import (
"github.com/nobonobo/examples/todo/components"
)
...
return wecty.Tag("div",
&components.Item{},
)
...
開発用簡易 HTTP サーバー
Usage of server:
-addr string
listen address (default ":8080")
-p path=endpoint
add reverse proxy rule (allow multiple rules)
-tinygo
use tinygo tool chain
- 静的コンテンツのサーブ
- "main.wasm"を要求されるとその該当フォルダで go-generate と WASM のビルド、gzip が行われその結果をサーブします
- index.html が無いところで要求されたら標準的な WASM 読み込み用 HTML を返します
- "wasm_exec.js"リソースが要求されたら適切な wasm_exec.js をサーブします
- 指定パスへのアクセス要求を別の Web サーバーへ転送します(リバースプロキシー)
- -addr: 開発サーバーのリッスンアドレスの指定
- -p: 転送ルールの追加(複数指定可能)
- -tinygo: WASM ビルドに tinygo を使う
例:
wecty server -addr :8080 -p /api/=http://localhost:9001
バックエンドサーバーを http://localhost:9001 にて動作させた状態で開発を進める場合などで使います。
- http://localhost:8080/api/ へのアクセスは http://localhost:9001/ へ転送されます。
- http://localhost:8080/api/hoge へのアクセスは http://localhost:9001/hoge へ転送されます。
Todo サンプルのコンパイル事例
ツール | WASM サイズ | gzipped |
---|---|---|
Go | 2.8MiB | 787.2KiB |
TinyGo | 374KiB | 154KiB |
- TinyGo コンパイラはまだ Go-Module サポートがありません。GOPATH、GO111MODULE=off などの環境変数設定が必要です
- TinyGo の WASM 出力は js.finalizeRef が未実装なためメモリーリークが起こりえます
- TinyGo 0.13.1 は log.Print 系を使うとデッドロックするバグがあります(dev 版では修正済み?)
- 条件別マークアップ機能の提供
- デプロイ用の静的ファイルセットをエクスポートする支援機能の提供
- 新規プロジェクト生成機能の提供
コマンドツールの統合&サブコマンドによる多機能化