Cloud FunctionsでIAMを利用する

Firebase便利ですよね。

とくに最近はCloud Functionsで簡単なServerlessプログラムを書いてライフハック*1に利用することが多いです。

Cloud FirestoreのイベントトリガーだけでなくHTTPリクエストによるトリガーも可能なため、Cron Jobを使用して定期的に関数を実行したりしています。

f:id:andoshin11:20180423234039p:plain

セキュリティとアクセス制限

Cloud Functionsの長所は誰でもHTTPリクエストで起動できるところであり、短所もまたその点にあります。

これは特定の権限を持った発信元のみに実行を許可したい際、問題になるでしょう。

今回は公式でも紹介されている上記の方法でその問題を解決します。

具体的には新たにCloud Storage Bucketを作成し、承認を行うプロキシとして利用する方法です。BucketはCloud Functionsと異なり柔軟な承認の仕組みを有しているため、リクエストが実行されるごとにそちらのAPIで権限を確認してレスポンスを切り替えます。

前提条件

以下の項目を満たしていることを確認してください。

ついでに複数のGCPプロジェクトを管理している方は以下の設定でデフォルトプロジェクトを変更しておくと便利です

$ gcloud config set core/project [PROJECT_NAME]

サービスアカウントを作成する

権限を付与するアカウントをCLIから作成します

$ gcloud iam service-accounts create test-account --display-name "Test Account"
> Created service account [test-account].

GCPコンソールから「IAMと管理」 -> 「サービスアカウント」と進むとアカウントが作られたことを確認できるはずです

f:id:andoshin11:20180423205631j:plain

Bucketの作成

Cloud Storage BucketCLIから作成します

$ gsutil mb gs://andoshin11-test-bucket 
> Creating gs://andoshin11-test-bucket/...

gs://~以降のBUCKET_NAMEは任意の名前を選択できますが、ユニークである必要があるため既に存在している場合はエラーで怒られます。こちらもGCPコンソールの「Storage」 -> 「ブラウザ」から確認してください

f:id:andoshin11:20180423211058j:plain

Access Tokenの取得

Access Tokenを取得するためにはサービスアカウントの情報が必要です。以下のコマンドでサービスアカウントの情報を格納したJSONが取得できます

$ gcloud iam service-accounts keys create --iam-account test-account@<PROJECT_NAME>.iam.gserviceaccount.com ./test-account.json

CI等からこのアカウントの認証情報を利用するためには、上記で取得したJSONbase64などでエンコードして環境変数に保存すると取り回しがしやすいでしょう。

$ base64 ./test-account.json

// 上記の結果を$CLIENT_SECRETという環境変数に代入した場合は下記のように実行環境でデコード
$ echo $CLIENT_SECRET | base64 --decode > ./client-secret.json

上記のJSONを元にAccess Tokenを取得する方法は以下のとおりです

$ export TEST_ACCOUNT_TOKEN=$(GOOGLE_APPLICATION_CREDENTIALS=./test-account.json gcloud auth application-default print-access-token)

ちなみに自分はFish shellを使っているのでコマンドはこんな感じ

$  export TEST_ACCOUNT_TOKEN=(env GOOGLE_APPLICATION_CREDENTIALS=./test-account.json gcloud auth application-default print-access-token)

Cloud Functionsを拡張する

以下のCloud Functionがあったとします

// index.js
...
exports.echo = function(req, res) {
    res.send('hoge')
}

この関数に認証フローを追加し、以下のように書き換えます

// index.js
...
const Google = require('googleapis');
const BUCKET = '[BUCKET_NAME]'; // 上で選択したBucket Nameに置き換えてください

// リクエストヘッダーからTokenをパース
const getAccessToken = function(header) {
    if (!header) return null;

    const match = header.match(/^Bearer\s+([^\s]+)$/);
    return match ? match[1] : null;
}

// メインで実行したい関数
const authorized = function(res) {
    res.send('hoge')
}

// メイン関数を認証フローでラップした関数
exports.echo = function(req, res) {
    const accessToken = getAccessToken(req.get('Authorization'));
    const oauth = new Google.auth.OAuth2();
    oauth.setCredentials({access_token: accessToken});

    const permission = 'storage.buckets.get'; // 権限の種類
    const gcs = Google.storage('v1');
    gcs.buckets.testIamPermissions(
        {bucket: BUCKET, permissions: [permission], auth: oauth}, {},
        function(err, response) {
            if (response && response['permissions'] && response['permissions'].includes(permission)) {
                authorized(res);
            } else {
                res.status(403).send("不正なアカウントです");
            }
        });
};

上記の関数をデプロイ

Access Tokenを付与してリクエス

まずは愚直に curlを実行してみます。

$ curl https://<your cloud function's URL>
> 不正なアカウントです

