修行編 - 1日目

やったこと

1. Django + Reactプロジェクトの計画

いつもは7日間のWeekly Sprintを組んで作業するのですが、今回はスピードが欲しいため3日間でSprintを切って細かく振り返りを行えるようにしました。

タスクの管理はGitHub Projectを利用しています。

github.com

2. APIドキュメントの作成

お決まりのSwaggerを利用してREST APIの雛形を作成しました。

ForkしたSwagger Editor Liveの設定と、Dockerを利用したモックサーバーの生成フローの導入までコピペでDone。

github.com

いい加減Yaml書くのツライなーという気持ちが高まってきたのでgRPC向けにprotoを書くことも考えたのですが、初めてのDjangoプロジェクトなのでベーシックなHTTP + RESTFulでがんばります

3. Flutterアプリの写経だん

inKinoというOSSのFlutterアプリが非常に完成度も高く勉強になるので、数日前から写経を行っていました。

github.com

ようやくReduxとUIの設定が終わり一通り動くところまで完成。

Viewの実装パターン等もフワッと理解できたので、近い将来にガチめなFlutterアプリをスクラッチで書くぞという機運が高まってきました。

これにてしばらくFlutterはお休みです。

4. ランニング

久々に走りました。東京に来てからは初めてです。

平日の昼間ということもあり人も少なく、外気も適温で絶好のコンディションでした。

が、、、

予想以上に体力の減退がひどく、3kmほど走ったところで死亡😇😇😇

続くかなこれ・・・

5. 帰省しました

京都に戻ってきました。

上賀茂神社で蛍を見る恒例のイベントも無事完了。月曜日まで初夏の京都を楽しむ予定です。

6. NBA Final観た

Game 1勝ちましたね。

サンキューJ.R.

明日やること

弟の誕生日だったり、入洛する祖母の案内だったりで作業時間取れないフラグがビンビン立ってます笑

  • Next.jsでプロジェクト立ち上げ
  • TypeScriptの設定
  • React in patternsを読む

頑張るやで。

それではみなさん良い週末を!

修行編 - 0日目

ひょんなことから1ヶ月ほどゆっくりする時間ができました。

久々に納期の概念から解放されるまたと無い機会なので、今一度立ち止まってカラダとアタマを鍛え直そうと思います。

カラダを鍛える

走ります。

長く続けたバスケットを止めてからというもの、サイクリングの他には碌に運動というものをしてきませんでした。

体脂肪率8%の中学時代の身体をもう一度!とまでは言いませんが、まずはお腹周りの肉を落とす&持久力をつける意味で毎日ランニングをやってみます。

幸い家から3分で駒沢オリンピック公園があるので地の利を活かしたいです。ホコリを被ったFitBitも充電しなければ・・・

アタマを鍛える

新しい武器を身につけます。

書き慣れたRuby on RailsとVue.jsをしばらく封印して、他のスキルを業務レベルで使えるようにしたいです。

まずは基礎であるPython(Django)とReactをガッツリ復習して自信を持って仕事が受けられるようにするのが目標。作りたいサービスがいくつかあるので2週間くらいでカタチにするところまでかな。

現在取り組んでいるGo + Flutterのネイティブアプリも完成させたいですが、そちらはおそらく保留。後半の2週間はせっかくなのでScalaやRustのような新しい概念を学ぼうと思います。

