倒れるときは前のめり。

カレーが好きです。

JavaScript だけでなくスタイルシートと画像ファイルも webpack 管理下に置いて、 Asset Pipeline から完全にサヨナラした

よくある Rails アプリケーションから JavaScript のビルドを webpack に移譲した前回に続いて、今回はスタイルシートや画像ファイルといった静的アセットもすべて webpack の管理下に置くことにした。これによりフロントエンド管理を Rails が提供する Asset Pipeline のしくみから webpack に完全移行できた(と思っている)。

これまでのあらすじは以下のリンクから。

cheezenaan.hatenablog.jp

Tl;dr

github.com

例に漏れず Pull Request を作成しているので、物好きな人はコミットを追ってたらいいと思う。

構成

~/s/g/c/c/sample_app_rev4 » tree frontend -I node_modules
frontend
├── config
│   └── webpack.config.js
├── package.json
├── postcss.config.js
├── src
│   ├── images
│   │   └── rails.png
│   ├── javascripts
│   │   └── application
│   │       ├── Hello.js
│   │       └── index.js
│   └── stylesheets
│       ├── _common.scss
│       ├── _footer.scss
│       ├── _header.scss
│       ├── _mixin.scss
│       └── application.scss
├── yarn-error.log
└── yarn.lock

6 directories, 13 files

スタイルシートのバンドル

$ docker-compose exec node yarn add -D node-sass style-loader css-loader sass-loader postcss-loader autoprefixer extract-text-webpack-plugin

webpack で sass を扱えるようにした。スタイルシート関連の loader が乱立しており当初は混乱したけど、以下の URL が理解の助けに役立った。

qiita.com

css-loader は CSS 間の依存関係解決、 sass-loader や postcss-loader は CSS へのコンパイルを行っている。style-loader はバンドル時に CSS の内容を style タグとして出力するのだけど、extract-text-webpack-plugin を使うと style タグに出力する内容を別途 CSS ファイルとして生成できる。あと autoprefixer がコンパイル時にベンダープレフィックスを自動で追加してくれるので控えめに言って最高。

bootstrap-sass を webpack で管理する

$ docker-compose exec node yarn add bootstrap-sass

bootstrap-sass を webpack で管理する。

// frontend/src/stylesheets/index.scss
@charset "utf-8";

// ...
@import './_mixin.scss';

// ...
// frontend/src/stylesheets/_mixin.scss
@charset "utf-8";

@import '~bootstrap-sass/assets/stylesheets/bootstrap-sprockets';
@import '~bootstrap-sass/assets/stylesheets/bootstrap';

$gray-medium-light: #eaeaea;

bootstrap-sprockets からにじみ出てくる投げやり感。。

画像ファイルのバンドル

$ docker-compose exec node yarn add -D file-loader

file-loader 導入にあたりファイルの出力場所や 読み込み先の設定で躓いたのだが、以下のページが大変参考になった。

qiita.com

エントリーポイントの整理

Rails 側で使用するテンプレートファイルの構造とバンドルファイルの関係を揃えてみた。たとえば、app/views/static_pages/home で使用するアセット群は、public/assets/frontend/static_pages/home という名前のエントリーポイントでバンドルして、