エラーで怒られました。

次にAccess Tokenをリクエストヘッダーに付与して実行します。

$ curl https://<your cloud function's URL> -H "Authorization: Bearer "$TEST_ACCOUNT_TOKEN
> 不正なアカウントです

また怒られました...

まぁTest Accountには権限が無いので当然です。下記のコマンドを実行して read権限を追加

$ gsutil acl ch -u test-account@<PROJECT_NAME>.iam.gserviceaccount.com:R gs://<BUCKET_NAME>

もう一度上記のコマンドを実行すると無事にリクエストが通るはず

$ curl https://<your cloud function's URL> -H "Authorization: Bearer "$TEST_ACCOUNT_TOKEN
> hoge

よっしゃ👏👏

まとめ

GCPのIAMは柔軟に権限が設定できる反面、Tokenの発行には若干癖がある印象です。Docker Imageが用意されているのでCIからは問題なく叩けそうですが、それ以外の環境ではやや苦労するかもしれません。

もちろん今回はプロキシとして無理やり認証システムを噛ませているだけなので、認証部分を別の形式に置き換えていただいても問題なし。お好みでどうぞ。

次回は実際にCloud Fuctionsで動くプログラムを紹介します。おつきあいありがとうございました。

*1:ほとんどゴミみたいなBot

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が動くならそもそも毎回静的ファイルをビルドしなくて良かった説ある...

文学部歴史学科出身の社会人がエンジニアになるまでやった事/やらなかった事

早いものでエンジニアとして働き始めてから今月でもう1年です。

1年前は時給1,000円ちょっとのバイトと派遣をやっていました。

先日ようやく正社員としてプロエンジニアになれたので、この機会に文学部出身のぼくが勉強し始めてからの3年半でやった事/やらなかった事を備忘録的に残していきたいと思います。

対象読者

  • 文系だけどIT業界ではたらきたい学生
  • これからプログラミングを勉強してIT業界への転職を考えている社会人
  • すでにプログラミングを勉強中の社会人(←エラい)
  • 未来の自分

ただの備忘録ではありますが、これからIT業界を目指す誰かの参考になれば嬉しいです。

サマリー

エンジニア以前

タイトルにもある通り大学では文学部歴史学科に所属しており、1行もコードを書いたことがありませんでした。

また最初の仕事も観光・宿泊業だったのでITに全く縁がありませんでした。

「会社のHPとかあればカッコいいなー」「ネットで集客できれば便利だなー」くらいのモチベーションで勉強し始めたのがきっかけです

沼への入口

エンジニアコミュニティの門を叩く

当時僕がいた京都にはCamphor-という学生ITコミュニティがあり(今もある)、そこのエンジニアを紹介してもらったのが沼への入口です。

(余談ですが、初めてエンジニアに会うのに予備知識が無いのは流石に失礼かと思いdotinstallでHTMLとCSSを勉強していきました)

このコミュニティに迎えてもらったおかげで素晴らしいメンターに巡り会えただけでなく、優秀なエンジニアを定点観測することで彼/彼女らが日頃どの程度のInput/Outputを行なっているのかを学ぶことができました。

「現在の地点」でInput/Outputに差が出るのは経験の差なので仕方ないですが、「半年後の地点」でInput/Outputの成長度合いに差が出るのは別の理由があるはずです。優秀なメンバーの「成長度合い」を学習のペースメーカーとして利用できた点で、始めにコミュニティに所属できたのは大きかったかなと思います。

0ヶ月目~: dotinstallでの自己学習

前述のエンジニアコミュニティに顔を出す前の予習としてスタート。やれるとこまでやって質問を1つ持って行こうと決めていたので、HTMLとCSSを修了し、「ローカルサーバーを立てる」章でつまづくまで進めました。

dotinstallには大抵の技術のいろはが揃ってるので、今でも新しい技術を身につけたあとに、タイトルだけ眺めて要点を抑えられてるか確認するため利用しています

この時学んだ事:

  • HTML/CSS
  • 黒い画面の触り方

2ヶ月目~: Wordpressでサイト制作

「Webサイトを作るにはWordpressを勉強すれば大丈夫」と聞いたので本を買ってブログサイトを作りました。

カスタムテンプレートを自作することで動的にサイトを組むという概念に触れられ、コピペでjQueryも使ってみたりしました。書籍だけでは当然情報不足なのですが、幸いにもWordpressの情報は日本語でも死ぬほどネットに落ちているためそちらも参考にしました。

この延長で自社の会社情報サイトも制作することになり、入り口としては良かったのかなと思います。サイトの公開作業はエンジニアに丸投げしました。

この時学んだ事:

10ヶ月目~: Ruby on Rails (Viewだけ)

