Upgrade to Pro — share decks privately, control downloads, hide ads and more …

GopherJS+Vecty

Bc142fbacf80354db09fe3dda44afcf1?s=47 irieda
February 24, 2018

 GopherJS+Vecty

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

Bc142fbacf80354db09fe3dda44afcf1?s=128

irieda

February 24, 2018
Tweet

Transcript

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

  2. お前誰よ? お前誰よ? メカトロソフト屋 Pythonista -> Gopher なんでも Go で書いちゃうひと Go

    歴は 5 年目 サイト: 会社: 144Lab(2017/07/01 から新社名) HN: @nobonobo http://golang.rdy.jp/
  3. リッチなフロントエンド作りたい? リッチなフロントエンド作りたい? もちろん もちろん 作りたいですよね! 作りたいですよね!

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

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

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

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

    ( 個人的な感想です) そこで そこで
  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
  9. 関連ライブラリ 関連ライブラリ トランスパイラ: 仮想 DOM: ルーター: Bootstrap4コンポーネントセット: GopherJS(BSD) Vecty(BSD) github.com/go-humble/router(MIT)

    github.com/nobonobo/bootstrap4(MIT)
  10. GopherJS の近況 GopherJS の近況 速度より安定性を重視 出力ターゲットは ES5 ES2015~ES2017 の新記述の最適化がない Go1.10

    サポートが着々と進んでる (Go 本体では WebAssembly 実装の実験も!!)
  11. Vecty の近況 Vecty の近況 鋭意安定板に向けて改善真っ只中 昨年中に 5~6 回の破壊的変更がありました 破壊的変更の予定がまだ少しあります 本格利用はしばしお待ちを

    HTML/CSS/JS の全てを Go で書ける というわりに CSS 支援は弱い
  12. Vecty の提供機能 Vecty の提供機能 HTML ビルダ レンダラー コンポーネント プロパティ ほんとこれだけ。

  13. HTML ビルダ HTML ビルダ タグ定義を使って生成 属性は Markup でラップして記述 慣れるまで大変 elem.Body(

    // <body> elem.Heading1(vecty.Text("TITLE")), // <h1>TITLE</h1> elem.Anchor( // <a href="/page"> vecty.Markup(prop.Href("/page")), vecty.Text("link"), // link ), // </a> ) // </body>
  14. レンダラー レンダラー コンポーネントのレンダリングは二種類ある RenderBody: 初期レンダリング Rerender: 差分レンダリング

  15. コンポーネント コンポーネント Core 実装と Render メソッドを持つ プロパティを元に HTML を生成する実装 コンポーネントツリーが仮想

    DOM 相当 type MyComponent struct { vecty.Core } func (c *MyComponent) Render() vecty.ComponentOrHTML { return elem.Header1(vecty.Text("Hello!")) }
  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)) }
  17. ミニマムな例 ミニマムな例

  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) }
  19. ミニマムな実行例 ミニマムな実行例 Top click

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

  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) })
  22. プロパティな実行例 プロパティな実行例 Top click

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

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

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

  26. Flux 相当 Flux 相当 サンプルが採用している方法を参考に。 アクション: メッセージ定義 ディスパッチ: メッセージのディスパッチャ モデル:

    アプリのデータモデル定義 ストア: アプリ全体のデータストア ビュー: 最上位コンポーネント(概念を追加) ハンドラー: 振る舞い実装(概念を追加)
  27. 1 方向に回す 1 方向に回す

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

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

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

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

    GopherJS はシングルスレッド動作のみなので 基本は排他処理等は気にしない
  32. ビュー ビュー コンポーネントの性質 HTML の組み合わせを抽象化 プロパティを持ち、それを元に描画を決定 DOM イベントをアクションに翻訳する ビューの性質 Body

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

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

  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(
  36. ルーターの実行例 ルーターの実行例 top link:next1 click

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

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

    メイン実装
  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
  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
  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"))
  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
  43. 中規模な動作例 中規模な動作例 Users Posts Leanne Graham Sincere@april.biz Ervin Howell Shanna@melissa.tv

    Clementine Bauch Nathan@yesenia.net Patricia Lebsack Julianne.OConner@kory.org Chelsey Dietrich Lucio_Hettinger@annie.ca Mrs. Dennis Schulist Karley_Dach@jasper.info Kurtis Weissnat Telly.Hoeger@billy.biz Nicholas Runolfsdottir V Sherwood@rosamond.me
  44. QR コードコンポーネント QR コードコンポーネント

  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 {
  46. QR コード動作例 QR コード動作例 QR Code Text: WIFI:S:<SSID>; T:<WPA|WEP|>; P:<password>;;

  47. スゴイところ スゴイところ JS の QR ライブラリつかってない。 JS を意識していない Go のライブラリを

    GopherJS から利用しているだけ。
  48. GopherJS 開発ツール GopherJS 開発ツール 「gopherjs serve <パッケージパス>」が基本 あとは Go ソースを書き換えた状態で

    「 」にアクセスするだけで 再構築した js がサーブされます。 index.html があればそれをサーブします。 存在しない場合はミニマムなものがサーブされます。 http://localhost:8080
  49. デバッグ デバッグ もちろんデバッグもできます。 Go のソースコードで!

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

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

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

  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) } }
  54. <フロントパッケージルート> vendor/ その他の Go 依存パッケージ actions/ アクション定義群 assets/ 静的ファイル群 components/

    コンポーネントセット dispather/ ディスパッチャ実装 models/ アプリモデル定義群 store/ アプリストレージ実装 router/ ルーター実装 views/ アプリ向けビューセット main.go メイン実装 index.html アプリ HTML
  55. チャットアプリ動作例 チャットアプリ動作例 Top dead‑simple chap app Create New Room

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

  57. パッケージ間依存 パッケージ間依存 store -> models actions -> models, store components

    -> models, dispatcher views -> store, components 振る舞いと見た目とアプリデータが 疎結合
  58. フロントのデプロイ フロントのデプロイ 以下のファイル群を静的にサーブ assets/ index.html app.js 内容 必要 追加 <html>

    <head> <meta charset="utf-8"> <script src="app.js"></script> </head> <body> </body> </html>
  59. ロード時間の解決案(1) ロード時間の解決案(1) スピナー表示のあとで async 付きで JS を読む。 <html> <head> <meta

    charset="utf-8"> <link href="assets/app.css" media="all" rel="stylesheet" /> </head> <body> <div class="loader">Loading...</div> </body> <script async src="app.js"></script> </html>
  60. ロード時間の解決案(2) ロード時間の解決案(2) 先日公開されたサービス GopherJS パッケージを CDN キャッシュ jsgo.io https://github.com/dave/jsgo

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

  62. Pros Pros 基本の知識は HTML/CSS/Go のみで OK API やモデル定義をクラサバで共有できる 成果物は ES5

    相当で polyfill 不要 豊富な PureGo のライブラリが利用可能 型のある開発は素晴らしい Vecty 本体は small & clear なので読みやすい
  63. Cons Cons 結局 DOM まわりの知識は必要 DOM まわりを Go で置き換えるのに慣れが必要 JS

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

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

  66. JS の Isomorphic 開発 JS の Isomorphic 開発 nodejs/yarn webpack/esm/babel

    eslint/prettier/flowtype mocha/react か vue.js etc... それぞれ要素技術ごとに薄い本ができるツ それぞれ要素技術ごとに薄い本ができるツ ラミ ラミ
  67. Go の Isomorphic 開発 Go の Isomorphic 開発 go: 依存/整形/型検査/Lint/テスト/バックエンド

    gopherjs: 開発サーバー/ES5/ミニファイ/パック vecty: 仮想 DOM フレームワーク たったこれだけで始められる たったこれだけで始められる Go に慣れた人が新たに覚える事は非常に少ない Go に慣れた人が新たに覚える事は非常に少ない
  68. いろんな資産が使える いろんな資産が使える PureGo の資産はそのまま GopherJS に JS の資産はラップすれば GopherJS に

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

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

  71. 応用 1 応用 1 面白そう pouchdb(js)/couchdb(ネイティブ)両対応 pouchdb 使うとオフライン動作し、 オンライン化した時にクラウド上の couchdb

    に同期なんてことができる。 Firebase(mBaaS)のような環境を作れるかも? だれかやってみて! だれかやってみて! kivik(isomorphic)
  72. 応用 2 応用 2 gRPC サーバーをバックエンドにできる。 クラサバ結合部の設計は gRPC の IDL

    で完結する。 だれかやってみて! だれかやってみて! gopherjs-grpc-websocket
  73. 質問? 質問?

  74. おしまい おしまい