# app/views/layouts/application.html.erb
<%= stylesheet_link_tag frontend_asset_path("layouts/application.css"), media: 'all' %>
# app/views/static_pages/home.html.erb
<%= javascript_include_tag frontend_asset_path("static_pages/home.js) %>

という具合で呼び出す。画像ファイルを Railsimage_tag で読み込むためだけに javascript_include_tag しているのが残念ポイントではある。

Asset Pipeline とサヨナラする

アセットパイプライン | Rails ガイド を参考に、 sass-rails と uglifer, coffee-rails を Gemfile から消し去る。ここまでの作業で app/assets 以下は完全に不要になるので、

$ rm -rf app/assets

で削除。これで Asset Pipeline のない世界線に到達できた。

参考: webpack.config.js

そこまでごちゃごちゃカオスになっていない…はず。

おまけ: Heroku デプロイ時に node でフロントエンドビルドを走らせる

qiita.com

以下 2(+1) つの準備が必要。

  • Heroku Buildpack for Node.js を導入する
  • ルートディレクトリにある package.json を編集する
  • bin/yarn を削除する

Heroku Buildpack for Node.js を導入する

package.json に記載されてる devDependencies のパッケージ群もインストールしてほしいので、NPM_CONFIG_PRODUCTION環境変数をいじる。

$ heroku buildpacks:add --index 1 heroku/nodejs
$ heroku config:set NPM_CONFIG_PRODUCTION=false

ルートディレクトリにある package.json を編集する

Heroku はルートディレクトリの package.json を認識して自動でビルドを走らせてくれるので、最低限必要な設定を追記する。

まずは engines に node と yarn のバージョンを記載する(記載しないとエラーで落ちる)。

// package.json
{
  "engines": {
    "yarn": "1.3.2",
    "node": "9.4.0"
  },
}

scriptspostinstall に定義した内容を Heroku が実行してくれる。--prefix オプションをつけることでビルド時の起点となるディレクトリを設定できる。意外と地味に便利。

// package.json
{
  "scripts": {
    "postinstall":
      "npm install --prefix frontend && npm run release --prefix frontend"
  },
}

bin/yarn を削除して assets:precompile へのフックを消す

いつからか Railsrake assets:precompile を叩く際に rake yarn:install もセットで走るようになった。 Heroku デプロイ時に node 側でビルド→ Railsassets:precompile と2回ビルドが走るようになってしまい完全に「余計なお世話」である。bin/yarn を消し去ることで assets:precompile へのフックもなくなった。

ref. https://github.com/rails/rails/blob/master/railties/lib/rails/tasks/yarn.rake#L3-L8

所感

「手段の目的化」が若干否めなかったが、多少なりとも webpack とは仲良くなれたと思っている。世間やネットの情報が言うほど設定ファイルがカオスになるわけではなかったし、あの膨大なプラグインや設定にも意味があることが理解できたのが収穫。なによりフロントエンドの依存管理を Rails(Asset Pipeline)から切り離せたので、もし仮にこの先 webpack に次ぐ新たなツールが現れたとしても Rails 側への影響を抑えて乗り換えられるようになったし、もっというとサーバサイドの開発言語を Rails 以外にスイッチしたり API / クライアントの分離…という選択肢も見えてきた。

webpacker は…機会があったらまた別にさわろう。

Rails on Docker な開発環境からモダン JavaScript のビルド環境をコンテナごと独立させてみた

Rails チュートリアルのサンプルアプリを題材に自分だけの Rails on Docker な開発環境を作ることが、最近のマイブームになりつつある。

github.com

もともと Rails チュートリアル自体はこの1〜2年で3周前後していたのだけど、ただ繰り返すだけなのもまぁ飽きるので、自分なりに工夫を入れることが多くなった。書きはじめると長くなるけど、例えばこんなかんじ:

  • 実行環境を Docker Compose で作成して、 docker-compose up のコマンド一発で開発環境が整うようにした
  • 現行チュートリアルの最新第4版ではテスティングフレームワークに minitest を使用しているところを、自分なりに RSpec で書き直した
  • Travis CI を導入して、プルリクエストが走るごとに自動ビルドをさせるようにした
    • (完全に余談だけどあのバッジが GitHubリポジトリに出ているとちょっと嬉しい)
  • フロントエンドのビルドに専用の node コンテナを構築して、 webpack(≠ webpacker) で生成したファイルを Rails 側で読み込めるようにした ←イマココ

今回は、最後の項目について少しだけまとめることにしてみる。

Tl:dr

github.com

↑のURLからコミットを追っていくのがいいと思う。ハイライトは以下の通り。

  • Rails 5.1 系から標準搭載された webpacker は未採用
  • webpack(with babel-loader) を使用して ES2015 をトランスパイル & ビルド
  • webpack-manifest-plugin で生成した manifest ファイルを javascript_include_tag で読み込めるよう専用のヘルパーを用意

構成

frontend ディレクトリをルートに掘ってまとめて管理している。

$ tree frontend -I node_modules
frontend
├── config
│   └── webpack.config.js
├── package.json
├── src
│   ├── images
│   ├── javascripts
│   │   └── application
│   │       ├── Hello.js
│   │       └── index.js
│   └── stylesheets
└── yarn.lock

6 directories, 5 files

webpack による JavaScript のビルド

ビルドした生成物は ./public/assets 以下に配置する。なお開発環境では webpack-dev-server を使用するので具体的な生成物がないことに注意。

// config/webpack.config.js

const path = require('path');
const ManifestPlugin = require('webpack-manifest-plugin');
const UglifyJSPlugin = require('uglify-js-plugin');

const isProduction = process.env.NODE_ENV === 'production';
const fileName = isProduction ? '[name]_[hash]' : '[name]';
const pathForAssets = path.resolve(__dirname, '../../public/assets');

const Manifest = new ManifestPlugin({ fileName: 'webpack-manifest.json' });
const UglifyJS = new UglifyJSPlugin({
  parallel: 4,
  sourceMap: !isProduction,
  warnings: false,
});

const plugins = [Manifest];
const pluginsForProudction = plugins.concat(UglifyJS);

module.exports = {
  entry: {
    'frontend/application': ['./src/javascripts/application/index.js'],
  },
  output: {
    filename: `${fileName}.js`,
    path: pathForAssets,
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules|bower_components/,
        loader: 'babel-loader',
      },
    ],
  },
  plugins: isProduction ? pluginsForProudction : plugins,
  resolve: {
    extensions: ['.js', '.jsx'],
  },
  devServer: isProduction ? {} : { contentBase: pathForAssets },
  devtool: isProduction ? 'eval' : 'cheap-module-source-map',
};