社内でRailsを使ったWebサービスを作ることになりましたが、人材が不足していたのでMVCのView部分だけ担当しました。自分でもRails Tutorialにチャレンジしたのですが、巨大な壁にはじき返されたのを覚えています。

また、表示に必要なデータを自分でも用意するためController部分も少しだけ触らせてもらいました。

この時学んだ事:

  • hamlの使い方
  • Rubyの基礎
  • Gitの使い方

1年2ヶ月目~: Slack Bot

いわゆる"プログラム"的なものが作りたくてSlack Botを作りました。名目としては「業務効率化」のためでしたが実際はネタBotばかり作っていました。その時の記事がこちら。

tech.camph.net

この頃からJavaScriptで簡単なロジックを書いたりするのがだんだん楽しくなって来ました

この時学んだ事:

1年3ヶ月目~: クローラーの作成(コピペ)

業務でホテル予約サイトやAirbnbのデータを分析する機会があったのですが、MITの学生がクローラースクリプトを公開していたので(下記)そちらを参考にして(コピペして)自社向けのスクリプトを開発しました。

hamelsmu.github.io

実行は手動で行わなければならずシンプルなCSVを出力するだけのものでしたが、自動で大量のデータを集めることができるのが面白く、いろんなサイトのクローラーを20種類くらい作成しました。DOMを解析したり、HTTP通信の仕組みを勉強できて楽しかったです。

この時学んだ事:

  • Pythonの基礎の基礎
  • HTTP通信の仕組み
  • DOMの仕様

1年5ヶ月目~: Ruby on Rails

3回くらい挫折したTutorialをようやく終えて自分でもサービスを作りたくなってきました。初めてスクラッチからアプリケーションを作り、メンターにボコボコにレビューしてもらいながら一応のリリースまで持って行けて良かったです(すぐクローズしたけど)。

このときはRubyクローラーも書きましたが、デプロイとインフラは相変わらずリードに丸投げ。

この時学んだ事:

2年2ヶ月目~: Vue.js (SFCだけ)

リードがRailsアプリにVue.jsおよびモダンJSの開発基盤を整えてくれたので、コンポーネントファイルのメンテだけ担当しました。世間的にはちょうどv2.0が出て来てVueが注目され始めたころです。RailsAPIで通信してUIゴリゴリ動かすのめっちゃ楽しいやんけ、ってなりました。

この時学んだ事:

2年6ヶ月目~: Docker (エンジニアデビュー)

メンタル病んだりいろいろあって会社を離れたものの、手につく職も特になかったのでエンジニアデビュー。 事務作業の片手間でやってたプログラミングがどれだけ世間で通用するか分からなかったので、修行のつもりでWeb制作会社でWordpressのバイトをさせてもらいました。

Wordpress周りのタスクならそれなりにできるようになっていたので、新たにインフラに手を出したくなりDockerの勉強開始。

なんとなくでしか理解していなかったLinuxをもう一度勉強してDockerfile書いたり、docker-composeで複数のミドルウェアを組み合わせたりできるようになってエンジニアとしてレベルアップした気がします

この時学んだ事:

  • MySQLの基礎,
  • 開発環境構築とデプロイの自動化
  • VPSへのサービスデプロイ

2年7ヶ月目~: モダンフロントエンド

環境の整ったRailsアプリでVueのコンポーネントを書いたことはありましたが、初めてゼロから環境構築するのはこの時が初めて。Webpack、Babel、ES6、Promiseやnpmの使い方などをザックリと理解して最低限のモダンフロントエンド開発環境をスクラッチで組めるようになりました。

この頃は狂ったようにRails + Vueのアプリ作って遊んでいました。だんだんRailsが面倒になってFirebaseのようなBaaSの使い方も覚えました。

この時学んだ事:

2年7ヶ月目~: Perlでソシャゲ開発

バイトだけでは食べていけないので派遣に登録してソシャゲの下請けでPerlを書くように。

はじめての言語かつオレオレのフレームワークでしたが、MVCを理解してたのでなんとか仕事ができました。それなりの規模のサービスではありながら、実質3~4人で保守と新規開発を全部見てたので死ねました。

それまでORMしか触ったことがなかったので生SQLを書くときに死ぬほど怒られたし、DBやメモリのパフォーマンスなど気にしたこともないままコードを書いていたので本番障害も引き起こしてしまうなど、大規模サービスならではの経験ができて良かったです。(その節はご迷惑をおかけしました)

これがきっかけで「動けば良い」という思考から少し成長し、パフォーマンスやスケーラビリティを考慮した設計をするようになりました。

何十万行もあるクソコ温かみのあるコードベースから気合のgrepでバグを見つけ出すスキルも身につきました。

