Calmery.me

みっかぼうずにならないようがんばる

良さげな開発環境を作る

最近やっていることをまとめる.忘れないようにメモ.適宜更新したい.

何をしているの

身内で使用するための Twitter のアカウントに紐づいた Web アプリケーションを開発している.

ここではサーバ側とクライアント側で完全にコードを分けている.サーバーサイドレンダリングとかはしてない.サーバ側は REST API っぽく Node.js,Express を使用して開発して,クライアント側は React を使用して開発している.認証周りは Firebase の Twitter 認証,認証後はサーバ側で API キーを発行してヘッダーに X-Api-Key を追加してやり取りを行う.データ自体は Postgres に,Node.js の Sequelize を使って良しなに管理している.

ちなみにサーバ側は Heroku を使用して,クライアント側は GitHub Pages を使用している.

色々

使っているツールとかをまとめる.アルバイト先で教えて頂いたものが多い.

見た目を整える

クライアント側の見た目は material-ui を使用している(どこかに使用できるアイコンまとめないかな?)

認証する

Firebase を使用している.クライアント側で Twitter にログインして発行されるトークンをサーバ側に送ってサーバ側は firebase-admin - npm を使用してそのトークンを認証する.認証できたら uid が取得できるのでそれをデータベースに保存するという感じ.サーバ側は認証後にクライアント側に API キーを返して,クライアント側はリクエストを行うときにこの API キーをヘッダーの X-Api-Key に追加するようにしている.

データベースは Heroku 上でも使える Postgres を使用している.Sequelize を TypeScript から使える sequelize-typescript - npm が良さげだった.

自動デプロイする

サーバ側もクライアント側も GitHubリポジトリに変更を加えられたときに自動でデプロイするようにしている.

クライアント側

CircleCI を使用してリポジトリに変更が加えられた際にテストとビルドを実行する.master に限ってはビルドの後にデプロイまで実行される.GitHub - facebook/create-react-app: Create React apps with no build configuration. を使用しているのでテストとビルドに関しては react-scripts をそのまま利用する.デプロイに関しては gh-pages というブランチにビルドで作成された build というフォルダの内容を GitHub 上の gh-pages というブランチに追加すれば良いので gh-pages - npm を使用して良しなにやっている.また gh-pages ブランチでは CircleCI を実行したくないのでコミットのコメントに [skip ci] を加えている.Skipping and Cancelling Builds - CircleCI を見ればわかる.

package.json の内容はこんな感じ.

{
  "scripts": {
    "test": "react-scripts test --env=jsdom",
    "build": "react-scripts build",
    "deploy": "gh-pages -d build -m \"Updates\nCircleCI [skip ci]\""
  }
}

長くなるので jobs 部分は省略するけど CircleCI で使用する config.yml はこんな感じ.

...
workflows:
  version: 2

  test-build-and-deploy:
    jobs:
      - test:
          filters:
            branches:
              ignore: gh-pages

      - build:
          requires:
            - test

      - deploy:
          requires:
            - build

          filters:
            branches:
              only: master

使用している外部サービスが CircleCI だけなので簡単で作りやすい.

サーバ側

Node.js を使用して TypeScript,Express で開発しており,テストには Jest を使用している.基本的にはクライアント側とほとんど同じだが,こちらは Heroku にデプロイする必要があるので CircleCI で行うのはテストとビルドまでで,デプロイは Heroku の Automatic deploys を使用して行なっている.Automatic deploys の Wait for CI to pass before deploy にチェックを入れておくと CircleCI の処理が終わるまで待ってくれるので良い.

f:id:calmery:20180704220837p:plain

package.json はこんな感じ.Heroku の Automatic deploys は GitHub 上のコードを使用してデプロイを行おうとするので package.json の scripts に heroku-postbuild を追加してそこでビルドを行うようにする.

{
  "scripts": {
    "test": "jest --forceExit --coverage --verbose",
    "build": "rimraf dist/ && tsc",
    "heroku-postbuild": "npm run build"
  }
}

サーバ側はステージングとプロダクション環境で分けており,どちらも Automatic deploys で同時にデプロイされる.これステージングがもはやステージングと言えるのかが怪しい.ただ使用する Postgres のデータベースは分けているので壊せる環境としてある感じ.

レビューしやすくする

クライアント側は GitHub 上でプルリクエストを出した際に Heroku の Review Apps で自動で動作する環境を作ってくれる.Heroku Review Apps now Generally Available | Heroku を見るとわかりそう.コードのレビューと動作のレビューを同時に行えるので良い.ちなみにここで作った環境からはサーバのステージングの環境を使用するようになっている.ステージング云々というよりは,このためにサーバの環境を分ける必要があった.

ちなみにプルリクエストを作成して時間経過,またはプルリクエストのクローズでレビューのための環境は自動で壊される.Personal の場合はクレジットカードの登録で Free プランでも 1000 時間稼働させることができるし,小規模であればこれで全然良さそう.