ちなみに package.jsonscripts はこんな具合。

{
  // ...
  "scripts": {
    "build": "webpack --config ./config/webpack.config.js",
    "clean": "rimraf '../public/assets/frontend/**/*.{js,css}'",
    "format": "prettier --write 'src/**/*.{js,css}'",
    "lint": "eslint src",
    "publish": "yarn clean && NODE_ENV=production yarn build",
    "watch":
      "webpack-dev-server --config ./config/webpack.config.js --host 0.0.0.0 --port 4000 --colors --inline --progress"
  },
}

ビルドした JavaScriptRails で読み込む

webpack-manifest-plugin を導入することで、 webpack でビルドした生成物のマッピングが記された manifest ファイルを作成してくれる。

github.com

このファイルを用いて RailsJavaScript を読み込ませる。やることは2つ。

  • manifest ファイルを Rails に読み込ませる
  • 読み込ませた manifest ファイルを使って JavaScript のパス解決を行う

manifest ファイルを Rails に読み込ませる

config/initializers/assets.rb なるファイルがデフォルトで存在しているので、こちらに追記する。

# config/initializers/assets.rb

manifest = Rails.root.join("public", "assets", "webpack-manifest.json")
Rails.application.config.assets_manifest = JSON.parse(File.read(manifest)) if File.exist?(manifest)

manifest ファイルをもとに JavaScript のパス解決を行う

Rails.application.config.assets_manifest を呼ぶことで manifest ファイルの情報を JSON 形式で読み込めるので、あとはいいかんじにヘルパーを書いてやればいい。

module ApplicationHelper

  # ...

  def frontend_asset_path(path)
    return "http://0.0.0.0:4000/#{path}" if Rails.env.development?

    routes = Rails.application.routes.url_helpers
    host = Rails.application.config.action_controller.asset_host || routes.root_path
    manifest = Rails.application.config.assets_manifest
    return unless manifest.fetch(path, false)

    Pathname.new(host).join("assets", manifest[path])
  end
end

先述の通り開発環境では webpack-dev-server を使いたいので、ちょっとかっこ悪いけど分岐を入れている。

あとは View のファイル内で

<%= javascript_include_tag 'application', frontend_asset_path("frontend/application.js") %>

のように呼べば終了。rake assets:precompile のない世界線へようこそ。

E2Eテストの対応

躓いたのが RSpec などのテスティングフレームワークによるE2Eテストの実施。デフォルトでは RAILS_ENV=TEST で実行されるため、 webpack でビルドした生成物が存在しない場合ファイル不在で落ちてしまう。いまはテスト実行前に手動で yarn build してからテストを回すようにしているけれど、なかなかに面倒なので早くこの手間をなくしたい。

How to Dockerize

ここでやっとタイトルを回収するわけなんだけど、今回は Rails アプリを動かす app コンテナから独立して webpack によるビルドを実行する node コンテナを用意した。たしかに Procfile を使えば1コンテナ内で複数のプロセスを管理できるみたいだけど、まずは「1コンテナ1プロセス」という基本(出所不明)に沿ってやることにした。

$ tree docker
docker
├── node
│   └── Dockerfile.dev
└── rails
    ├── Dockerfile.dev
    └── Dockerfile.test

Dockerfile はサービスごとにディレクトリを掘った。補足をしておくと Rails アプリの開発時は bundle install するたびに docker build したくないので、 .dev ではボリュームをマウントするだけにして docker-compose exec app bundle install -j4 と叩んでコンテナにつど反映させている。

# docker-compose.yml
version: '3'
volumes:
  app_data:
  db_data:
  node_modules:
services:
  # ...
  app: &app_base
    # ...
  
  node:
    container_name: node
    build:
      context: .
      dockerfile: ./docker/node/Dockerfile.dev
    command: yarn watch
    ports:
      - 4000:4000
    volumes:
      - ./frontend/:/app/frontend:cached
      - ./frontend/node_modules:/app/frontend/node_modules:cached
      - ./public/assets/:/app/public/assets:cached

  # ...

Rails アプリが動く app コンテナの public/assets にビルド結果の生成物を置きたいので ./public/assets/:/app/public/assets のようにボリュームをマウントしてやる。

次にやりたい

ここまでで JavaScript の管理は Webpack でできるようになったので、次は順当にスタイルシート(scss, css)でも webpack で管理できるようにしていきたい。まずはチュートリアル5章までで作成したスタイルシートfrontend/src/stylesheets に移すところから。