この時学んだこと:

  • パフォーマンスへの意識
  • 保守の大変さ
  • バッチの運用方法
  • CIの使い方
  • チーム開発のいろは
  • RDBMSの仕様

3年目~: React Nativeでモバイルアプリ開発

バイトと派遣を継続する傍ら、受託案件もいただけるようになりました。フロントエンドが足りていないとのことだったので「よっしゃVueやるぞー!」と意気込んだものの、アプリ開発もやってほしいとのことで泣く泣くReactおよびReact Nativeで開発することに。

社内はおろか周囲にもReduxやReact Nativeの知見を持った人がいなかったので、海外のチャットコミュニティや掲示板で情報収集しながら開発を進めるのはなかなか楽しい経験でした。レールのない世界でスケーラブルなアーキテクチャを模索するのは面白かったです。

まだ開発中でリリースまではいけてないのですが、Xcodeを触る中でモバイルアプリのパラダイムも学ぶことができてとても勉強になりました。

この時学んだこと:

3年2ヶ月~: データ分析と機械学習

バイト先まで片道2時間ほどかかっており、ゲームや読書で時間を潰すのも飽きたのでデータサイエンスを勉強し始めました。

そもそも数1Aの記憶すら怪しい文系マンなので、まずは数3Cまで勉強し直すところから。その後UdacityのData AnalystコースとCourseraのMachine Learningコースを受講しました。ライブラリを使えば簡単な画像判定ツールくらいは作れますが、スクラッチで実装しろと言われると厳しいレベルです。

3ヶ月ほど勉強してみましたが他のことに興味が移ったので学習はストップしています

この時学んだこと:

3年6ヶ月目: 現在とこれから

2018年3月1日よりmerpay, Inc.でフロントエンドチームの立ち上げに参画しています。

mercari本社はReactの会社なのですが、merpayではVue.jsを採用して会社全体で新たなチャレンジを行なっている最中です。 BFFサーバーとしてNuxt.jsも採用しているため、「フロントエンドチーム」と名乗りつつバックエンドに踏み込んだ仕事もできる魅力的な環境だと思います。

疑いの余地なく日本で最高のメンバーと最高にBoldな未来を創っていける職場なので、この場所でもっとディープにWeb技術全般を学習・検証していくつもりです。

エンジニアリングを始めてからの数年間、様々な経験を公私ともにさせてもらって本当に恵まれていました。 これからは積極的にイベント等に登壇してコミュニティに還元していきたいと思います。

やらなかった事

これまでで自分がやってきた事を書き終わったので、最後に自分が「やらなかった事」を簡単に紹介します。

ハッカソンに出る

ハッカソンは一度だけ出てもう良いかなって思いました。良くも悪くもいろんなレベルの人がいるので必ずしもプラスにならないのと、ハッカソン常連の仲良しコミュニティ感が肌に合わなかったです。技術力だけじゃなく参加回数や業界年数を加味したコミュニケーションを強いられてバチバチ勝負出来なかったので、やや消化不良気味でした。

魅力的な景品や、同レベル以上のライバルがいる事が保証されてるならアリだと思います。

勉強会を主催する

コミュニティに参加するのは大好きですが、勉強会の企画・運営はあえて避けていました。キャリアの浅いうちから外の人を巻き込んでしまって責任ばかり肥大すると、自分自身のフットワークを殺してしまうかなと思ったからです。

勉強会やイベントの運営ノウハウを学ぶのも大事なのでこの辺りはトレードオフですね。

本を読む

始めこそWordpressの教科書みたいな本を買いましたがそれ以来ほとんど買っていません。他にもRubyクローラー本やインフラ関係の本を買いましたが、ほぼ流し読み。

情報が体系的にまとまってるのはありがたいですが、勉強したてのころは必要に応じて様々な情報に網羅的に目を通すのが僕には合っていました。普段からブラウザのタブを常に40個くらい開いて横断的に参照しているタイプなので、同程度の本を複数回し読みする運用はキビしかったです。

おかげで(?)海外のフォーラムを覗くのに抵抗がなくなりましたし、複数の著者(情報の提供者)の意見を統合して最終的な情報の良し悪しを判断するクセみたいなものがつきました。

逆にリーダブルコードのようなバイブル的なやつは読んどくべきだったかも、と最近反省しています。

最後に

エンジニアになる以前も営業、経営企画、経理、法務、労務、マーケ、CS等々いろいろやっていましたが、結局今の仕事が一番肌にあっている気がします。

IT業界の面白いところは、会社の枠を超えて業界全体で知識の共有とアップデートが文字通り秒単位で行われている点です。自分がどれだけ勉強しても天井が見えず、「もっともっと勉強してやろう」というモチベーションを他者から得られます。これは他の業界ではなかなか見られない現象です。