app ドメインを使う

app ドメインを使うには HTTPS の使用が必須となっている.今回は GitHub Pages が HTTPS に対応しているのでリポジトリの設定から Enforce HTTPS にチェックを入れるだけで良い.

Heroku に関しては Hobby 以上でないとカスタムドメインSSL が使用できないので今はそのまま Heroku のアドレスを使用している.

まとめ

めっちゃ便利になったし感動してる.

ピクシブの春インターンに参加してきた

ピクシブ株式会社で行われた pixiv SPRING BOOT CAMP 2018 に参加してきた.
recruit.pixiv.net

pixiv SPRING BOOT CAMP 2018 とは

f:id:calmery:20180305022759j:plain
圧倒的猛者になれる.

この pixiv SPRING BOOT CAMP 2018 - ピクシブ株式会社 採用サイト を見てもらうとわかる.今回は pixiv コースに参加して pixiv のレイアウトを弄ってた.

選考

申し込みにはエントリーシートを使う通常選考と GitHub 選考の 2 つがある.自分は GitHub 選考で申し込みをした.

この通常選考,GitHub 選考を通過すると次はコーディング面接がある.出される問題の難易度は高くないので,落ち着いて解けば問題ないと思う.自分は単純なミスを何度もしてしまったけど訂正しつつ説明できれば問題ないようだった.

内容

自分が参加したのは主にユーザー体験の向上を図ることを目的としたコースで,ざっくりと言うと pixiv のトップページとかフォロー新着ページのレイアウトをいい感じにするという内容だった.

実際の開発は個別に割り当てられた開発用のサーバを使って行う.強い人たちは Vim とか Emacs を使って開発していた.恐い.Vim のこと何もわからないって言ったら Practical Vim: Edit Text at the Speed of Thought をそっと渡されたりした.恐い.そしてごめんなさい.結局,読みませんでした.

1 日目

前日は新宿御苑前駅の近くに宿泊した.初日は新宿御苑前駅から四ツ谷駅経由でピクシブの最寄駅である千駄ヶ谷駅まで.

この日は初日ということもあり,自己紹介や他のインターン生,メンターの方々とのランチ,開発を行うための環境構築,余った時間でちょこっと開発して終わった.

この日は交流会があって,お寿司とかピザとか食べた.

2 日目

この日から本格的に.自分はイラスト詳細ページの下にある関連作品ページのレイアウトを弄って,いくつかレイアウトを作って比較とかしてた.

初めは関連作品ページをタイムライン風のレイアウトにすることが目標だった.だけど進めていくうちにタイムライン風のレイアウトはこのページには適切ではない云々ということで,最終的にタイムライン風のレイアウトが合うであろうフォロー新着ページに提案という形で似たようなレイアウトを実装することとなった.

3 日目

前日に引き続きフォロー新着ページのレイアウトを弄った.

現状のフォロー新着ページの問題点,改善案の洗い出しをしたり,メンターの sesta さんに相談したり,デザイナーの yksk さんにレビューをして頂いたりなどなど.

最終的に yksk さんのレイアウトをほぼそのまま使用することとなった.この辺りは,力をつけて自分からどんどんレイアウトとか提案できるようになりたい.

残った時間で頑張って実装した.

4 日目

前日に開発した箇所を Elm に置き換えるということをやっていた.今回は特定の機能の開発や改善というよりもレイアウトの提案をする形になったので sesta さんの許しを得て,見た目はそのままに,中はかなり自由にさせて頂いた.

5 日目

最終日.この日は前日までの成果をスライドにまとめていた.スライドも Elm で作った.

最後に記念のグッズを貰った.

ちなみに,使用したスライドはテンプレート化してある.
github.com

まとめ

実際に運用されているプロダクトのレイアウトを自ら考えて実装するという良い経験ができた.ユーザー体験を考えながら画面を作っていくこと,自分を基準に考えてしまったり,感じる違和感を上手く説明できなかったり,難しいけどめっちゃ楽しい.今は全然わからないけどもっと色々と知りたいしやってみたい.

このインターンシップは短い期間だけど確実に学びはあるし,何よりも楽しいのでおすすめ.自分もイラストが好きで pixiv を使っているし,好きで使っているサービスの開発に携わることができたこと,デプロイもできたこと,最高にエモい.本当に参加できてよかったと思う.ありがとうございました.

色々

入り口に大量の絵馬が飾られていた.以前はもっと沢山飾られていたみたい.

かみさんの成果発表が卒論発表だった.

かみさんとは席が近かったので一緒にご飯食べに行ったりしてた.
f:id:calmery:20180305024517j:plain

Elm の布教に成功した.

ElmとElectronでデスクトップアプリを作ってみた