参考

2018年を迎えたので開発環境を見直した(iTerm2 + zsh + prezto + peco + anyframe + vim + VSCode)

MacBook Proクリーンインストールして homebrew で必要なアプリ群を導入したあとにやることをまとめた。アプリごとの細かい設定は mackup で Dropbox に退避していたのでほぼノータイムで復旧できて大変よろしい。

こちらの記事の続きとなる。

Ricty を捨てて Myrica に乗り換える

もともと Ritcy を使っていたんだけど、もろもろのビルドが面倒なので、この機に Myrica へ乗り換えることにした。

myrica.estable.jp

brew tap caskroom/fonts
brew cask install font-myrica font-myricam

VSCode で使うときは "editor.fontFamily": "Myrica M" で OK. あとは powerline 用にパッチを当てる。

ghq get Lokaltog/powerline-fontpatcher
cd -- ~/src/github.com/Lokaltog/powerline-fontpatcher
fontforge -lang=py -script ./scripts/powerline-fontpatcher ~/Library/Fonts/MyricaM.TTC

作業ディレクトリに MyricaM Monoscape for Powerline.otf なるファイルができているので、Finder からファイルをダブルクリックしてインストールすれば OK.

ターミナル環境

iTerm2 + zsh + prezto + peco + anyframe の環境を整えていく。

iTerm2

iTerm2 の設定ファイルは mackupDropbox に移しているので、Preference > General > PreferencesLoad preferences from a custom folder or URL: に設定ファイルのパスを指定すればよい。

なお vim-powerline では文字化けするので、iTerm2 の Profiles > Text > Non-ASCII Fonthttps://github.com/powerline/fonts から Space Mono for Powerline をインストールして指定する。

zsh + prezto + peco + anyframe

zsh の見た目をかっこよくしてくれる prezto を導入。さらに peco + anyframe でらくらくコマンドライン環境を整える。

まずは homebrew でインストールした zsh をデフォルトシェルに設定する。

sudo sh -c "echo '/usr/local/bin/zsh' >> /etc/shells"
chsh -s /usr/local/bin/zsh

つぎは prezto.

github.com

git clone --recursive https://github.com/sorin-ionescu/prezto.git "${ZDOTDIR:-$HOME}/.zprezto"
setopt EXTENDED_GLOB
for rcfile in "${ZDOTDIR:-$HOME}"/.zprezto/runcoms/^README.md(.N); do
  ln -s "$rcfile" "${ZDOTDIR:-$HOME}/.${rcfile:t}"
done

anyframe は作者の Qiita の記事を読みながら導入。

qiita.com

ghq get mollifier/anyframe

~/.zshrc へ以下追記してやる。

function exists { which $1 &> /dev/null }
if exists peco; then
  fpath=($HOME/src/github.com/mollifier/anyframe(N-/) $fpath)
  autoload -Uz anyframe-init
  anyframe-init

  # 最近使用したのコマンドを叩いたりディレクトリに移動したり
  bindkey '^]' anyframe-widget-cdr
  bindkey '^r' anyframe-widget-put-history

  # ghq list のディレクトリに移動する
  bindkey '^g' anyframe-widget-cd-ghq-repository

  # kill process
  bindkey '^x^k' anyframe-widget-kill

fi

vim(macvim)

vim のパッケージ管理には dein を使用する。プラグイン管理は .vimrc から切り離して toml ファイルにまとめているので、例に漏れずシンボリックリンクを貼ってやる。

github.com

brew install macvim --with-lua --with-override-system-vim

# dein のインストール
sh ~/Dropbox/Mackup/.vim/installer.sh ~/.vim/dein

ln -s ~/Dropbox/Mackup/.vim/.dein.toml ~/.vim/dein/.dein.toml
ln -s ~/Dropbox/Mackup/.vim/.dein_lazy.toml ~/.vim/dein/.dein_lazy.toml

あとは vim を起動して、call dein#install(), call dein#update(), call dein#clear_state() すれば動くはず。

vim に導入している設定はだいたい以下の URL を参考にしている。

qiita.com

tmux + powerline

http://powerline.readthedocs.io/en/latest/installation/osx.html をもとに、シュッと powerline をインストール。

pip3 install powerline-status

tmux のバージョンが 2.6 以降だとコピー機能がうまく動かなかったので、公式の issue を見ながら .tmux.conf を修正する。

ref. https://github.com/tmux/tmux/issues/754#issuecomment-297452143

# ~/.tmux.conf
bind-key -T copy-mode-vi y send-keys -X copy-pipe-and-cancel "reattach-to-user-namespace pbcopy"
bind-key -T copy-mode-vi Enter send-keys -X copy-pipe-and-cancel "reattach-to-user-namespace pbcopy"