ようやくテックカンパニーに就職することはできましたが、チームではブッチギリで経験の浅いペーペーなので、遠慮なく先輩方のスキルを盗ませていただこうと思っています。

繰り返しにはなりますがぼくはコミュニティのお陰で様々な恵まれた経験をしてこられたと思っているので、今後はそこに恩返しもしつつ更に成長速度を加速させていきたいです。

以上!!年度末のエモい記事終了!!!!

おまけ

merpayの求人サイトが新しくなったので是非見てみてください。めちゃんこオシャレです。

www.merpay.com

弊社に興味ある方がいれば気軽に声かけてください。肉やりましょう。

Falcon Heavyの打ち上げは、紛れもなく僕のアポロだった

1969年7月20日 アポロ11号月面着陸

人類が初めて地球以外の天体に降り立ったこの日、全世界が熱狂した。

この「一人の人間にとっては小さな一歩」が灯した火は半世紀を経た今もなお、多くの人の心の中で輝き続けている。

ある者はその「一歩」を追って月への想いを焦がし、またある者は小惑星探査へと野望を抱き、そしてある者は火星へと夢を見た。

 


 

2018年2月7日 午前5時44分

「T-30」のアナウンスが流れた瞬間、僕の身体に電撃が走り全身の毛が逆立つのを感じた。

前の晩から興奮で眠れずとうに疲労の限界を迎えていたけれど、歴史的瞬間を前にして僕の脳からは猛烈な量のアドレナリンが分泌されていた。

25歳の冬、紛れもなく僕は「熱狂」していた。

 


 

はじめて宇宙に想いを馳せたのはおそらく9歳か10歳のころ。

小学生の僕が父とお風呂に入るときは必ず父が広い世界の話をしてくれた。ときには歴史の話だったり、物理の話だったり、地理の話だったり、社会のしくみの話だったり、そして宇宙の話だったり。

当時札幌に住んでいた僕は年に数回は青少年科学館に足を運び、プラネタリウムや天体模型に夢中になっていた。いつかの冬休みには4日間の宇宙講座に参加して、ロケットのしくみを勉強したり宇宙食を食べたりした記憶が今でも残っている。

 

それからしばらくの間、宇宙への想いを忘れていた僕がふたたび「それ」と出会ったのは大学1回生の夏休みのこと。

 

いわゆる「ブラックバイト」で消耗していた僕は、なけなしのバイト代をはたいて種子島に旅をすることに決めた。そのころから新海誠作品のファンだったこともあり、なんとなく「秒速5センチメートル」に出てきた風景に惹かれて南の島に逃げたくなったのだ。

f:id:andoshin11:20180209032102j:plain

f:id:andoshin11:20180209032134j:plain

せっかくの島旅なので北の端から南の端まで巡る計画を立て、宇宙センターの近くにも宿を取った。この時泊まった門倉荘という宿はどうやらJAXAの関係者がよく利用する宿だったようで、食堂の壁にも宇宙飛行士のサインがズラッと並ぶなどしていた。

結局、宇宙一色の町の雰囲気に押されるがまま宇宙センターの見学を申し込む形にはなったが、この時暇そうにしている僕に宇宙センター見学を勧めてくれた宿のおばちゃんには心から感謝している。

f:id:andoshin11:20180209032443j:plain

f:id:andoshin11:20180209032506j:plain

f:id:andoshin11:20180209032530j:plain

 

生まれて初めてロケットを見た

 

生まれて初めてロケットのエンジンを見た

 

生まれて初めてロケットの格納庫を見た

 

生まれて初めてロケットの発射台を見た

 

心の底から何かをカッコイイと思うのは、多分初めての経験で、胸の奥から熱いものが込み上げてくるのを感じた。人の手で組み上げられたものが大空の彼方まで飛んでいくのだという「現象」の衝撃が、確かな実感として心に刻み込まれた。

 

その日、宿に戻って食事をしてから種子島の夜道を2時間ほど散歩した。

 思えばこの時が人生で初めてしっかりと自分の将来について考えた瞬間なんだと思う。それまでもぼんやりと「ここの高校に行きたい」「ここの大学に行きたい」「こんな仕事をしたい」などと考えたことはあったけれど、「何を成し遂げて死ぬのか」を明確にしたことはなかった。

 

宇宙センターでのことを思い出し、夜道を照らす星々の美しさに感謝し感激し、それまでの人生について振り返り、それまで訪れた様々な場所や出会った人々のことを思い出し、そしてなにより若くして命を落とした友人たちの生き様を振り返った時に、19歳の僕は「宇宙を見てから死のう」と決意した。

 


 

25歳の僕は、まだ夢を叶えていない。

 

 

この6年間でいろいろな回り道をしてきた。

短い間だが大学では歴史のロマンに魅せられて西洋史を専攻した(結局中退した)

