Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

でも・・・ でも・・・

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

TITLE

elem.Anchor( // vecty.Markup(prop.Href("/page")), vecty.Text("link"), // link ), // ) //

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

プロパティ プロパティ コンポーネント定義のフィールド 任意の型に`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)) }

Slide 17

Slide 17 text

ミニマムな例 ミニマムな例

Slide 18

Slide 18 text

ミニマムな実装例 ミニマムな実装例 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) }

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

プロパティな例 プロパティな例

Slide 21

Slide 21 text

プロパティな実装例 プロパティな実装例 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) })

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

1 方向に回す 1 方向に回す

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

ルーターの例 ルーターの例

Slide 35

Slide 35 text

ルーターの実装例 ルーターの実装例 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(

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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"))

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

中規模な動作例 中規模な動作例 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]

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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 {

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

開発時のサーバー 開発時のサーバー 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) } }

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

Vecty の Pros/Cons Vecty の Pros/Cons

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

まとめ まとめ

Slide 65

Slide 65 text

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

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

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

Slide 68

Slide 68 text

いろんな資産が使える いろんな資産が使える PureGo の資産はそのまま GopherJS に JS の資産はラップすれば GopherJS に GopherJS の資産は GopherJS に GopherJS は GopherJS はジャイアニズム ジャイアニズム

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

GopherJS+Vecty GopherJS+Vecty おすすめです おすすめです

Slide 71

Slide 71 text

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

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

質問? 質問?

Slide 74

Slide 74 text

おしまい おしまい