VSCode

これまで AtomIDE(RubyMine)を使用していたが、2017 年から VSCode を本格導入して以来もうこれなしには開発できない。

code --list-extensions > ~/Dropbox/backup/vscode/vscode-extensions.txt

クリーンインストール前に拡張機能の一覧を出力しておいて、

cd ~/Dropbox/backup/vscode
cat ./vscode-extensions.txt | while read line
do
  code --install-extension $line
done

で一括インストール。あとは Setting sync で gist にアップロードした設定を読み込めば終了。

code.visualstudio.com marketplace.visualstudio.com

開発言語のバージョン管理

RailsJavaScript 開発は Docker 上で完結させたいのだけど、ちょっと irb やコンソール上から動かしたいこともあるので、最低限の実行環境を入れることにした。

Ruby(rbenv + ruby-build)

brew install rbenv ruby-build

# ~/.zshrc に追加
export PATH=$HOME/.rbenv/bin:$PATH
eval "$(rbenv init - zsh)"

# 変更を反映させておく
source ~/.zshrc

rbenv install 2.4.0
rbenv global 2.4.0

which ruby # => /Users/cheezenaan/.rbenv/shims/ruby
ruby -v # => ruby 2.4.0p0 (2016-12-24 revision 57164) [x86_64-darwin17]

JavaScript(nodebrew + yarn)

公式のとおり curl を用いて nodebrew をインストールする。

github.com

curl -L git.io/nodebrew | perl - setup
export NODEBREW_ROOT=/path/to/.nodebrew

homebrew で nodebrew をインストールした場合、以下のエラーが表示されてコケる。

nodebrew install-binary latest

Fetching: https://nodejs.org/dist/v9.3.0/node-v9.3.0-darwin-x64.tar.gz
Warning: Failed to create the file
Warning: /Users/cheezenaan/.nodebrew/src/v9.3.0/node-v9.3.0-darwin-x64.tar.gz:
Warning: No such file or directory
                                                                           0.0%
curl: (23) Failed writing body (0 != 1124)
download failed: https://nodejs.org/dist/v9.3.0/node-v9.3.0-darwin-x64.tar.gz
brew install yarn --without-node

落穂ひろい

  • logicool options のインストール
  • Google 日本語入力を IME のデフォルトにする

Karabiner-Elements

https://pqrs.org/osx/karabiner/complex_modifications/ から For Japanese の設定を Import しておく。

rcmdnk.com

残課題

2018年を迎えたので MacBook Pro のクリーンインストールをした(homebrew + Brewfile + mackup)

f:id:cheezenaan:20180102111010j:plain
毎年毎年、最初からはじめられる。それは素晴らしいことだと思いますよ ― 「響け!ユーフォニアム2 第13話『はるさきエピローグ』」

ここ数年は年末年始を使って、自宅PCのクリーンインストールと開発環境構築をしているのだけど、2018年も例に漏れずやってみた。

TL:DR;

homebrew + Brewfile + mackup を使って、らくらく環境構築。

クリーンインストール前にやること

SSH 鍵のバックアップ

[SSH] 秘密鍵を Dropbox にバックアップしつつ、シンボリックリンクを張って使えるようにする設定 | CodeNote を参考に、Dropbox 上に SSH 鍵を移してシンボリックリンクを貼った。