これは Elm Advent Calendar 2017Electron Advent Calendar 2017 の 15 日目の記事です.

Qiita にも同じ内容の記事を投稿しています.
ElmとElectronでデスクトップアプリを作ってみた - Qiita

はじめに

ElmElectron を使って Twitterハッシュタグである #elm をストリーミングするアプリを作ってみました.完成したものは GitHub - calmery/elm-advent-calendar-2017 で見ることができます.
f:id:calmery:20171214230543p:plain

実装

コミット毎にまとめていきます.

Hello World

GitHub - calmery/elm-advent-calendar-2017 at 6490533e51c1fb4afea9e03aa9562e4762f52207
f:id:calmery:20171215004356p:plain

Elm が undefined になった

早速というか.Electron から Elm を参照すると undefined となってしまいました.

Elm.Main.fullscreen() // Uncaught ReferenceError: Elm is not defined

実際に生成されたコードを見ると module.exports が優先されてしまうようです.

if (typeof module === "object")
{
  module['exports'] = Elm;
  return;
}

var globalElm = this['Elm'];
if (typeof globalElm === "undefined")
{
  this['Elm'] = Elm;
  return;
}

なので module.exports から直接参照するようにしました.

const Elm = module.exports

// require を使って読み込むこともできる
const Elm = require( './app.js' )

この問題は webpack を使用することで気にならなくなりました.

Twitter から取得したツイートをプロセス間通信と Port で送る

GitHub - calmery/elm-advent-calendar-2017 at 45fff5fea0170ef369d8554a4dbcbe0f020abe81
Electron はメインプロセスとレンダラプロセスが別れています.なので,その間でやりとりを行うためにプロセス間通信を行う必要があります.ここは ipcMain | Electron を見るといいかなと思います.

// src/entry.js

// In main process.
stream.on( 'data', event => {
  const tweet = event.text
  window.webContents.send( 'newTweet', tweet )
} )
// src/public/entry.js

// In renderer process.
ipcRenderer.on( 'newTweet', ( _, tweet ) => {
  // do something ...
} )

ひとまずツイートの本文だけをレンダラプロセスに渡すようにしました.ここから,さらに受け取ったデータを Elm に Port を使って渡します.ここは JavaScript · An Introduction to Elm の Step 2: Talk to JavaScript とか ElmのPortでJSを使う。 - Qiita が参考になります.

ipcRenderer.on( 'newTweet', ( _, tweet ) => {
  app.ports.newTweet.send( tweet )
} )
port newTweet : (String -> msg) -> Sub msg

type Msg
    = NewTweet String

subscriptions : Model -> Sub Msg
subscriptions model =
    newTweet NewTweet

JSON をデコードする

GitHub - calmery/elm-advent-calendar-2017 at 39ab6b701fcfbbf29a46ec706286c2f4c76a5f1f
ツイートの本文だけでは物足りないのでユーザの情報なども一緒に渡すようにしました.

// src/entry.js
stream.on( 'data', event => {
  const tweet = {
    text: event.text,
    created_at: event.created_at,
    user: {
      profile_image_url: event.user.profile_image_url,
      name: event.user.name,
      screen_name: event.user.screen_name,
    }
  }

  window.webContents.send( 'newTweet', JSON.stringify( tweet ) )
} )

Elm で JSON をデコードします.JSON · An Introduction to Elm[Elm] Decoder a からいろいろ理解ってしまおう - Qiita が参考になります.

-- src/public/elm/Main.elm
type alias Tweet =
    { user : User
    , text : String
    , created_at : String
    }

decodeTweet : String -> Result String Tweet
decodeTweet response =
    decodeString tweetDecoder response

tweetDecoder : Decoder Tweet
tweetDecoder =
    map3 Tweet
        (field "user" userDecoder)
        (field "text" string)
        (field "created_at" string)

通知する

GitHub - calmery/elm-advent-calendar-2017 at 5ee82825bd10cd5c3b0705f21b0804ba33455ca3
Elm から Port を通して Notification - Web API インターフェイス | MDN を呼び出すことでツイートを受け取った際に通知を表示するようにしました.

-- src/public/elm/Main.elm
port notification : String -> Cmd msg

...

update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
  ...
    Ok tweet ->
      ( List.append [ tweet ] <| List.take 100 model, notification tweet.text )
// src/public/entry.js
app.ports.notification.subscribe( message => {
  new Notification( message )
} )

見た目を整える

GitHub - calmery/elm-advent-calendar-2017 at c72da54e9bfb6d0aff18446d4313bfd31af246c5
Elm にも elm-styled とか style-elements とかあるようですが,今回は普通に CSS を使いました.

まとめ

躓いたところもいくつかあったけれど,思っていたよりすんなりできたかなと思います.Elm はまだわからないことが多いので色々と作りながら試していけたらと思います.