そのあとはKotolinもやりたいしk8sも勉強したいしML Kitも触りたいしword2vecでも遊びたいし(ry

とにかく時間が欲しい。

生活を切り詰めて+もう1ヶ月お休みするのもアリなんじゃないかという気がしてきました(フラグ

ただし欲望の赴くままにあれもこれもと手をつけると中途半端な結果になりそうなので、戦略的にスキルアップを図っていきたいと思います。

というわけで明日は・・・

  • Flutterプロジェクト(写経)をキリの良いところまで進める
  • Django + Reactプロジェクトのカンバンとマイルストーンを用意する
  • SwaggerにAPI定義を書いてモックサーバーを立てる

くらいまで進めれば最高ですね。

新幹線で京都に移動したり、両親と食事したりというイベントがあるので先行きは不安です笑

最後に

今回「ヒマ人になるよー」という話をしたところ、本当にビックリするほど多くの方から仕事のお誘いをいただきました。

プログラマとしての経歴も浅く東京に来てからの日も浅い自分にこれだけお声がけをいただけて、本当に感謝の気持ちでいっぱいです。

生憎そのすべてにはお応えできそうにありませんが、この機会にまたパワーアップしてより大きな価値を提供できるよう頑張ります。

改めて今後とも不肖Andyをどうぞよろしくお願い致します。

P.S.

関東近郊の方へ:

食事のお誘いをいただければいつでもどこでも飛んでいきます。

GoでWebアプリを作ろう 第一回 : Goで簡単なCRUD

こんにちは、Andyです。

普段はフロントエンドチームでJSばかり書いているのですが、せっかくGoの会社に入ったので良い機会だと思いGoに入門してみました。「Goの作法」を知ればより裏側のシステムについての理解が深まり、フロント側も良いプロダクトが作れるんじゃないかなと期待しています。

せっかく新しい言語を学ぶので、学習の中でやった事や詰まった事を文字で残そうというのが本記事の目的。

とてもじゃないですが1回で全てをカバーできないので数回に分けてチャレンジします。

手探りで自分なりのベストプラクティスを模索している最中なのでマサカリ大歓迎です。

f:id:andoshin11:20180416012128p:plain

現在のスタック

学習を始めるにあたって、自分のエンジニアとしてのスタックはこんな感じ。

ちなみにgolangの経験値はA Tour of Goを流し読みした程度です

作るもの

簡単なTODOアプリです。次回以降でユーザー認証機能も乗っけていきます。

2018年なので当然*1フロントはSPAで受ける事を考えて、JSONのI/Oを受けるAPIサーバーを想定しています。

またルーティングにGinを利用しています

プロジェクト構成

Railserとして一番始めにつまづくのがGoのプロジェクト構成です。いろいろと宗派があるようなので詳しくは触れません。

$GOPATH$HOME/dev/goに設定しています。プロジェクトは $Home/dev/go/github.com/andoshin11/go-web-appに配置しました。

アーキテクチャについてはDDDやその他のClean Architectureをベースとしたものも検討したですが、初めてのアプリなのでわかりやすくMVC(or MC?)デザインパターンを採用。

最終的なファイル構成はGitHubを参考のこと

github.com

パッケージ管理

依存関係の管理にはdepを利用

$ brew install dep
$ dep init

Hello, world!

まずは基本となるControllerを src/controller以下に作成します

// src/controller/index.go
package controller

import (
    "net/http"

    "github.com/gin-gonic/gin"
)

// IndexGET displays application index page
func IndexGET(c *gin.Context) {
    c.String(http.StatusOK, "Hello, world!")
}

上記をHandlerとして登録

// main.go
package main

import (
    "github.com/andoshin11/go-web-app/src/controller"
    "github.com/gin-gonic/gin"
)

func main() {
    router := gin.Default()

    router.GET("/", controller.IndexGET)
    router.Run(":8080")
}

$ go run main.goでサーバーを起動

f:id:andoshin11:20180415212530p:plain

ページが表示されました👏👏

Databaseの用意

DBはMySQLを利用していきます。

$ mysql -u root
mysql> CREATE DATABASE gwa;

なにかと衝突しそうな名前だけどとりあえずDBを作成。

マイグレーションツールはgooseを利用。

$ go get github.com/pressly/goose
$ mkdir db
$ touch db/dbconf.yml
// db/dbconf.yml
development:
    driver: mymysql
    open: tcp:localhost:3306*gwa/root/hogehoge

DBの情報が正しいか疎通確認

$ goose status
goose: status for environment 'development'
    Applied At                  Migration
    =======================================

テーブルの作成

gooseで taskテーブルを作成します

$ goose create createTask sql

上記のコマンドを実行するとタイムスタンプを名前に含んだマイグレーションファイルが db/migrations以下に作成されるので、そちらにSQLを追記

-- db/migrations/2018......createTask.sql

-- +goose Up
-- SQL in section 'Up' is executed when this migration is applied
CREATE TABLE IF NOT EXISTS task (
    id INT UNSIGNED NOT NULL,
    created_at TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
    updated_at TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
    title VARCHAR(255) NOT NULL,
    PRIMARY KEY(id)
);


-- +goose Down
-- SQL section 'Down' is executed when this migration is rolled back
DROP TABLE task;

$ goose upを実行してマイグレーションを実行し、テーブルが作成されていることを確認します

f:id:andoshin11:20180415233901p:plain

ORM(?)の選定

MySQLとの接続をラップしてくれるORMとしてGORMsqlxなどを検討したものの、

「いちいちstructのメンテやってられんわ」

という強い理由によりxoを採用しました。

現実問題としてファットモデルも避けたかったし、基本的なCRUDを記述することだけ考えると必要十分な気がします。

$ go get github.com/knq/xo
$ mkdir src/model
$ mkdir -p db/xo/templates
$ cp $GOPATH/src/github.com/knq/xo/templates/* db/xo/templates/
$ xo mysql://<user>:<pass>@<host>/<db> -o src/model/ --template-path db/xo/templates/

上記の手順を実行すると src/model/**.xo.goというファイルが作成されます。 goosedbversion.xo.goは必要ないので削除して大丈夫です。

Modelの接続

xoコマンドで各テーブルのmodelファイルが作成されたのでアプリケーションに接続していきます。まずは src/model/model.goを作成してDBインスタンスを返す関数を記述。

// src/model/model.go
package model

import (
    "database/sql"
    "log"
    "os"

    // mysql driver
    _ "github.com/go-sql-driver/mysql"
)

// DBConnect returns *sql.DB
func DBConnect() (db *sql.DB) {
    dbDriver := "mysql"
    dbUser := "root"
    dbPass := os.Getenv("MYSQL_ROOT_PASSWORD") // 環境変数から取得
    dbName := "gwa"
    dbOption := "?parseTime=true"
    db, err := sql.Open(dbDriver, dbUser+":"+dbPass+"@/"+dbName+dbOption)
    if err != nil {
        log.Fatal(err)
    }
    return db
}

続いてTODOの一覧を返すコントローラーアクションを記述。

xoの設定をしておいてなんですが、今回だけControllerにデータ取得処理を直接書きました。本来なら templateを拡張すべきですが、実際のユースケースとして任意のテーブルのレコードを全取得するケースは中々ないので無視。

// src/controller/task.go
package controller

import (
    "database/sql"
    "fmt"
    "net/http"
    "time"

    "github.com/andoshin11/go-web-app/src/model"
    "github.com/gin-gonic/gin"
)

// TasksGET returns list of tasks
func TasksGET(c *gin.Context) {
    db := model.DBConnect()
    result, err := db.Query("SELECT * FROM task ORDER BY id DESC")
    if err != nil {
        panic(err.Error())
    }

    tasks := []model.Task{}

    // iterate result
    for result.Next() {
        task := model.Task{}
        var id uint
        var createdAt, updatedAt time.Time
        var title string

        err = result.Scan(&id, &createdAt, &updatedAt, &title)
        if err != nil {
            panic(err.Error())
        }

        task.ID = id
        task.CreatedAt = createdAt
        task.UpdatedAt = updatedAt
        task.Title = title
        tasks = append(tasks, task)
    }
    fmt.Println(tasks)
    c.JSON(http.StatusOK, gin.H{"tasks": tasks})
}

返り値はリスト形式のJSONです。 main.goで新たなnamespaceを作成して上記のHandlerを登録

// main.go
...


func main() {
    router := gin.Default()

    // API namespace
    v1 := router.Group("/api/v1")
    {
        v1.GET("/tasks", controller.TasksGET)
    }

    router.GET("/", controller.IndexGET)
    router.Run(":8080")
}

サーバーを再起動してエンドポイントが有効なことを確認。当然結果は空っぽです。

f:id:andoshin11:20180415232608p:plain

雑にレコードを突っ込んでレスポンスを再確認

f:id:andoshin11:20180415234152p:plain

f:id:andoshin11:20180415234234p:plain

良さそう

タスクを追加する

そういえば自動採番の設定を忘れていたので新しいmigrationを追加

$ goose create autoIncrementTask sql
// 2018....autoIncrementTask.sql

-- +goose Up
-- SQL in section 'Up' is executed when this migration is applied
ALTER TABLE task MODIFY id INT UNSIGNED AUTO_INCREMENT NOT NULL;

-- +goose Down
-- SQL section 'Down' is executed when this migration is rolled back
ALTER TABLE task MODIFY id INT UNSIGNED NOT NULL;
$ goose up

xoのアップデートも忘れずに行います。

taskコントローラーにHandlerを追加

// src/controller/task.go

...

// TaskPOST creates a new task
func TaskPOST(c *gin.Context) {
    db := model.DBConnect()

    title := c.PostForm("title")
    now := time.Now()

    task := &model.Task{
        Title:     title,
        CreatedAt: now,
        UpdatedAt: now,
    }

    err := task.Save(db)
    if err != nil {
        panic(err.Error())
    }

    fmt.Printf("post sent. title: %s", title)
}
// main.go
...
v1.POST("/tasks", controller.TaskPOST)
...

f:id:andoshin11:20180416002605p:plain

DBにレコードが追加されるようになりました

タスクの更新

新しいHandlerを taskコントローラーに追加

// src/controller/task.go

...

// TaskPATCH updates a task
func TaskPATCH(c *gin.Context) {
    db := model.DBConnect()

    id, _ := strconv.Atoi(c.Param("id"))

    task, err := model.TaskByID(db, uint(id))
    if err != nil {
        panic(err.Error())
    }

    title := c.PostForm("title")
    now := time.Now()

    task.Title = title
    task.UpdatedAt = now

    err = task.Update(db)
    if err != nil {
        panic(err.Error())
    }

    fmt.Println(task)
    c.JSON(http.StatusOK, gin.H{"task": task})
}
// main.go

...
v1.PATCH("/tasks/:id", controller.TaskPATCH)
...

f:id:andoshin11:20180416004713p:plain

更新成功!

タスクの削除

同じ要領でコントローラーに追記

// src/controller/task.go

// TaskDELETE deletes a task
func TaskDELETE(c *gin.Context) {
    db := model.DBConnect()

    id, _ := strconv.Atoi(c.Param("id"))

    // Check if record exists
    task, err := model.TaskByID(db, uint(id))
    if err != nil {
        panic(err.Error())
    }

    err = task.Delete(db)
    if err != nil {
        panic(err.Error())
    }

    c.JSON(http.StatusOK, "deleted")
}
// main.go

...
v1.DELETE("/tasks/:id", controller.TaskDELETE)
...

f:id:andoshin11:20180416010451p:plain

削除も問題なくできました。

まとめ

複雑な機能は何もありませんがとりあえずのAPIサーバーができました。Go楽しいですね。

第二回以降はログイン機能とかモデルの関連付けとか作業の自動化とかやりたいです。

それではまた次回

*1:text/templateとか使ってる人類いるんですか?

君は2クリックでVue appを立ち上げたことがあるか!!<CodeSandboxを世の中に広めなければと思った件>

www.youtube.com

ちょっと時間が経ってしまったのですが、2月にアムステルダムで行われたVue conf AmsterdamFrontend Developer Love conferenceに参加してきました。

初めての技術カンファレンス&海外でのイベントということで学ぶことばかりの数日間だったのですが、今回はその中でも特に印象に残ったセッションをご紹介します。この記事を読んで「CodeSandboxおもしろそう!」「使ってみたい!!」という方が1人でも増えると嬉しいです。

CodeSandboxとは

CodeSandboxとはオンラインで動くコードエディターで、ReactやVue、Angularなどのフレームワークですぐにアプリケーションを作成できるのが特徴です。

codesandbox.io

GitHubでオープンに開発が行われているのですが、なにより驚いたのがプロジェクトリーダーのIvesがまだ21歳の学生であること!まだ日本ではあまり知られていないプロダクトですが、将来がとても楽しみなのでこのCodeSandboxの便利な機能をいくつかご紹介したいと思います。

Ives van Hoorne

2クリックでアプリを立ち上げる

まずはCodeSandboxのページを開き、右上の"Create Sandbox"をクリックします

するとフレームワークを選択する画面が出てくるので、Vueのアイコンをクリックします

完成です👏👏

Lower the learning curve.

Installing tooling should not stand in the way of getting started

という言葉にもあるように、CodeSandboxでは「コードを書きたい!」と思った人がなるべくスムーズに開発できよう徹底的にしきいを下げる工夫が行われています。

画面に並んでいるのは左端から順にファイルツリー、エディタ、ミニブラウザです。 もちろんここにNPMパッケージを追加したり import/exportを記述することも可能です。

試しに簡単なButtonコンポーネントを作成してみましょう

f:id:andoshin11:20180412003851p:plain

propsで受け取った textを表示するだけのbuttonです

f:id:andoshin11:20180412003907p:plain

f:id:andoshin11:20180412003949p:plain

ローカルで開発するのと全く同じ感覚で表示までできました

f:id:andoshin11:20180412004133p:plain

画面下部のコンソールにエラーも出力してくれます

f:id:andoshin11:20180412004824p:plain

外部ライブラリを読み込もうとすると怒られますが、

ボタンをクリックするだけで自動でライブラリがダウンロードされ、 package.jsonにも追記してくれます。

またCodeSandboxの特徴としてライブラリの設定ファイルをGUIからconfigできる機能があります。各種設定の概要を確認しながら調整できるのでなかなか便利です

f:id:andoshin11:20180412005651p:plain

さらにさらに、ライブラリの追加もGUIの検索ボックスから行えます(バージョンも指定できる)。このお手軽感めっちゃ好き❤️

Encourage sharing & discovery

CodeSandboxで作成したアプリは簡単に共有ができるだけでなく、お目当のプロジェクトを見つけるのも簡単に行えます。

せっかくなので <iframe/>でエディタを埋め込んでみました。みなさんも実際に僕が書いたコードを確認できるかと思います。ちゃんと動作もするはず・・・

共有方法は簡単。

f:id:andoshin11:20180412012102p:plain

f:id:andoshin11:20180412012110p:plain

左上の"share"をクリックすると埋め込みリンクやエディタの共有リンクがすぐに取得できます。

また、CodeSandbox上から直接GitHubのレポジトリを作成してpushすることも可能です

f:id:andoshin11:20180412021114p:plain

github.com

リポジトリができた!!

他のプロジェクトを探したい時はSearchページから検索するだけ。プロジェクト名だけでなく使っているフレームワークやライブラリからも絞りこむことができます

例えばElement UIを使っているプロジェクトならこんな具合に。

プロジェクトのページに飛べば実際にどのように動作して、どのようにコードが書かれているのか学べます

Easy to import

今までに作成したプロジェクトをCodeSandboxに取り込むのも簡単です。方法としては大きく

という3つの方法が用意されていますが、今回はDEMOとしてGitHub importを試してみましょう。

github.com

以前作成したVue ToDo List sample(https://github.com/andoshin11/vue-todoList-sample)というレポジトリがあるのでこちらでお試し。

importするにはこのURLを貼り付けるだけ。あとは自動でCodeSandbox上にワークスペースが作成され、GitHubと同期された状態になります。

一瞬でimportが完了しました。こちらも <iframe/>で埋め込んでみるので是非触ってみてください

https://codesandbox.io/s/github/andoshin11/vue-todoList-sample/tree/master/

Local Editor Experience

CodeSandbox上ではmonaco-editorが動いています。

github.com

monaco-editorはブラウザ上で動くVS Codeのようなものです。そこで veturをもとにした monaco-vueが動作しているため、まるでVS Code上で開発しているかのような体験がブラウザで行えるのは良いですね。

その他の機能

ここでは紹介しきれないほどまだまだ機能がたくさんあるのですが、主だったものだけ箇条書き

  • Parcelサポート
  • preactサポート
  • svelteサポート
  • Sassサポート
  • TypeScriptサポート
  • Prettierデフォルトサポート
  • ES Lintデフォルトサポート
  • Liveコラボレーション機能
  • Jest実行サポート
  • 静的ファイルホスティング
  • HMR
  • Emmetサポート
  • zipエクスポート

などなど素敵なものがたくさん。OSSなので日々機能が追加されていくのも魅力の一つです。

まとめ

CodeSandboxめっちゃ良い👏👏

簡単なハンズオンやデモをする際にサクッと環境が立ち上がるのが良いですね。そしてシェアの手軽さも嬉しい。

VueやReactの楽しさを知る前にnpm, webpack, babel等の設定でドロップしてしまった人も多いと思います。これからはCodeSandboxをスッと差し出して「とりあえず触ってみて!」と言えるようになると素敵ですね。

普通に開発環境として優秀なので出先でGitHubからプライベートモードでimport → 開発 & デバッグ → 動作確認までワンストップで行えそうで期待大です。

まだリリースされて1年ほどなので今後がとても楽しみ。みなさんもこの機会にぜひ試してみてください!!

codesandbox.io

おまけ

CodeSandboxへのお布施やIvesへの投げ銭はこちらからどうぞ👇👇

https://codesandbox.io/patron

Pull RequestごとにStorybookがビルドされたら最高じゃね?

merpayでは積極的にStorybookを活用してコードと見た目の両方の観点でレビューを行なっていますが、Pull Requestが飛んでくるたびに該当ブランチをpullしてローカルでStorybookを起動するのが大変という運用上の問題を抱えていました。

そこでソリューションチームの協力のもと、PRが作成されるたびにCIからGAE上にユニークなpathをもったStorybookを配信する仕組みが導入されたのが先週のこと。とてもめでたい👏👏

f:id:andoshin11:20180407223751p:plain

これによってPRのデザインをチェックする時もURLをクリックするだけでよくなりました。

UI開発の概念がひっくり返るほど便利だったので、この仕組みをプライベートの開発でも導入したいと思います。

やりたいこと

  • Storybookをホスティングする環境(URLの共有だけで完結するもの)を作りたい
  • Pull Requestが作成されるごとに自動で新しいURLを発行したい
  • Pull Requestがクローズされたら上記の環境を自動で破壊したい

これらの要件を達成するため始めは社内のスクリプトを丸ごとパクろうかと思いましたが、プロジェクトごとにGCPを設定するのはなかなか大変なので別の方法を模索してみます。

Heroku Review Apps

Heroku Review Appsとは、Herokuにpipelineを設定することでPull Requestが作成されるたびに独自のステージング環境を作成してくれるものです。

さらにPull Requestがクローズされたらそのステージング環境を一定時間後に破棄してくれるなど、今回の要件にピッタリの機能が詰まっています。こいつを今回は使っていきましょう!

プロジェクトの立ち上げとStorybookの設定

いつものようにvue-cli 3.0でプロジェクトを立ち上げました。

gyazo.com

RouterやStoreの設定を自動でやってくれるのでいつも重宝しています。以下の記事を参考にしてこちらにStorybookを設定。

studio-andy.hatenablog.com

シュッとButton componentを作成します。

gyazo.com

GitHubレポジトリとHerokuアプリの作成

GitHubにレポジトリを作成して masterをpush。Heroku側でもアプリを作成して接続します

f:id:andoshin11:20180407230416p:plain

f:id:andoshin11:20180407230430p:plain

StorybookをビルドしてExpressで配信する

package.jsonにコマンドを一行追加。

"build-storybook": "build-storybook -c .storybook -o storybook-static"

このコマンドを実行することでStorybookが静的なページとしてビルドされますが、このままではHerokuで公開できません。

そこで今回はExpressをHeroku上で動かしてそこから配信してみようと思います。まずは Expressを追加。

$ yarn add express

server.jsを作成

// server.js
var express = require("express");
var path = require("path");
var serveStatic = require("serve-static");

app = express();
app.use(serveStatic(__dirname + "/storybook-static"));

var port = process.env.PORT || 5000;
app.listen(port);

そしてHerokuにデプロイする時に実行されるコマンドを package.jsonに追記します

"heroku-postbuild": "npm run build-storybook",
"start": "node server.js"

heroku-postbuildはHeroku上で自動で npm installが走ったあとに実行されるフックで、 startはデプロイ完了時にトリガーされるものです。

最後にHeroku上でAutomatic DeployをEnableにし、 masterブランチがpushされたら自動でデプロイが走るように設定します。

f:id:andoshin11:20180407232327p:plain

この状態で masterをpushもしくは手動デプロイし、ビルドされたStorybookが無事にExpressから配信されていることを確認

f:id:andoshin11:20180407232459p:plain

f:id:andoshin11:20180407232732p:plain

これで最新のStorybookがHerokuから配信されるようになりました🎉

Heroku Review Appsの設定

ここからはPRごとに配信を行うためにHeroku Review Appsの設定を行なっていきます。まずは "create new pipeline"で storybook-review-appという名前のpipelineを作成。(ここの名前は自由です)

f:id:andoshin11:20180407233016p:plain

f:id:andoshin11:20180407233200p:plain

そうするとpipelineの管理画面が出てくるので画面左端より "Enable Review Apps..."を選択

f:id:andoshin11:20180407233301p:plain

f:id:andoshin11:20180407233404p:plain

Enableにするとプロジェクト内で app.jsonファイルを作成するように言われます。 app.jsonの中身はGUIからポチポチ設定できるのですが、今回は特に触らなくて大丈夫です。Buildpackが nodejsになっていることだけ確認しておいてください。

f:id:andoshin11:20180407233719p:plain

f:id:andoshin11:20180407233731p:plain

最下部の "Commit to Repo"を押すとプロジェクトの masterブランチに自動で app.jsonがcommitされます(ちょっと気持ち悪い)

f:id:andoshin11:20180407234039p:plain

最後にReview Appの設定を行なって完了です。Destroyの期限は最長30日まで設定できますがとりあえず1日で問題ないでしょう。

f:id:andoshin11:20180407234149p:plain

PRを作成してみる

Heroku Review Appsがちゃんと動作するか実際にテストしてみましょう。ボタンの文言だけ変更する簡単なPRを作成

f:id:andoshin11:20180407234543p:plain

するとpipeline上で自動でアプリのビルドが始まります

f:id:andoshin11:20180407234524p:plain

ログも見れちゃう。

f:id:andoshin11:20180407234618p:plain

ビルド完了。

f:id:andoshin11:20180407234757p:plain

f:id:andoshin11:20180407234822p:plain

すごい!新しいバージョンが配信されてる!!

f:id:andoshin11:20180407234931p:plain

そしてPRのスレッド上にもデプロイが完了した旨の通知が届いています。これをSlackで受け取ったりすると良さそうですね。

f:id:andoshin11:20180407235044p:plain

URLもおしりにPRナンバーが付与されており、ユニークであることがわかります。

PRをクローズする

PRをマージしてクローズしてみました

f:id:andoshin11:20180407235201p:plain

f:id:andoshin11:20180407235449p:plain

一瞬で環境が消えた😲めっちゃ優秀

まとめ

Heroku Review Appsが予想の数段上を行く優秀さでした。これでいつでも開発メンバーやデザインチームの手を煩わせることなくレビューしてもらうことができます。Heroku自体にBasic認証などを設定しておけばプライベートプロジェクトでも大丈夫。

みなさんも(Herokuの無料枠に注意して)どんどんStorybookを育てていきましょう。今回のコードは下に貼っておきます。

github.com

P.S.

HerokuでNode.jsが動くならそもそも毎回静的ファイルをビルドしなくて良かった説ある...