cp .ssh/* ~/Dropbox/ssh/
chmod 755 ~/Dropbox/ssh
chmod 600 ~/Dropbox/ssh/id_rsa
rm -rf .ssh/
ln -s ~/Dropbox/ssh/ .ssh

~/.ssh » la ~/.ssh
lrwxr-xr-x  1 cheezenaan  staff    29B 12 31 12:18 /Users/cheezenaan/.ssh -> /Users/cheezenaan/Dropbox/ssh

~/Dropbox » la ssh
total 24
-rw-------@ 1 cheezenaan  staff   3.2K 12 31 12:16 id_rsa
-rw-r--r--@ 1 cheezenaan  staff   766B 12 31 12:16 id_rsa.pub
-rw-r--r--@ 1 cheezenaan  staff   189B 12 31 12:25 known_hosts
homebrew

こうなっていれば OK.

ref. お前らの SSH Keys の作り方は間違っている - Qiita

Brewfile の作成

github.com

homebrew でインストールしたパッケージの一覧を Gemfile 風にまとめたファイルを生成する。 Brewfile は Git 管理するのがいいかもしれない。

brew bundle dump -f --file=~/Dropbox/backup/Brewfile

mackup で設定ファイルを Dropbox に待避

github.com

デフォルトだと ~/Dropbox/Mackupディレクトリを掘って、dotfiles をはじめとする設定ファイルをバックアップし、シンボリックリンクで読み込むようにしてくれる。

mackup backup

クリーンインストール後にやること

XCode と追加コンポーネントのインストール

High Sierra 以降からなのか homebrew インストール時には XCode が不要になっていた。とはいえ macvim を homebrew で導入する際に XCode が必要になるので、このタイミングでインストールする。

homebrew + Brewfile を用いたパッケージ管理

なにはなくとも homebrew をインストール。

/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
brew doctor
brew tap caskroom/cask

Brewfile や mackup を使いたい都合上、先に Dropbox だけインストールして認証を済ませておく。

brew cask install dropbox

homebrew 経由で peco をインストールする際に GitHub 上の peco/peco から git clone しているっぽく、 Dropbox に保管していた ssh 鍵の同期をあらかじめ行う。 id_rsaパーミッションが 600 以外になっていたらよしなに直す。

ln -s ~/Dropbox/ssh/ .ssh

あとは

brew bundle --file=~/Dropbox/backup/Brewfile

を実行。Amazon ビデオで SHIROBAKO でも視聴していれば、もろもろのインストールが終了する。

mackup で dotfiles など設定ファイルを同期する

mackup restore

コマンド一発で設定ファイルの同期が終わる。控えめに言って最高。

あとはターミナル環境(iTerm2 + zsh + tmux + vim)と開発言語(Ruby, JavaScript)等の整備があるけれど、長くなりそうなのでいったん区切る。

2017年を振り返る

年の瀬なので、2017 年にやってきたことを振り返ってみることにした。

PowerPoint 用のアドオン開発

PIXTA (お試し版) – Microsoft AppSource

  • PIXTAAPI を利用して画像検索したりサンプルデータをダウンロードできるように
  • 実質ひとりチームで、Rails アプリケーションを 1 つ シュッ と作る貴重な体験をした
  • 諸事情あって今はペンディングになってるけどいつか再開したい

音楽素材販売対応

nlab.itmedia.co.jp

  • 写真、イラスト、動画につづく第 4 の素材販売
  • 具体的には、素材詳細ページならびに検索結果ページの改修を行っていた

素材詳細ページ

  • 煩雑化していた Controller / View をミニマムでリファクタしつつ対応
    • いろいろとつらい処理が多かった Controller および関連ロジックの整理
    • View にビジネスロジック書きすぎていたので、適切に Decorator や Model へ移譲
  • mediaelement とはだいぶ仲良くなれた
    • 開発中にひとつバグを踏んだのでさっさと issue なり Pull Request 送らねば…

検索結果ページ

詳細は以下のブログを読んでくれ…と言いたいところだけど、簡単にまとめるとだいたい以下の通り。

texta.pixta.jp

  • 既存の検索結果ページに React + Redux + Flow が導入されていたが「今回のスコープなら Redux いらなくね」となり、 React + Flow で組み込み
  • ES2017 + React + Flow によるフロントエンド JavaScript 開発の前線を肌で体感するいい機会に
  • medialement と React コンポーネントのつなぎ込みがなかなかの苦労ポイント

モバイル対応

2018 年から本格適用されると噂の MFI(Mobile First Index) に向けて自社サービスのモバイル化対応をやっている。自分が携わっているのはこのあたり。

  • 素材詳細ページの構造化データを microdata から JSON-LD へ移し替える
  • 検索結果ページの絞込フィルタを knockout + erb から React へ移し替える

素材詳細ページへの JSON-LD 組み込み

  • そもそも既存の gem だと JSON-LD とパンくずの両方をいいぐあいに生成できない
  • 諸般の事情で 1 ページ 2 パンくず出しているため Breadcrumb On Rails では機能が足らず、 Gretel をハックすることに
  • Value Object や immutable / mutable とか、インタフェース駆動で開発するぞ!とか、gem のコードを意識して読みはじめるとか、もろもろ意識しはじめたのがこのあたり

検索結果ページの絞込フィルタ React 移行

  • storybook + enzyme を用いた UI コンポーネント開発をやりはじめる
    • UIの確認は storybook 上でチェックしつつ
    • 複雑なロジック実装で不安なところは enzyme で TDD のサイクルを回していく
    • コード修正のフィードバックがほぼリアルタイムでなされるので、開発スピードが爆上げ
  • knockout に取り残されていた jQuery プラグインも React に移植しきる
    • コンポーネントのライフサイクル、ref の使い方をようやく実感としてわかってきたかな…という段階
      • componentDidMount とか componentWillReceiveProps とか
      • mediaelement とか既存ライブラリをラップするコンポーネント、という発想とか
  • React コンポーネントの設計指針、これというものがまだ自分の中で確立してない
    • いわゆる Container / Presentational とかいう分け方はまぁ意識できているんだけど、HoC とかもろもろのパターンとかまだ実感わかない
    • 既存の OSSソースコードを読んで引き出しを蓄積していかないとかなぁ、というお気持ち

社内勉強会・読書会の開催

この数年で社内の人員が急増したこともあり、開発チームで保有している知識やノウハウをうまく全社に展開できたら、ハッピーになるんじゃないの…という思いがあっていろいろやっていた。具体的にはスクラム本の読書会や、Redash を用いた社内 SQL 勉強会の企画・運営など。このあたりの話もブログにまとめているので読んでもらえると。

texta.pixta.jp

社内ブログ執筆

ひょんなことから社内ブログを書く機会をもらい、気がつけば年内に 3 本公開していた。

texta.pixta.jp

  • 自分の経験や知見を文章にまとめて世に送り出す歓びや楽しみを知ってしまう
  • 「これブログにまとめたら面白そうじゃない?」という視点から日々の業務を取り組むようになった

2018年にむけて

来年で30の大台に乗ることもあるし、いろいろと思うこともあるのだけど、それを書くには長くなるので端折ってまとめてしまうと、

  • 健康でありつづける
  • (半年くらい無職になっても生きられる程度には)蓄えをつくる
  • 腕を磨きつづける
  • 人との縁を大事にする

の3+1つを意識しながら過ごしていきたい。

『プロを目指す人のためのRuby入門』出版記念イベント&リファクタリングコンテストに行ってきた

まぁタイトルのとおりなんですが、行ってよかったです(KONAMI

techplay.jp

今日のイベントはざっくり、

  • 書籍『プロを目指す人のためのRuby入門』(通称「チェリー本」)の紹介
  • jnchito 氏による講演: 「わかりやすい技術記事を書くための心構えとテクニック」
  • 公開リファクタリングコンテスト

の3本立て。当日のやりとりは twitterハッシュタグ #railsdm を追いかけるのがよいかと。

『プロを目指す人のためのRuby入門』

パラパラとまえがきと目次を読み、本文をつまみ食いしながら読んでみて、「1年前に出会いたかった!!!!!!111」というのが率直な感想。自分はサーバサイド言語を PHP ではじめて、Ruby をさわるようになったのは今の会社に移ってから。これまでどうやって Ruby を習得してきたんだっけと振り返ってみると、

  • ドットインストールをなめて雰囲気を掴んで、
  • Railsチュートリアルを2周くらいやりきって、
  • 業務で Ruby を触るようになってからは、Qiita の記事や技術ブログから雑多で断片的な知識を吸収しつつ、
  • 『パーフェクトRuby』を読んで体系的に知識を整理しようとするも、メタプログラミングの章で挫折する
  • あとは日々のコードレビューで指摘をもらいながら1つずつ少しずつ自分のものにしていった
    • 振り返るとこれが大きいかなあ

「『パーフェクトRuby』や『Effective Ruby』よりももう少し敷居が低くて、ふだんの Rails 開発に活かせるな本があればなあ」と頭を抱えていた自分にまさにベストマッチで、ありがたや…という言葉しかない。

gihyo.jp

「わかりやすい技術記事を書くための心構えとテクニック」

もともとは「ぼくのかんがえたさいきょうのRubyにゅうもんしょ」~初心者のスキルアップのために技術書は何ができるか~というテーマだったが、「わかりやすい技術記事を書くための心構えとテクニック」 に変更。

あとは今の時代における技術書のあり方論、みたいな話もちらほら。

リファクタリングコンテスト

github.com

前にも同様の試みをやっていたのは twitter のタイムラインで追って知っていたけど、実際に参加するのは今回がはじめて。

とりあえずお題のプロダクションコードとテストコードを初見で読んだ際のメモがこれ。

## テストコード

- minitest さわるの、Railsチュートリアル以来だなあ(小声
- にしてもテストケース読みづらい
- 「ドキュメントとしてのテスト」として書く視点を持つといいかもしれない
  - ex. メソッドを使う側の人間が `\n` とか意識するか?
- たとえば
  - 1 2 3
    4 5 6
    7 8 9
  - を Ruby で書けないか?と考える
    - たしか <~~ でいけたような

## プロダクションコード

- Ruby 本来が持っている仕様を調べるクセをもつといい
  - 誰かがやりたいと思っていることは、きっと他の誰かも考えている
  - それが例えば gem で実現されていることもあれば、 Ruby 標準のライブラリで実現していることもある

メモをもとに自分が出したプルリクエストはこれ。 github.com

ふだん業務で交わしてるやりとりを思い出しつつやってみたものの、どこまで前提条件に踏み込んでいいのかとかコメントのレベル感どうするか?とか、実際のコードリファクタリング以外で考えるポイントが多かった…1

所感

  • 生 jnchito さんは見た目通りに気さくな方だった
  • リファクタリングコンテストは社内でもやってみたい。モブプロとかとあわせてワイワイやるのがいいかも
  • 会場を提供いただいた Speee さん、ラウンジがおしゃれすぎて息を吸うのも心苦しかった

戦果


  1. たとえば「そもそもなんで転置行列なんだ?」とか、「このメソッドはプロジェクト内でどんな役割を果たすのか?」…とか、そのへんの話

ES2015 + Babel + Webpack + ESLint な開発環境をつくった

ナウなヤングに人気な React のチュートリアルを自分もやりたくなったんだが、まあまずは環境構築だよね…ということで、サクッとやってみた。 ついでに Docker 上で動くようにこまかい工夫を加えてみた。

つくったもの

How to Use

$ dbin/yarn install
$ docker-compose up

ディレクトリ構成

$ tree
.
├── .babelrc
├── .dockerignore
├── .eslintignore
├── .eslintrc.json
├── .git
├── .gitignore
├── Dockerfile.dev
├── dbin
│     ├── console
│     └── yarn
├── dist
│     └── index.html
├── docker-compose.yml
├── node_modules
├── package.json
├── src
│     ├── components
│     │     ├── hello.jsx
│     │     └── world.jsx
│     └── index.js
├── webpack.config.js
└── yarn.lock

つくりかた

Docker

公式の node イメージを使った。 alpine ベースだからサイズも小さい。yarn も導入済み。

Dockerfile

FROM node:7.8.0-alpine

WORKDIR /myapp

docker-compose.yml

version: '2'

services:
  main:
    build:
      context: .
      dockerfile: Dockerfile.dev
    ports:
      - 4000:4000
    volumes:
      - .:/myapp:cached
    command: "yarn start"

docker-compose run ~ と毎回入力するのがだるいので、こんなスクリプトを用意して dbin/console として呼び出すことにした。

#!/bin/bash
set -eux

readonly APP_CONTAINER="your_conitainer_name"
readonly COMMAND="/bin/sh"

docker-compose run --rm $APP_CONTAINER $COMMAND $@

Babel

トランスパイラ。

$ yarn add --dev babel-core babel-loader babel-preset-2015  babel-preset-react

Webpack

ビルドしてくれたりファイル変更を監視してオートリロードしてくれるすげーやつ。

$ yarn add --dev webpack webpack-dev-server

注意点

Docker 環境上で動作させる際には以下留意すること。

  • host: ‘0.0.0.0’
  • watchOptions でポーリング時間を定義
// webpack.config.js

{
  devServer: {
    contentBase: './dist',
    inline: true,
    host: '0.0.0.0',
    port: 4000,
    hot: true
  },
  watchOptions: {
    aggregateTimeout: 300,
    poll: 1000
  },
(略)
}

ESLint

めちゃくちゃカスタマイズできる文法チェックツール。 とりあえず airbnb のルールを当ててみる。

$ npm info eslint-config-airbnb@latest peerDependencies --json | command sed 's/[\{\},]//g ; s/: /@/g' | xargs yarn add --dev eslint-config-airbnb@lates
// eslint.json

{
    "extends": "airbnb",
    "env": {
        "browser": true,
        "es6": true,
        "node": true
    },
    "parserOptions": {
        "ecmaFeatures": {
            "jsx": true
        },
        "sourceType": "module"
    },
    "rules": {
    }
}

React

みんなだいすきビューライブラリ。

$ yarn add react react-dom

とりあえずそれっぽくコンポーネントつくってみる。

// src/components/hello.jsx

import React from 'react';

const Hello = () => <h1>Hello</h1>;
export default Hello;
// src/components/world.jsx

import React from 'react';

const World = () => <h1>World</h1>;
export default World;
// src/index.js

import React from 'react';
import ReactDOM from 'react-dom';

import Hello from './components/hello';
import World from './components/world';

ReactDOM.render(
  <div>
    <Hello />
    <World />
  </div>,
  document.getElementById('app'),
);
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>JavaScript Boilerplate</title>
</head>
<body>
  <div id="app"></div>
  <script src="/bundle.js"></script>
</body>
</html>

あとは、http://localhost:4000/ にアクセスすれば変更内容を確認できる。 コンポーネントを修正するとブラウザに内容がリアルタイムで反映される。すごい時代だ。

感想

とりあえずトランスパイラとビルドツールの準備が整い、ES2015 の世界へ飛び込めるようになったので、当初の目的は達成。細かい作法や文法は、ESLint で怒られながら気軽に学べるしなんとかなるでしょ。あと webpack-dev-server はやはり便利だ。

つぎはいよいよ React チュートリアルかな。

参考リンク