経営者として経済的成功を勝ち取ろうとして会社を興し、テレビや新聞に取り上げてもらって浮かれていたりもした。当時は本気で億単位の金を40歳までに稼いで宇宙旅行に行こうと考えていたのだ。(結局メンタルが持たずに多くの人に迷惑をかけた)

そして現在は社会人になってから始めたプログラミングの面白さに魅せられ、技術力を高めつつ社会復帰することに身骨を砕いている。

いろいろやってきたが、多分これが一番性に合っていそうだし夢への近道なのだと最近は思っている。

 

結局のところ、僕は大富豪になって「宇宙観光」がしたいわけではなく、人類のフロンティアである宇宙開発に貢献し、歴史上誰もまだみたことのない世界を切り拓くことにワクワクするのだと。

 

 

去年の夏に、NASAJPL無人探査機の研究開発を行なっている小野雅裕さんの講演を拝聴する機会を得られた。

彼の立っている場所は、まさに僕の夢の番地だった。

技術者としてエンケラドスの氷の底を観測しようという小野さんが積み重ねてきた努力や熱意は僕が描く将来の自身のイメージそのもので、これから進む道が間違っていないのだという希望と勇気をもらえるとても素晴らしい瞬間だった。

 

人類がエウロパやタイタンの海を開拓していく瞬間に携わりたいと今では強く思うし、プロキシマ・ケンタウリbのような系外惑星まで衛星や探査機を届ける技術的な貢献がしたいと、空を見上げるたびに想っている。

 


2月7日の早朝、200万人が見つめる中でFalcon Heavy打ち上げのカウントダウンが始まった。

 それまでもSpaceXのFalcon 9の打ち上げ中継を欠かさず見ていた僕だったが、この時の興奮はいつもの比ではなかった。

2004年にイーロン・マスクがFalcon Heavyの構想をぶち上げてから、多くの資金難、技術的困難、社会的バッシングがあった事を僕たちは知っている。

それら全てを笑い飛ばすかのようにFalcon Heavyは大空へと美しいを弧を描いた。

 

f:id:andoshin11:20180209143310p:plain

 

魔法のように地上へ戻ってくるロケットの姿を見て、全ての人が「これからはロケットも飛行機のように何度も再利用される時代がくるのだ」と考えるより先に肌で感じた。

火星に向けて射出されたロードスターとスターマンを見て、全ての人が「これは他の惑星に荷物や人を届けるためのロケットなのだ」と考えるより先に直感した。

 

この日、もう一つ個人的に思い出深かった事としては、打ち上げの瞬間を母親と一緒に見られたことだ。

ロケットやSpaceXのことはおろか宇宙のことなどほとんど何も知らない母が、この時ばかりは見たこともないくらい興奮し、「熱狂」していた姿がとても印象的だった。

 

人が空を見上げる時、そこには年齢も性別も国家も人種も壁なんて存在しない。

なにせあの冷戦時代のアメリカとソ連でさえ宇宙では手を取り合ったのだから(1975年7月17日 アポロ・ソユーズテスト計画)

 

2018年に全人類にそんな「空を見上げる」機会をプレゼントしてくれたイーロン・マスクSpaceXのスタッフには賞賛の声を送っても送りつくせない。

打ち上げの成功を喜びあう彼らの笑顔と涙を、僕は一生忘れないだろう。

 

あのカウントダウンの瞬間からスターマンの中継が途切れる最後の瞬間まで、彼らが与えてくれた感動と希望の灯火を胸にこれからも夢に向かって歩んで行こうと思う。

 

最短距離ではないだろうしこれからも多くの人に迷惑をかけるだろうけど、もしも僕の「回り道」の途中でその灯火を誰かに分け与えることができたのなら、人類に対して少しは貢献できたと言えるんじゃないかな。

 

2018年2月7日、200万人と共に「熱狂」したFalcon Heavyの打ち上げは、紛れもなく僕にとってのアポロだった。

 

2018年2月9日 好日

 

f:id:andoshin11:20180209132715p:plain

 

今度こそVimを攻略したい:neovimをインストールして最高のdev UXを手に入れるまで(その1)

先日の記事に様々な反響をいただいたことをきっかけに「アウトプットやっぱ大事だなー」と思ったので、アドベントカレンダーに関係ないタイミングでも記事を書いてみようと思います。

僕のエディタ遍歴

  • ~2014年夏:Pages

大学では歴史学を専攻していました。当時はレポートさえ書ければ良かったのでMacデフォルトのPagesを使用。プログラミングのプの字も知らなかった時代。

  • 2014年夏 ~ 2015年:Sublime Text 2

