Hirooooo’s Labo

開発メモ、ガジェット、日記、趣味など、思った事を思ったまんま書くブログ

React × Redux 初心者入門(5日目:webpackでCSSを読み込ませて反映する)

f:id:hirooooo-lab:20160713002241j:plain

前回はMaterial-uiの実装を行い、デザインの変更を行いました。
今回もデザイン関連の設定になりますが、cssファイルを作成して、webpackで読み込んで反映させる方法を書きたいと思います。

複数のcssファイルが作成されたときのことを考慮して、bundle時に1つのcssにまとめて出力するようにしてみたいと思います。

必要なnodeモジュールをインストールする

cssを反映させるために、以下のモジュールをインストールします。

  • css-loader
  • extract-text-webpack-plugin

css-loader

cssをjavascriptで読み込めるようにするローダーです。
以下のコマンドでインストールしてください。

$ npm install --save-d css-loader

extract-text-webpack-plugin

今回はcssを1つのファイルにまとめて出力するために使用しています。
その際minimizeも行おうと思います。
以下のコマンドでインストールしてください。

$ npm install --save-d extract-text-webpack-plugin

これで必要なモジュールのインストールは完了です。

最近はbundle.js内に<style>タグとして読み込ませてしまい、cssファイルを別に出力しないようにするのが流行りですが、今回はあえてcssファイルを別で出力するようにしたいと思います。

Javascript in cssを行う場合は style-loaderを使用するとできます。
ちなみに、webpackのcss周りについては以下の記事が参考になると思います。

qiita.com

qiita.com

cssファイルを作成する

src/css/main.cssファイルを作成します。

ブラウザの規定でbodyに余分なmarginがあったりしたので、そのあたりを削除ました。
あと、スタイルとしてdiv用のセンタリングスタイルとしてcenterDivというスタイルを作ってみました。

html {
    font-size: 13px;
    line-height: 20px;
    color: #5b5b5b;
    min-height: 100%;
    position: relative;
}

body {
    margin: 0px;
    min-height: 100%;
    background-color: #FAFAFA
}

.centerDiv {
    margin: 0 auto 0 auto;
    display: table;
}

cssファイルを反映させる

src/index.jsxの修正

index.jsxでcssファイルをインポートしておきます。

// cssインポート
require('./css/main.css');

src/index.jsx

// index.jsx

import React from 'react';
import { render } from 'react-dom';
import { Provider } from 'react-redux';

import App from '../containers/App';
import configureStore from '../store/configureStore';

// cssインポート
require('./css/main.css');

const store = configureStore();

render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root'),
);

components/Counter.jsxの修正

Counter.jsxのカウント値が表示されるところと、ボタンのところをセンタリングさせたいと思います。
対象箇所を<div>でくくり、作成したcssのcenterTableスタイルを当てます。

components/Counter.jsx

// components/Counter.jsx

import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import Button from 'material-ui/Button';
import AddIcon from 'material-ui-icons/AddCircle';
import RemoveIcon from 'material-ui-icons/RemoveCircle';
import { withStyles } from 'material-ui/styles';


const styles = {
  button: {
    margin: 5,
  },
};

class Counter extends PureComponent {
  static propTypes = {
    counter: PropTypes.object.isRequired,
    actions: PropTypes.object.isRequired,
    classes: PropTypes.object.isRequired,
  };

  render() {
    const { counter, actions, classes } = this.props;
    return (
      <div style={{ width: '100%' }}>
        <div className="centerTable">
          <h2>count={counter.value}</h2>
        </div>
        <div className="centerTable">
          <Button raised color="primary" className={classes.button} onClick={actions.increment}>
            増加
            <AddIcon />
          </Button>
          <Button raised color="secondary" className={classes.button} onClick={actions.decrement}>
            減少
            <RemoveIcon />
          </Button>
        </div>
      </div>
    );
  }
}

export default withStyles(styles)(Counter);

webpack.configの修正

cssを読み込めるようにloadersの追加とpluginの追加を行います。

ExtractTextPluginの追加

以下の記述を追記します。

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

 plugins: [
    new ExtractTextPlugin('css/bundle.css'),
  ],

css-loaderの追加

以下の記述を追記します。

 module: {
    loaders: [
      { test: /\.css$/, loader: ExtractTextPlugin.extract('css-loader?minimize') },
    ],

これで、ソース上で参照しているcssが読み込まれ、bundle時にはcss/bundle.cssというファイルに纏められて出力されるようになります。

HtmlWebpackPluginも使用しているので、作成されるindex.htmlには出力されたcss/bundle.cssへのLinkタグが動的に挿入されます。

全体的にはこんな感じになりました。
webpack.config.js

const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');


module.exports = {
  context: __dirname,
  entry: [
    './src/index.jsx',
  ],
  output: {
    path: path.resolve(__dirname, 'dist'),
    publicPath: '/',
    filename: 'bundle.js',
  },
  devtool: 'inline-source-map',
  module: {
    loaders: [
      {
        test: /\.jsx?$/,
        enforce: 'pre',
        exclude: /node_modules/,
        loader: 'eslint-loader',
      },
      {
        test: /\.jsx?$/,
        exclude: /node_modules/,
        loaders: ['babel-loader'],
      },
      { test: /\.css$/, loader: ExtractTextPlugin.extract('css-loader?minimize') },
    ],
  },
  resolve: {
    extensions: ['.js', '.jsx'],
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html',
      filename: 'index.html',
      inject: 'body',
    }),
    new webpack.LoaderOptionsPlugin({
      options: {
        eslint: {
          configFile: './.eslintrc',
        },
      },
    }),
    new ExtractTextPlugin('css/bundle.css'),
  ],
};

実行してみる

ターミナルでnpm startを打ってみてください。

$ npm start

以前の状態
f:id:hirooooo-lab:20180126151707p:plain
↑から↓
f:id:hirooooo-lab:20180126170335p:plain
に変更されて、センタリングされていたらcssの反映は完了です。

buildしてみる

npmスクリプトのbuildタスクを実行してみると、生成ファイルがdistディレクトリに出力されるようになっています。
以下のコマンドをターミナルから出力してみてください。

$ npm run build

distディレクトリが生成されて中に以下のファイルが作成されたと思います。
f:id:hirooooo-lab:20180126170816p:plain

index.htmlの内容を見てみると、出力されたbundle.cssのリンクが動的に作成されているのがわかると思います。

// index.html

<!doctype html>
<html>
<head>
    <meta charset="utf-8">
    <title>React-Redux Boilerplate</title>
<link href="css/bundle.css" rel="stylesheet"></head>
<body>
<div id="root"></div>
<script type="text/javascript" src="bundle.js"></script></body>
</html>

まとめ

これでcssの読み込みと出力については完了です。
今回はcssファイルを別出しにしましたし、cssメタ言語のsassやlessは使用しませんでした。
使用する場合でもwebpackのloaderで問題なく対応できると思います。

参考にさせていただいたサイト

次回予告

次回予告というか、あとやらなきゃいけないと思ってること

  • 開発環境の効率化をするためにHotReloadとソースマップを実装する → Done!
  • ESLintでAirbnbのスタイルガイドを実装する → Done!
  • Materialデザインを実装する → Done!
  • CSSの読み込みを実装する → Done!
  • ページルーティングを実装する
  • REST通信を実装する
  • ログイン制御のフローを実装する
  • デプロイ方法を検討、実装する

次回の記事

現在作成中です。もうしばらくお待ち下さい。

今までの記事