$30 off During Our Annual Pro Sale. View Details »

GopherJS+Vecty

irieda
February 24, 2018

 GopherJS+Vecty

Goだけでつくるフロントエンド入門

irieda

February 24, 2018
Tweet

More Decks by irieda

Other Decks in Technology

Transcript

  1. Go
    だけで作る
    Go
    だけで作る
    フロントエンド入門
    フロントエンド入門

    View Slide

  2. お前誰よ?
    お前誰よ?
    メカトロソフト屋
    Pythonista -> Gopher
    なんでも Go で書いちゃうひと
    Go 歴は 5 年目
    サイト:
    会社: 144Lab(2017/07/01 から新社名)
    HN: @nobonobo
    http://golang.rdy.jp/

    View Slide

  3. リッチなフロントエンド作りたい?
    リッチなフロントエンド作りたい?
    もちろん
    もちろん
    作りたいですよね!
    作りたいですよね!

    View Slide

  4. でも・・・
    でも・・・

    View Slide

  5. JS
    を書きたく無いでござる
    JS
    を書きたく無いでござる

    View Slide

  6. JS
    を書きたく無いでござる
    JS
    を書きたく無いでござる

    View Slide

  7. 絶対に JS
    を書きたく
    絶対に JS
    を書きたく
    無いでござる
    無いでござる
    (
    個人的な感想です)
    (
    個人的な感想です)
    そこで
    そこで

    View Slide

  8. Go
    から Javascript
    Go
    から Javascript
    もちろん、Go から Javascript への変換には
    ですね!
    参考情報
    Go
    を JS
    に 100%
    変換できます
    Go
    を JS
    に 100%
    変換できます
    GopherJS
    http://golang.rdy.jp/2015/10/15/gopherjs/
    http://golang.rdy.jp/playgopherjs/#/1

    View Slide

  9. 関連ライブラリ
    関連ライブラリ
    トランスパイラ:
    仮想 DOM:
    ルーター:
    Bootstrap4コンポーネントセット:
    GopherJS(BSD)
    Vecty(BSD)
    github.com/go-humble/router(MIT)
    github.com/nobonobo/bootstrap4(MIT)

    View Slide

  10. GopherJS
    の近況
    GopherJS
    の近況
    速度より安定性を重視
    出力ターゲットは ES5
    ES2015~ES2017 の新記述の最適化がない
    Go1.10 サポートが着々と進んでる
    (Go 本体では WebAssembly 実装の実験も!!)

    View Slide

  11. Vecty
    の近況
    Vecty
    の近況
    鋭意安定板に向けて改善真っ只中
    昨年中に 5~6 回の破壊的変更がありました
    破壊的変更の予定がまだ少しあります
    本格利用はしばしお待ちを
    HTML/CSS/JS の全てを Go で書ける
    というわりに CSS 支援は弱い

    View Slide

  12. Vecty
    の提供機能
    Vecty
    の提供機能
    HTML ビルダ
    レンダラー
    コンポーネント
    プロパティ
    ほんとこれだけ。

    View Slide

  13. HTML
    ビルダ
    HTML
    ビルダ
    タグ定義を使って生成
    属性は Markup でラップして記述
    慣れるまで大変
    elem.Body( //
    elem.Heading1(vecty.Text("TITLE")), // TITLE
    elem.Anchor( //
    vecty.Markup(prop.Href("/page")),
    vecty.Text("link"), // link
    ), //
    ) //

    View Slide

  14. レンダラー
    レンダラー
    コンポーネントのレンダリングは二種類ある
    RenderBody: 初期レンダリング
    Rerender: 差分レンダリング

    View Slide

  15. コンポーネント
    コンポーネント
    Core 実装と Render メソッドを持つ
    プロパティを元に HTML を生成する実装
    コンポーネントツリーが仮想 DOM 相当
    type MyComponent struct {
    vecty.Core
    }
    func (c *MyComponent) Render() vecty.ComponentOrHTML {
    return elem.Header1(vecty.Text("Hello!"))
    }

    View Slide

  16. プロパティ
    プロパティ
    コンポーネント定義のフィールド
    任意の型に`vecty: prop `タグをつける
    コンポーネントが保有・参照するデータ
    親から受け取って参照しつつ HTML を組む
    type MyComponent struct {
    vecty.Core
    Title string `vecty:"prop"`
    }
    func (c *MyComponent) Render() vecty.ComponentOrHTML {
    return elem.Header1(vecty.Text(c.Title))
    }

    View Slide

  17. ミニマムな例
    ミニマムな例

    View Slide

  18. ミニマムな実装例
    ミニマムな実装例
    type TopView struct {vecty.Core}
    func (c *TopView) Render() vecty.ComponentOrHTML {
    vecty.SetTitle("TopView")
    return elem.Body(
    elem.Heading1(vecty.Text("First")),
    elem.Button(vecty.Text("click")),
    )
    }
    func main() {
    top := &TopView{}
    vecty.RenderBody(top)
    }

    View Slide

  19. ミニマムな実行例
    ミニマムな実行例
    Top
    click

    View Slide

  20. プロパティな例
    プロパティな例

    View Slide

  21. プロパティな実装例
    プロパティな実装例
    type TopView struct {
    vecty.Core
    Heading string `vecty:"prop"`
    }
    func (c *TopView) Render() vecty.ComponentOrHTML {
    vecty.SetTitle("TopView")
    return elem.Body(
    elem.Heading1(vecty.Text(c.Heading)),
    elem.Button(
    vecty.Markup(
    event.Click(func(ev *vecty.Event) {
    now := time.Now().Format(time.RFC3339Nano)
    c.Heading = fmt.Sprintf("Top: %s", now)
    vecty.Rerender(c)
    })

    View Slide

  22. プロパティな実行例
    プロパティな実行例
    Top
    click

    View Slide

  23. Vecty
    の不足機能
    Vecty
    の不足機能
    ルーター/ビュー
    スタイルシート
    Flux 相当

    View Slide

  24. ルーター/
    ビュー
    ルーター/
    ビュー
    いくつか試したところ
    これが Vecty 用でもないけど
    使いやすかった。
    github.com/go-humble/router(MIT)

    View Slide

  25. スタイルシート
    スタイルシート
    個別のスタイル操作はできる
    スタイルシートに対する支援がまだない
    CSS は頑張って用意するしかなさそう

    View Slide

  26. Flux
    相当
    Flux
    相当
    サンプルが採用している方法を参考に。
    アクション: メッセージ定義
    ディスパッチ: メッセージのディスパッチャ
    モデル: アプリのデータモデル定義
    ストア: アプリ全体のデータストア
    ビュー: 最上位コンポーネント(概念を追加)
    ハンドラー: 振る舞い実装(概念を追加)

    View Slide

  27. 1
    方向に回す
    1
    方向に回す

    View Slide

  28. アクション
    アクション
    1 アクションに 1 振る舞い
    振る舞い予約の抽象的概念
    振る舞いに渡すパラメータ定義

    View Slide

  29. ディスパッチ
    ディスパッチ
    アクションの送信と受信を繋ぐ
    送信側と受信側を疎結合にする
    定型処理

    View Slide

  30. モデル
    モデル
    アプリで保有するデータ構造定義
    概ね JSON タグをつけて運用
    バックエンドと共通で利用できる

    View Slide

  31. ストア
    ストア
    モデル定義に沿ったデータを保持
    ロードされる JS と同じライフサイクル
    アプリ要件で永続化と復元機能が必要
    ぶっちゃけただのグローバル変数
    API 結果のキャッシュのようなもの
    GopherJS はシングルスレッド動作のみなので
    基本は排他処理等は気にしない

    View Slide

  32. ビュー
    ビュー
    コンポーネントの性質
    HTML の組み合わせを抽象化
    プロパティを持ち、それを元に描画を決定
    DOM イベントをアクションに翻訳する
    ビューの性質
    Body を根とするコンポーネントツリー
    ルーターで URL と 1 対 1 に対応

    View Slide

  33. ハンドラー
    ハンドラー
    アクションに対応する挙動実装群
    ストアを書き換えしてもいい唯一の存在
    サンプルではストアパッケージに書いてる

    View Slide

  34. ルーターの例
    ルーターの例

    View Slide

  35. ルーターの実装例
    ルーターの実装例
    type NormalView struct {
    vecty.Core
    Title string `vecty:"prop"`
    Next string `vecty:"prop"`
    }
    func (c *NormalView) Render() vecty.ComponentOrHTML {
    vecty.SetTitle(c.Title)
    return elem.Body(
    elem.Heading1(vecty.Text(c.Title)),
    elem.Anchor(
    vecty.Markup(prop.Href("#/"+c.Next)),
    vecty.Text("link:"+c.Next),
    ),
    elem.Button(
    vecty Markup(

    View Slide

  36. ルーターの実行例
    ルーターの実行例
    top
    link:next1 click

    View Slide

  37. 中規模な実装例
    中規模な実装例
    Bootstrap4 と API 呼び出し

    View Slide

  38. フォルダ構成
    assets/ スタティックファイル群
    models.go データスキーマ
    components.go コンポーネント定義群
    views.go ビュー定義群
    main.go メイン実装

    View Slide

  39. models.go:Post
    models.go:Post
    // Post https://jsonplaceholder.typicode.com/posts
    type Post struct {
    UserID int `json:"userId"`
    ID int `json:"id"`
    Title string `json:"title"`
    Body string `json:"body"`
    }
    type Posts []*Post

    View Slide

  40. components.go:Post
    components.go:Post
    type PostInfo struct {
    vecty.Core
    Post Post `vecty:"prop"`
    }
    func (c *PostInfo) Render() vecty.ComponentOrHTML {
    return elem.Div(vecty.Markup(vecty.Class("card")),
    elem.Div(vecty.Markup(vecty.Class("card-body")),
    elem.Heading5(vecty.Text(c.Post.Title)),
    elem.Paragraph(vecty.Text(c.Post.Body)),
    ),
    )
    }
    type PostInfoList struct {
    vecty Core

    View Slide

  41. views.go:/posts
    views.go:/posts
    type PostsView struct {
    vecty.Core
    Posts Posts `vecty:"prop"`
    }
    func (c *PostsView) Render() vecty.ComponentOrHTML {
    vecty.SetTitle("Posts")
    return &Layout{
    Children: vecty.List{
    elem.UnorderedList(
    vecty.Markup(
    vecty.Class("nav"),
    vecty.Class("nav-tabs"),
    ),
    elem.ListItem(
    vecty Markup(vecty Class("nav-item"))

    View Slide

  42. main.go
    main.go
    const API = "https://jsonplaceholder.typicode.com"
    func getResource(url string, v interface{}) error {
    resp, err := http.Get(url)
    if err != nil {
    return err
    }
    defer resp.Body.Close()
    return json.NewDecoder(resp.Body).Decode(&v)
    }
    func main() {
    vecty.AddStylesheet("assets/bootstrap.min.css")
    vecty.AddStylesheet("assets/app.css")
    r := router.New()
    r ForceHashURL = true

    View Slide

  43. 中規模な動作例
    中規模な動作例
    Users Posts
    Leanne Graham
    [email protected]
    Ervin Howell
    [email protected]
    Clementine Bauch
    [email protected]
    Patricia Lebsack
    [email protected]
    Chelsey Dietrich
    [email protected]
    Mrs. Dennis Schulist
    [email protected]
    Kurtis Weissnat
    [email protected]
    Nicholas Runolfsdottir V
    [email protected]

    View Slide

  44. QR
    コードコンポーネント
    QR
    コードコンポーネント

    View Slide

  45. QR
    コード実装例
    QR
    コード実装例
    package components
    import (
    "bytes"
    "log"
    "github.com/gopherjs/vecty"
    "github.com/gopherjs/vecty/elem"
    "github.com/aaronarduino/goqrsvg"
    "github.com/ajstarks/svgo"
    "github.com/boombuler/barcode/qr"
    )
    // QRCode ...
    type QRCode struct {

    View Slide

  46. QR
    コード動作例
    QR
    コード動作例
    QR Code Text:
    WIFI:S:;
    T:;
    P:;;

    View Slide

  47. スゴイところ
    スゴイところ
    JS の QR ライブラリつかってない。
    JS を意識していない Go のライブラリを
    GopherJS から利用しているだけ。

    View Slide

  48. GopherJS
    開発ツール
    GopherJS
    開発ツール
    「gopherjs serve <パッケージパス>」が基本
    あとは Go ソースを書き換えた状態で
    「 」にアクセスするだけで
    再構築した js がサーブされます。
    index.html があればそれをサーブします。
    存在しない場合はミニマムなものがサーブされます。
    http://localhost:8080

    View Slide

  49. デバッグ
    デバッグ
    もちろんデバッグもできます。
    Go のソースコードで!

    View Slide

  50. ビルド
    ビルド
    「-m」にてミニファイした結果が出力されます。
    $ gopherjs build -m -o app.js .

    View Slide

  51. チャットアプリの実際
    チャットアプリの実際
    https://github.com/nobonobo/vecty-sample

    View Slide

  52. <リポジトリルート>
    app/ フロントパッケージルート
    backend/ バックエンドルート
    main.go サーバー起動実装

    View Slide

  53. 開発時のサーバー
    開発時のサーバー
    gopherjs serve を別途起動しておく
    /api/をバックエンドハンドラにマップ
    その他を「gopherjs serve」にリバースプロキシ
    func main() {
    u, _ := url.Parse("http://localhost:8080")
    rp := httputil.NewSingleHostReverseProxy(u)
    http.Handle("/", rp)
    http.Handle("/api/", backend.New())
    l, err := net.Listen("tcp", ":8888")
    if err != nil {
    log.Fatalln(err)
    }
    log.Println("listen:", l.Addr())
    if err := http.Serve(l, nil); err != nil {
    log.Fatalln(err)
    }
    }

    View Slide

  54. <フロントパッケージルート>
    vendor/ その他の Go 依存パッケージ
    actions/ アクション定義群
    assets/ 静的ファイル群
    components/ コンポーネントセット
    dispather/ ディスパッチャ実装
    models/ アプリモデル定義群
    store/ アプリストレージ実装
    router/ ルーター実装
    views/ アプリ向けビューセット
    main.go メイン実装
    index.html アプリ HTML

    View Slide

  55. チャットアプリ動作例
    チャットアプリ動作例
    Top
    dead‑simple chap app
    Create New Room

    View Slide

  56. パッケージが分かれてると
    パッケージが分かれてると
    追加するモデルやストア
    追加するアクション
    追加する静的ファイル
    追加するコンポーネント
    追加するビュー
    追加先に迷わなくて済みます。

    View Slide

  57. パッケージ間依存
    パッケージ間依存
    store -> models
    actions -> models, store
    components -> models, dispatcher
    views -> store, components
    振る舞いと見た目とアプリデータが
    疎結合

    View Slide

  58. フロントのデプロイ
    フロントのデプロイ
    以下のファイル群を静的にサーブ
    assets/
    index.html
    app.js
    内容 必要 追加








    View Slide

  59. ロード時間の解決案(1)
    ロード時間の解決案(1)
    スピナー表示のあとで async 付きで JS を読む。






    Loading...



    View Slide

  60. ロード時間の解決案(2)
    ロード時間の解決案(2)
    先日公開されたサービス
    GopherJS パッケージを CDN キャッシュ
    jsgo.io
    https://github.com/dave/jsgo

    View Slide

  61. Vecty
    の Pros/Cons
    Vecty
    の Pros/Cons

    View Slide

  62. Pros
    Pros
    基本の知識は HTML/CSS/Go のみで OK
    API やモデル定義をクラサバで共有できる
    成果物は ES5 相当で polyfill 不要
    豊富な PureGo のライブラリが利用可能
    型のある開発は素晴らしい
    Vecty 本体は small & clear なので読みやすい

    View Slide

  63. Cons
    Cons
    結局 DOM まわりの知識は必要
    DOM まわりを Go で置き換えるのに慣れが必要
    JS の既存 lib 使うならやっぱり JS 知識が必要
    成果物 JS のファイルサイズがやや大きめ
    開発中でもパッキングするのでリロード重い
    コンポーネントの設計はやはり難しい
    Vecty 安定板までに破壊的変更の予定がある

    View Slide

  64. まとめ
    まとめ

    View Slide

  65. とにかくビルドが早い
    とにかくビルドが早い
    型チェック
    トランスパイル
    ミニファイ
    パック
    上記処理のほとんどを1度のパースで行う。

    View Slide

  66. JS
    の Isomorphic
    開発
    JS
    の Isomorphic
    開発
    nodejs/yarn
    webpack/esm/babel
    eslint/prettier/flowtype
    mocha/react か vue.js
    etc...
    それぞれ要素技術ごとに薄い本ができるツ
    それぞれ要素技術ごとに薄い本ができるツ
    ラミ
    ラミ

    View Slide

  67. Go
    の Isomorphic
    開発
    Go
    の Isomorphic
    開発
    go: 依存/整形/型検査/Lint/テスト/バックエンド
    gopherjs: 開発サーバー/ES5/ミニファイ/パック
    vecty: 仮想 DOM フレームワーク
    たったこれだけで始められる
    たったこれだけで始められる
    Go
    に慣れた人が新たに覚える事は非常に少ない
    Go
    に慣れた人が新たに覚える事は非常に少ない

    View Slide

  68. いろんな資産が使える
    いろんな資産が使える
    PureGo の資産はそのまま GopherJS に
    JS の資産はラップすれば GopherJS に
    GopherJS の資産は GopherJS に
    GopherJS

    GopherJS
    はジャイアニズム
    ジャイアニズム

    View Slide

  69. しなくていいこと
    しなくていいこと
    毎年追加される言語仕様を追う
    モジュール機構の選択
    依存ツールメジャーバージョンアップ追従
    コード整形ツールの調整
    ツール間の競合機能の調整
    バージョンの不整合の調整

    View Slide

  70. GopherJS+Vecty
    GopherJS+Vecty
    おすすめです
    おすすめです

    View Slide

  71. 応用 1
    応用 1
    面白そう
    pouchdb(js)/couchdb(ネイティブ)両対応
    pouchdb 使うとオフライン動作し、
    オンライン化した時にクラウド上の
    couchdb に同期なんてことができる。
    Firebase(mBaaS)のような環境を作れるかも?
    だれかやってみて!
    だれかやってみて!
    kivik(isomorphic)

    View Slide

  72. 応用 2
    応用 2
    gRPC サーバーをバックエンドにできる。
    クラサバ結合部の設計は gRPC の IDL で完結する。
    だれかやってみて!
    だれかやってみて!
    gopherjs-grpc-websocket

    View Slide

  73. 質問?
    質問?

    View Slide

  74. おしまい
    おしまい

    View Slide