仕事の合間にdotinstallでHTMLとかCSS勉強し始めた時代。プラグインも何も入れずになんとなくかっこいいなーという理由でSublimeを使っていた。この頃はWordpressの本を読んで会社サイトとか作ってました

  • 2016年前半:Vim

社内で仕事をお願いしていたエンジニアが黒い画面でよく分からないことやってるの見てすごいなーという憧れでVimを使い始めた。なんとなく .dotfilesリポジトリとか作ってみたけど .vimrcの中身は全てネットで見たやつのコピペ。使ってる機能は1割くらいしかなかったと思います。

この頃からRails書き始めましたが、開発するには同時多発的にいろいろなファイルを触らなきゃいけないので当時のVim力では少し厳しかったです。 NERDTree入れてたけど全然活用できなかった思い出

  • 2016年後半 ~ 2017年春:Sublime Text 3

本格的にプロダクト作りたいなーとなって使い慣れたSublimeに戻ってきました。ショートカットやプラグインの使い方もいくつか覚えて体感でこれまでの3倍くらいに開発速度も向上。ブワーっとスクロールして任意のファイルの中身をザッピングできるGUIのありがたさを実感

  • 2017年春 ~ 現在:Atom

サイドバーのファイルアイコンが可愛いという理由だけでAtomに乗り換え

f:id:andoshin11:20171217010020p:plain

あとは痛エディタにしたくて背景画像もこんな感じにしてました

f:id:andoshin11:20171217010144p:plain

Atomプラグインやショートカットの手触り感はSublimeと大差ないのですが、いかせん重すぎて開く気にすらならない!!! 最近は案件でXcodeも動かすことが増えてきてだんだん辛くなってきました。。。

Vimに戻るかー

ということで主に以下の理由でVimに戻ることにしました。

  • いろいろなエディタを触る中で自分が開発に必要とする機能が具体的に見えてきた

  • Vimを使っていたころよりも髪の毛1本ほどは黒い画面の操作に慣れてきたので今度こそいけるやろ感

他のエディター候補としてはMSに行ったときに紹介していただいたVSCodeがとてもとても魅力的だったのですが、「Electronで動くアプリはSlackだけで十分!!」という強い意思を持ってVimを頑張っていきたいと思います。

要件

Vimは何度も挫折を繰り返しているので触り始める前に意識すべき点として以下の項目を挙げました

  1. 設定ファイルをキチッと管理すること
  2. カーソル移動のしやすさを最優先にすること
  3. シームレスにファイルツリーの確認とファイルの移動が行えること

1についてはいうまでもないですが、2と3は過去にVimを捨ててSublimeに走ったクリティカルな理由なので確実に攻略していきたいと思います

neovimのインストール

今回は以前から良いと評判を聞いていたneovimを利用します。(* Mac vimによくわからない破壊的変更を加えすぎて手がつけられない)

Homebrewから neovimをインストール

$ brew install neovim/neovim/neovim

設定ファイルにpathを通す

デフォルトでneovimは ~/.config/nvim/init.vimを設定ファイルとして参照しますが(vimでいう .vimrc)、dotfilesを一限管理したかったので以下のようにシンボリックリンクを貼りました

$ ln -sf ~/dev/dotfiles/nvim/init.vim ~/.config/nvim/init.vim 

これでdotfiles以下を変わらずGit管理できます

dein.vimプラグイン管理

昔は NeoBundleを用いてプラグインの管理を行なっていた記憶がありますが、今は dein.vimというのが良いらしいですね。

init.vimに以下のような設定を追記

let s:dein_path = expand('~/.vim/dein')

" dein Scripts-----------------------------------------------
if &compatible
  set nocompatible
endif

" Required:
set runtimepath+=~/.vim/dein/repos/github.com/Shougo/dein.vim

" Let dein mange itself
if dein#load_state(s:dein_path)
  " Required:
  call dein#begin(s:dein_path)

  " Add or remove your plugins here:
  call dein#add('Shougo/dein.vim')

  " Required:
  call dein#end()
  call dein#save_state()
endif

" Install uninstalled plugins on startup
if dein#check_install()
  call dein#install()
endif

" Required:
filetype plugin indent on
" End dein Scripts-------------------------------------------

syntax enable

call dein#add(<plugin name>)というように追加したいプラグインを記述するとneovim起動時にそのプラグインが自動でインストールされます。

また、まだ試していませんが TOMLという形式のファイルを別に作成してそれを dein.vimから読み込むこともできるらしいので、もう少しプラグインの数が増えたら試してみます

カーソルの移動速度を上げる

これは多分Mac独自のHack。「システム環境設定」 -> 「キーボード設定」を見るとキー入力に対するディレイとリピート速度が設定できるので、こちらから適度なスピードに変更。

f:id:andoshin11:20171218082114p:plain

tmux上でのモード切り替え速度を上げる

