wecty

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 8, 2020 License: BSD-2-Clause Imports: 5 Imported by: 10

README

Wecty

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&A

  • 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メソッドを持つこと

DOM ツリーのマークアップ

コンポーネント.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 サーバー

wecty generate

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
HTML ライクなマークアップの基本

<body></body> -> wecty.Tag("body")

属性マークアップ

<h1 id="title">Title</h1>:

wecty.Tag("h1",
  wecty.Attr("id", "title"),
  vecty.Text("Title"),
)
Class マークアップ

<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")),
)
RAW 記述
<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{},
  )
...

wecty server

開発用簡易 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 にて動作させた状態で開発を進める場合などで使います。

出力サイズ

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 版では修正済み?)

今後の機能追加

  • 条件別マークアップ機能の提供
  • デプロイ用の静的ファイルセットをエクスポートする支援機能の提供
  • 新規プロジェクト生成機能の提供
  • コマンドツールの統合&サブコマンドによる多機能化

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func AddMeta

func AddMeta(name, content string)

AddMeta ...

func AddStylesheet

func AddStylesheet(url string)

AddStylesheet adds an external stylesheet to the document.

func Callback0

func Callback0(fn func() interface{}) js.Func

Callback0 ...

func Callback1

func Callback1(fn func(res js.Value) interface{}) js.Func

Callback1 ...

func CallbackN

func CallbackN(fn func(res []js.Value) interface{}) js.Func

CallbackN ...

func GetURL

func GetURL() *url.URL

GetURL ...

func LoadModule

func LoadModule(names []string, url string) <-chan js.Value

LoadModule ...

func LoadScript

func LoadScript(url string)

LoadScript ...

func Navigate(s string)

Navigate ...

func RenderBody

func RenderBody(c Component)

RenderBody ...

func RequestAnimationFrame

func RequestAnimationFrame(callback func(float64)) int

RequestAnimationFrame ...

func Rerender

func Rerender(c Component)

Rerender ...

func SetTitle

func SetTitle(title string)

SetTitle sets the title of the document.

Types

type Applyer

type Applyer interface {
	// contains filtered or unexported methods
}

Applyer ...

type Class

type Class map[string]bool

Class markup

type Component

type Component interface {
	Renderer
	// contains filtered or unexported methods
}

Component ...

func NotFoundPage

func NotFoundPage() Component

NotFoundPage ...

type Core

type Core struct {
	// contains filtered or unexported fields
}

Core ...

type HTML

type HTML interface {
	// contains filtered or unexported methods
}

HTML ...

func Render

func Render(c Component) HTML

Render ...

type Markup

type Markup interface {
	// contains filtered or unexported methods
}

Markup ...

func Attr

func Attr(key string, value interface{}) Markup

Attr ...

func Event

func Event(name string, fn func(ev js.Value) interface{}) Markup

Event ...

func If

func If(cond bool, m Markup) Markup

If ...

func Text

func Text(s string) Markup

Text Node

type Mounter

type Mounter interface {
	Mount()
}

Mounter ...

type Node

type Node struct {
	Core
	// contains filtered or unexported fields
}

Node element

func Tag

func Tag(name string, markups ...Markup) *Node

Tag Node

func TagWithNS

func TagWithNS(name, ns string, markups ...Markup) *Node

TagWithNS Node with NameSpace

func (*Node) JSValue

func (n *Node) JSValue() js.Value

JSValue ...

func (*Node) Render

func (n *Node) Render() HTML

Render ...

func (*Node) String

func (n *Node) String() string

type Renderer

type Renderer interface {
	Render() HTML
}

Renderer ...

type Router

type Router struct {
	// contains filtered or unexported fields
}

Router ...

func NewRouter

func NewRouter() *Router

NewRouter ...

func (*Router) Current

func (r *Router) Current() *url.URL

Current ...

func (*Router) Handle

func (r *Router) Handle(key string, fn func(key string))

Handle ...

func (*Router) Navigate

func (r *Router) Navigate(s string) error

Navigate ...

func (*Router) Start

func (r *Router) Start() error

Start ...

type Unmounter

type Unmounter interface {
	Unmount()
}

Unmounter ...

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL