Ruby on Railsにwebpackを使ってPostCSSを導入してみた【ローカル環境編】

こんにちは、エンジニアの井戸田です。
今回はモダンなCSS環境を構築するために流行っていると言われているPostCSS×webpackをRailsに導入してみました。
今回は開発環境だけですが、次回本番運用に関して書いていきたいと思います。

環境

  • Ruby 2.3.3
  • Rails 5.0.2
  • Node.js 6.9.1
  • npm 3.10.8

PostCSSとは

PostCSSはJSプラグインでスタイルを変換するためのツールです。特徴として下記のことが挙げられます。

  • PostCSS自体が変換するのではなくCSSパーサーとASTを操作するためのAPIを提供しているだけで、実際にCSSに変換するのはPostCSSのプラグインが行います。
  • Sassでよく使う mixin extend 変数($) などの機能がそれぞれ1つ1つのプラグインになっており、それらを入れることで使用が可能になります。
  • メタ言語の中でも特にPostCSSは速度が速いです。
    ref) https://github.com/postcss/benchmark

Railsプロジェクトを作る

$ rails new sample-postcss -BJ
$ bundle install --path vendor/bundle

とりあえず $ bundle exec rails s -b 0.0.0.0 -p 3000

f:id:cluex-developers:20170329110722p:plain

webpack環境のためのディレクトリを作成

プロジェクトルートに client というディレクトリを作成。ここがwebpack環境のためのディレクトリになっています。 clientディレクトリの構成については下記のようになります。

client
  |-- webpack.config.js
  |-- node_modules
  |-- package.json
  └── src
        |-- images
        |-- javascripts
        └── stylesheets

npmで必要なライブラリをインストール

client ディレクトリに移動し、下記のコマンドを入力して package.json を生成します。

$ npm init -y

webpackをインストール。webpackでは1系を使用したかった為、バージョンの指定をしています。

$ npm install -D webpack@1.14.0

ローカル環境では下記ことを可能にする webpack-dev-server が、便利なので使っていきます。 webpack-dev-server はポート8080を使うNode.jsのexpressサーバーです。

  • ファイルの変更を検知して自動でビルドをしたのち、ブラウザを自動でリロードする
  • HMR( Hot Module Replacement )という編集したモジュールを自動で更新する
$ npm install -D webpack-dev-servser

cssやpostcssに必要な style-loader css-loader postcss-loader モジュールと、CSSJavaScriptでロードせずにlinkタグでロードさせるため、 extract-text-webpack-text をインストール

$ npm install -D style-loader css-loader postcss-loader extract-text-webpack-plugin@1.0.1

webpack.config.jsにビルドの設定を書く

  • デバッグ用のSourceMapの設定
  • entryポイントの設定
  • 出力先を設定
  • トランスパイルの設定
  • exstract-text-webpack-plugin の設定
  • webpack-dev-server でファイル変更した際に検知し、自動でビルド、自動でブラウザをリロードしてくれるように設定

client/webpack.config.js

/**
 * Require basic plugins
 */
const path    = require('path');
const webpack = require('webpack');

/**
 * webpack plugins
 */
const ExtractTextPlugin = require('extract-text-webpack-plugin');

module.exports = {
  devtool: 'inline-source-map',
  context: path.join(__dirname + '/../client/src'),
  entry: {
    application: [
      'webpack-dev-server/client?http://localhost:8080',
      'webpack/hot/dev-server',
      './javascripts/application.js'
    ]
  },
  output: {
    filename: '[name].js',
    publicPath: 'http://localhost:8080/assets/'
  },
  module: {
    loaders: [
      {
        test: /\.css$/,
        loader: ExtractTextPlugin.extract('style', 'css!postcss')
      }
    ]
  },
  plugins: [
    new ExtractTextPlugin('[name].css'),
    new webpack.HotModuleReplacementPlugin()
  ],
  devServer: {
    headers: {
      'Access-Control-Allow-Origin': 'http://localhost:3000',
      'Access-Control-Allow-Credentials': 'true'
    }
  }
};

webpack-dev-serverの起動用にpackage.jsonに追記

client/package.json

{
  ...
  "scripts": {
    "dev": " ./node_modules/.bin/webpack-dev-server --debug --hot --inline --devtool --public --host 0.0.0.0 --port 8080 --config ./webpack.config.js",
    ...
  },
  ...
}

webpack.config.js のentryは client/src/javascripts/application.js なので、 application.jsclient/src/stylesheets/application.css をrequireさせます。
画像などもwebpackで配信したい場合は、同様に application.js にrequireさせます。

client/src/javascripts/application.js

/**
 * Require stylesheets
 */
require('../stylesheets/application.css');

Rails側の設定

assets部分は webpack-dev-server から取得するため、 app/views/layouts/application.html.erb のheadタグの stylesheet_link_tagjavascript_include_tag 部分を変更。

app/views/layouts/application.html.erb

<!DOCTYPE html>
<html>
  <head>
    <title>SamplePostcss</title>
    <%= csrf_meta_tags %>

    <%= stylesheet_link_tag 'http://localhost:8080/application.css', media: 'all' %>
    <%= javascript_include_tag 'http://localhost:8080/application.js' %>
  </head>

  <body>
    <%= yield %>
  </body>
</html>

Railsの起動設定

rails snpm run dev の2つのコマンドを打てば起動できるのですが、面倒くさいので foreman というgemを使っていきます。

group :development do
  gem 'foreman'
end

ルートディクトリに Procfile という名前でファイルを作成し編集していきます。

rails: bundle exec rails server -b 0.0.0.0 -p 3000
webpack: npm --prefix ./client run dev

$ bundle exec foreman start のコマンドで立ち上がります。

テスト

以上でローカル環境の構築は完了したので、テストしていきましょう。

config/routes.rb

Rails.application.routes.draw do
  root 'static_pages#about'
end

app/controllers/static_pages_controller.rb

class StaticPagesController < ApplicationController
  # GET /
  def about
  end
end

app/views/static_pages/about.html.erb

<h1>about</h1>

client/src/stylesheets/application.css

h1 {
  color: red;
}

http://localhost:3000 にアクセスした結果
h1 タグのcolorが red になっているのと、 gray に変更した時に自動でブラウザの更新を行い色が変わりました。

f:id:cluex-developers:20170330171922g:plain

PostCSSのプラグインの導入方法

autoprefixer というprefixをつけてくれるプラグインを入れていきます。
まず client ディレクトリに移りnpmで autoprefixer をインストールします。

$ npm install -D autoprefixer

webpack.config.js を編集していきます。下記の記述でPostCSSのプラグインを導入することができます。

client/webpack.config.js

...

/**
 * postcss plugins
 */
const autoprefixer = require('autoprefixer');

module.exports = {

  ...
  
  postcss: [autoprefixer],

  ...

}

それではcssファイルの方に flex を書いてみます。

client/src/stylesheets/application.css

div {
  display: flex;
}

webpackのビルド後は以下のようになります。

dev {
  display: -webkit-box;
  display: -ms-flexbox;
  display: flex;
}

PostCSSのプラグインの紹介

postcss-import

ref) https://github.com/postcss/postcss-import

@import が使用できるプラグインになっています。postcss-simple-vars など他のプラグインを使用したファイルをimportした時にエラーになる可能性があるので、config.webpack.jspostcss の 方でこちらのプラグインを最初に読み込ませておくようにしましょう。

@import 'componsents/button.css';
@import 'base.css';

postcss-simple-vars

ref) https://github.com/postcss/postcss-simple-vars

SCSSのような $ を使って変数が定義できます。また弊社では変数( $ )はcolorの色を定義時に使用しています。

before

$red-color: #ff0000;
$column:    200px;

h1 {
  color: $red-color;
}

.column-400px {
  width: calc(2 * column);
}

after

h1 {
  color: #ff0000;
}

.column-400px {
  width: calc(2 * 200px);
}

postcss-nested

ref) https://github.com/postcss/postcss-nested

SCSSの様にネストを使用できるpluginです。

before

.item {
  &_title {
    width: 500px;

    @media (max-width: 500px) {
      width: auto;
    }

    body.is_dark & {
      color: white;
    }
  }

  img {
    display: block;
  }
}

after

.item_title {
  width: 500px;
}

@media (max-width: 500px) {
  .item_title {
    width: auto;
  }
}

body.is_dark .item_title {
  color: white;
}

.item img {
  display: block;
}

postcss-mixins

ref) https://github.com/postcss/postcss-mixins

SCSSの様に @mixin で定義し、 @include で呼び出す形ではないので注意が必要です。

before

@define-mixin margin-10px {
  margin: 10px;
}

.column {
  @mixin margin-10px;
}

after

.column {
  margin: 10px
}

また引数を指定することも可能です。
@define-mixin 引数で 0 を指定しています。 @mixin の方で $px の値を指定しなければ 0 になり、指定すればその指定した数値になります。 今回の場合は margin: 10px となります。

@define-mixin margin $px: 0 {
  margin: $px
}

.column {
  @mixin margin 10px;
}

PostCSS Sassy Mixinsと言うプラグインもあり、こちらだとSCSSの時と同じ記法で書けますので検討してみてください。

postcss-extend

ref) https://github.com/travco/postcss-extend

SCSSで使っていたextendと同じ記法で書けます。

before

%padding {
  padding: 10px;
}

.margin {
  margin: 10px;
}

.item {
  @extend %padding;
  @extend .margin;
}

after

.item {
  padding: 10px;
  margin: 10px;
}

いかがでしたでしょうか? PostCSSのプラグインの種類は豊富で、 mixin の機能を使いたい思った時には上記で書いた様に、 postcss-mixins , postCSS-sassy-mixins など複数ブラグインが存在するので、記法やコードを読んで選んでいく必要がありそうですね。 また拡張しすぎると本来のCSSの記法とかけ離れてしまうため使うプラグインは最小限に抑えるべきだと考えます。

We’re hiring!!

Cluexではエンジニアサイド、ビジネスサイド共にメンバーを募集しています! お気軽にご連絡下さいませ! エンジニアの方、ぜひ情報交換しましょう!

www.wantedly.com

www.wantedly.com