インサートモードからノーマルモードへの切り替えがもっさりしていたので調査。どうやらtmux側でescキーに対して入力待ちをしていたのが原因のよう。デフォルトで500msくらいの待ちが発生していたようなので ~/.tmux.confに設定を追記

set -g escape-time 0

tmuxをリフレッシュすると動作がサクサク快適になりました

NERDTreeを入れる

neovimの中から別のファイルを開く方法としては基本的に :e <file name>で十分な気もしますが、視覚的にディレクトリ構造を把握した上で任意のファイルを開きたいという欲求があったので NERDTreeをインストールしました。

インンストール方法自体は簡単で、上記スクリプト内に

call dein#add('scrooloose/nerdtree')

という一行を追記するだけ。 また、ファイルツリーを開く際にいちいち :NERDTreeToggleなどと打つわけにもいかないので以下のkeymapを init.vimに追加

map <C-t> :NERDTreeToggle<CR>

これで ctrl + tでファイルツリーを開けるようになりました。

また、 NERDTree上でのファイルの視認性が悪かったので、以下の設定を追記して拡張子ごとにHighlightするようにしました

" NERDTress File highlighting
function! NERDTreeHighlightFile(extension, fg, bg, guifg, guibg)
  exec 'autocmd filetype nerdtree highlight ' . a:extension .' ctermbg='. a:bg .' ctermfg='. a:fg .' guibg='. a:guibg .' guifg='. a:guifg
  exec 'autocmd filetype nerdtree syn match ' . a:extension .' #^\s\+.*'. a:extension .'$#'
endfunction
call NERDTreeHighlightFile('py',     'yellow',  'none', 'yellow',  '#151515')
call NERDTreeHighlightFile('md',     'blue',    'none', '#3366FF', '#151515')
call NERDTreeHighlightFile('yml',    'yellow',  'none', 'yellow',  '#151515')
call NERDTreeHighlightFile('config', 'yellow',  'none', 'yellow',  '#151515')
call NERDTreeHighlightFile('conf',   'yellow',  'none', 'yellow',  '#151515')
call NERDTreeHighlightFile('json',   'yellow',  'none', 'yellow',  '#151515')
call NERDTreeHighlightFile('html',   'yellow',  'none', 'yellow',  '#151515')
call NERDTreeHighlightFile('styl',   'cyan',    'none', 'cyan',    '#151515')
call NERDTreeHighlightFile('css',    'cyan',    'none', 'cyan',    '#151515')
call NERDTreeHighlightFile('rb',     'Red',     'none', 'red',     '#151515')
call NERDTreeHighlightFile('js',     'Red',     'none', '#ffa500', '#151515')
call NERDTreeHighlightFile('php',    'Magenta', 'none', '#ff00ff', '#151515')
call NERDTreeHighlightFile('vue',    'Green',   'none', '#4fc08d', '#4fc08d')

その他

Vueファイルのシンタックスハイライトを追加する

Vue.jsのSingle File Componentである .vueだけシンタックスハイライトがなくて寂しかったので以下のプラグインを追加しました

github.com

まれに動作が不安定な時もありましたが、以下の設定を init.vimに追記して解決

" autohighlight vue file
autocmd FileType vue syntax sync fromstart

emmetを入れる

本業がマークアッパーなのでemmetは必須。ということでdein.vimから emmet-vim入れました

call dein#add('mattn/emmet-vim')

また、デフォルトだとemmetのkeymapが <C-y>なのでAtomSublimeを使ってた人間からするとちょっと不便。

" Configure emmet-vim
" set trigger key
let g:user_emmet_leader_key='<C-e>'

こんな感じに書くとAtom風にkeymapを変更できます。 実際に呼び出す際は ctrl + e + ,というように末尾にカンマが必要なので注意

動いた 👏👏

個人的に便利だなーと思ったこと

今回新しくゼロから学んでみて、個人的に便利だなーと思ったneovim(vim)の機能を羅列します。始めてVimを触ったときに知っておきたかった...

  • oで次の行を空けてインサートモードに移行:

なんでいままで行末に移動 → インサートモードに移行 → 改行、なんて面倒なことをやっていたんだろうw

  • 0で前の行を空けてインサートモードに移行:

上に同じ。

  • rキーで1文字だけreplace

TypoScriptの使い手なので「1文字だけ修正したい」というケース非常に多いのでとてもありがたい。

  • Iで行の先頭に移動してインサートモードに移行:
  • Aで行末に移動してインサートモードに移行:
  • ctrl + oで直前に開いていたファイルに戻る
  • ctrl + i上記の逆

などなど挙げればキリがないので、続きはまた別の記事にまとめたいと思います。

Vim楽しくなってきたのでもっと使いこなすぞー