Webpack 4 + Babel + React 入門2018

ReactはAngularのようなフレームワークではなくライブラリです。このため学習しやすく直感的に使用ができます。しかし、フレームワークのように骨組みが用意されているわけではないため、ユーザが基盤(環境)を作ってあげる必要があります。具体的にはプラグインを自分で選択し組み合わせていきます。しかし、これが非常に面倒です。周辺ツールは複数あり、どれをどう組み合わせればいいか…入門者には中々厳しいものがあります。

これを解決するために、Facebookはcreate-react-appというコマンドを提供しています。これは必要なパッケージを一括で構成し、開発からビルドまで行える環境を提供してくれます。便利な反面、後々自分で構成を変えていく場合には多少知識を要します。

本記事は、現状(2018年6月)において、Reactの開発環境を最小構成で1から整えることを主とした解説記事です。入門者が構成の仕方をなんとなく理解できるぐらいが目標です。

目次

必要知識

ある程度噛み砕いて進行しますが、Nodeパッケージの管理方法の知識は必要です。尚、環境はLinux(Ubuntu)なので、多少のLinuxコマンドも出てきます。

また、本記事ではnodeパッケージ管理にyarnを使っていますが、npmでも置き換えて読み進められます。

コマンド yarn npm
開発環境に追加 yarn add package –dev npm install package –save-dev
パッケージの追加 yarn add package npm install package
開発環境の初期化 yarn init -y npm init -y
実行方法 yarn run development npm run development

本記事で利用している主なパッケージのバージョンは以下です。もし読み進めていてパッケージ側に問題があると思った場合は、以下のバージョンをインストールしてください。インストール時にpackage@10.0.0のような記述方法でバージョン指定が可能です。

"devDependencies": {
    "babel-core": "^6.26.3",
    "babel-loader": "^7.1.4",
    "babel-preset-env": "^1.7.0",
    "babel-preset-react": "^6.24.1",
    "babel-preset-stage-2": "^6.24.1",
    "clean-webpack-plugin": "^0.1.19",
    "css-loader": "^0.28.11",
    "file-loader": "^1.1.11",
    "html-loader": "^0.5.5",
    "html-webpack-plugin": "^3.2.0",
    "mini-css-extract-plugin": "^0.4.0",
    "node-sass": "^4.9.0",
    "react-hot-loader": "^4.2.0",
    "sass-loader": "^7.0.1",
    "style-loader": "^0.21.0",
    "webpack": "^4.10.2",
    "webpack-cli": "^2.1.4",
    "webpack-dev-server": "^3.1.4",
    "webpack-merge": "^4.1.2"
  },
  "dependencies": {
    "prop-types": "^15.6.1",
    "react": "^16.4.0",
    "react-dom": "^16.4.0",
    "react-router-dom": "^4.2.2"
}

Webpack 4

まずプロジェクトを始めましょう。

mkdir app_dir && cd $_
yarn init -y

上記によりpackage.jsonが作成されます。このファイルにはパッケージの依存関係などが記述されます。後で編集しますが、まずは必要なディレクトリを作成しましょう。

## ディレクトリの作成
mkdir -p src/components dist public/assets

Webpackはモジュラ―バンドラーです。現行のバージョン4では、デフォルトのエントリーポイントがsrc/index.js、出力ディレクトリがdistとなっています。エントリーポイントとは、Javascriptのバンドル構築を開始するファイルです。また、今回は上記で作成したsrc/componentsにReactコンポーネントを配置します。public/assetsは静的なファイル(例えば画像などのリソース)を置くディレクトリとします。

次に必要なパッケージをいくつかインストールします。

yarn add webpack webpack-dev-server webpack-cli webpack-merge --dev
## webpack - webpack本体
## webpack-dev-server - 開発用サーバー
## webpack-cli - webpackフロントツール
## webpack-merge - webpackの設定ファイルの結合パッケージ

webpackの設定ファイルを作成します。順番に、共通環境、開発環境、本番環境の設定ファイルとなります。

touch webpack.common.js webpack.dev.js webpack.prod.js

今は開発版の設定ファイルのみ記述しておきます。

src/webpack.dev.js

const merge = require('webpack-merge');
const common = require('./webpack.common.js');

module.exports = merge(common, {
    mode: 'development',
    devtool: 'inline-source-map',
    devServer: {
        contentBase: './dist',
    }
});

必要なモジュールを読み込み、モードの設定、ソースマップの設定、devServerという開発向けのローカルWebサーバーの設定をしています。webpack-mergeというプラグインを使い、必要環境に合わせた設定ファイルを作成しています。

Webpack4にはモードという概念があり、productiondevelopmentが選択できます。上記は開発版のWebpackの設定ファイルなので、developmentが指定されています。productionモードでは書き出されるファイルが縮小されたり本番環境向けに最適化されます。

さて、置き去りになっていたpackage.jsonに以下のスクリプトを追加しましょう。

  "scripts": {
    "develop": "webpack-dev-server --open --config webpack.dev.js",
    "build": "webpack --config webpack.prod.js"
  }

これが実行スクリプトです。developが開発版、buildが本番ビルドです。

Babel

現代のJavascriptの多くはES6で書かれています。しかし、すべてのブラウザがES6の構文に対応しているわけではありません。古いブラウザにも理解できるようにするためトランスパイルが必要です。その役目を担うのがBabelです。また、Webpackはトランスパイル機能を持っていませんが、ローダーを持っています。Babelにはbabel-loaderというローダーがあり、私たちはこれを使います。

Babelを利用するために必要なパッケージをインストールします。

yarn add babel-core babel-loader babel-preset-env babel-preset-stage-2 babel-preset-react --dev
## babel-core - Babel本体
## babel-loader - Babelローダー
## babel-preset-env - 古い環境用のプリセット
## babel-preset-stage-2 - Draft状態の仕様を有効にする
## babel-preset-react - React用のプリセット

## Babelの設定ファイルを作成
touch .babelrc

プリセットは言うならば設定値です。上記の他にも色々ありますが、React開発を行うには上記で問題がないと思われます。Babelの設定ファイルでこれを指定します。

.babelrc

{
    "presets": [
        "env",
        "react",
        "stage-2"
    ]
}

昨今はbabel-preset-es2015ではなく、babel-preset-envがES6以降のプリセットとして推奨されています。デフォルトの変換形式はcommon.jsです。

Webpackの共通部分の設定を記述します。

webpack.common.js

module.exports = {
    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: /node_modules/,
                use: {
                    loader: 'babel-loader'
                }
            }
    },
    output: {
        publicPath: '/',
        filename: '[name].[hash].js'
    }
};

.jsというファイルはbabel-loaderを利用しトランスパイルを行います。excludeで除外するディレクトリを指定でき、パッケージが入っている/node_modulesディレクトリを指定しています。

outputは出力時の設定です。ファイル名に加えハッシュ値をつけて出力します。

css-loader

WebpackでCSSを扱うために必要なパッケージをインストールします。

yarn add css-loader style-loader sass-loader node-sass mini-css-extract-plugin --dev
## css-loader - CSS用のローダー
## style-loader - styleタグで書き出す
## sass-loader - SASS用のローダー
## node-sass - sassのコンパイル
## mini-css-extract-plugin - CSSを別々のファイルに抽出する

webpack.dev.js

const merge = require('webpack-merge');
const common = require('./webpack.common.js');

module.exports = merge(common, {
    mode: 'development',
    devtool: 'inline-source-map',
    module: {
        rules: [
            {
                test: '/\.scss$/',
                loaders: ['style-loader', 'css-loader', 'sass-loader']
            }
        ]
    },
    devServer: {
        contentBase: './dist',
    }
});

.scssファイルをstyleLoader(cssLoader(sassLoader("source")))という順番でローダーに渡します。sassで記述したファイルはstyle-loaderにより<style>タグで書き出されます。

webpack.prod.js

const merge = require('webpack-merge');
const common = require('./webpack.common.js');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = merge(common, {
    mode: 'production',
    module: {
        rules: [
            {
                test: /\.scss$/,
                use: [
                    MiniCssExtractPlugin.loader,
                    'css-loader',
                    'sass-loader'
                ]
            }
        ]
    },
    plugins: [
        new MiniCssExtractPlugin({
            filename: '[name].[hash].css',
            chunkFilename: '[id].[hash].css',
        })
    ]
});

mini-css-extract-pluginはCSSを含むJavascriptファイル単位でCSSを書き出します。これは、Reactのようにコンポーネント単位でCSSを組み込む場合に役立ちます。コード分割(Code Spliting)をし、非同期でコンポーネントを読み込む際に便利です。

html-loader

CSSのローダーがあればHTMLにもローダーがあります。

yarn add html-loader html-webpack-plugin --dev
## html-loader - HTML用のローダー
## html-webpack-plugin - WebpackでHTMLを扱うプラグイン

webpack.common.js

const HtmlWebPackPlugin = require('html-webpack-plugin');

module.exports = {
    module: {
        rules: [
            ...
            
            {
                test: /\.html$/,
                use: [
                    {
                        loader: 'html-loader',
                        options: { minimize: true }
                    }
                ]
            }
        ]
    },
    ...
    
    plugins: [
        new HtmlWebPackPlugin({
            template: './src/index.html',
            filename: './index.html',
            favicon: './src/favicon.ico'
        })
    ]
};

templateは文字通りテンプレートで、このHTMLファイルにエントリーポイントのJSがバンドルが組み込まれます。続けて読み込むHTMLを作成しましょう。

src/index.html

<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="utf-8">
        <title>Webpack 4</title>
    </head>
    <body>
        <div id="app"></div>
    </body>
</html>

file-loader

Webpackで画像を取り扱えるようにします。パッケージをインストールします。

yarn add file-loader --dev
## file-loader - 画像ファイル用のローダー

webpack.common.js

module: {
	rules: [
    	{
    	    ...
            {
                test: /\.(png|jpg|gif|jpeg|ttf)$/,
                use: [
                    {
                        loader: 'file-loader',
                        options: {
                            name: '[path][name].[ext]',
                        }
                    }
                ]
            }
        ]
 ...

optionsでは書き出すファイル名をプレースホルダで定義しています。いくつか書式があるので、詳しくはドキュメントを参考にしてください。ここでは「パス/元のファイル名.拡張子」としています。

React

ようやくReactです。必要パッケージをインストールしましょう。

yarn add react react-dom prop-types react-router-dom
## react - React本体
## react-dom - ブラウザ版のReactパッケージ
## prop-types - Reactの型チェック用パッケージ(今回未使用)
## react-router-dom - React Router(今回未使用)

今回の説明で解説しないものもありますが、Reactでアプリケーションを作成する上でほぼ必須のパッケージもインストールしておきました。不要ならば入れる必要はありません。

簡単なコンポーネントファイルとSASSファイルを設置しましょう。

src/components/App.js

import React from 'react';
import './App.scss';
import img from '../../public/assets/cat.jpg';
const App = () => <p>App<br/><img src={img} /></p>;

export default App;

上記で画像ファイルを使用しているため、適当な画像ファイルをpublic/assets/cat.jpgに配置しておいてください。

src/components/style.scss

$black-color: #000;
body {
    background-color: $black-color;
    color: #FFF;
}

src/index.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/App';
ReactDOM.render(<App />, document.getElementById('app'));

あとはsrc/favicon.icoを設置しておいてください。ないと警告が表示されます。

実行

開発版で実行するとブラウザが起動します。

yarn run development

                       Asset       Size  Chunks             Chunk Names
main.c662ba07a4c02b3c5e7f.js   2.54 MiB    main  [emitted]  main
                 favicon.ico    136 KiB          [emitted]
                ./index.html  255 bytes          [emitted]
Entrypoint main = main.c662ba07a4c02b3c5e7f.js

プロダクション版だとブラウザは起動されず、distディレクトリにファイルが書き出されます。このdist以下が本番環境で必要なすべてのコードとなります。

yarn run build

                        Asset       Size  Chunks             Chunk Names
main.d56d6a82133c90683a3d.css   51 bytes       0  [emitted]  main
 main.d56d6a82133c90683a3d.js   99.4 KiB       0  [emitted]  main
                  favicon.ico    136 KiB          [emitted]
                 ./index.html  316 bytes          [emitted]
Entrypoint main = main.d56d6a82133c90683a3d.css main.d56d6a82133c90683a3d.js

コード分割などは行っていないので、1つのmain.jsにファイルがバンドルされています。

cleanup

新規ファイルを追加しビルドを行うたび、/distの不要なファイルを手動削除するのは面倒です。clean-webpack-pluginを使うと自動でディレクトリのクリーンアップ行えます。

yarn add clean-webpack-plugin --dev
## clean-webpack-plugin - ディレクトリのクリーンアップパッケージ

webpack.common.js

const CleanWebpackPlugin = require('clean-webpack-plugin');

...
module.exports = {
    plugins: [
        new CleanWebpackPlugin(['dist']), 
        ...
    ]
}

これでビルドすると、毎回/distが削除され、再生成されます。

React Hot Loader(HMR)

React Hot Loaderは通称HMRと呼ばれ、Reactの開発で広く利用されているパッケージです。現状、私たちのアプリケーションはソースコードの一部を変更するたび、ページのリロード(読み込み)が起こります。HMRはリロードすることなく、ブラウザにその変更を適用させ、再レンダリングを行わせることが可能となります。

yarn add react-hot-loader --dev
## react-hot-loader - HOTモジュール

webpack.dev.js

const webpack = require('webpack');
...

module.exports = merge(common, {
	...

	plugins: [
        new webpack.HotModuleReplacementPlugin()
    ],
    devServer: {
        contentBase: './dist',
        hot: true
    }
});

HMRを読み込み、devServerにhot: trueとすることで有効になります。そして設定ファイルにHMRを追加します。

.babelrc

{
    "presets": [
        ["env", { "modules": false }],
        "react",
        "stage-2"
    ],
    "plugins": [
        "react-hot-loader/babel"
    ]
}

ローカルサーバー側はこれでよいのですが、React側のコード部分も触る必要があります。作成するアプリケーションにもよりますが、今回の場合、src/inde.jsを以下のように変更します。

src/index.js

import React from 'react';
import ReactDOM from 'react-dom';
import { AppContainer } from 'react-hot-loader';
import App from './components/App';

const render = Component =>
    ReactDOM.render(
        <AppContainer>
            <App />
        </AppContainer>,
        document.getElementById('app')
    );

render(App);

// Webpack Hot Module Replacement API
if (module.hot) module.hot.accept('./components/App', () => render(App));

react-hot-loaderをインポートし、描画するコンポーネントを<AppContainer>でラップします。最後の1行でhotモジュールがtrueだったら、HMRを有効にしてレンダリングが行われます。無効の場合、つまりプロダクションのときはdevServerを使わないので、スルーされます。

それではyarn run developしてみましょう。出力結果は前回とまったく同じです。ただし、ブラウザが起動している状態で、App.jsを以下に変更し保存してみましょう。

import React from 'react';
import './App.scss';

// const App = () => <p>App<br/><img src={img} /></p>;
const App = () => <p>React App<br/><img src={img} /></p>;

export default App;

保存した瞬間ブラウザのページがリロードされることなく、結果が反映されたはずです。これで開発時のフィードバックループが短縮されます。

おわりに

初めての人だと煩わしい部分や理解しがたい内容もあると思います。冒頭に書いたようにReactはフレームワークではなくライブラリです。それゆえ、このようにWebpackと複数のプラグインを組み合わせて開発をしていく必要があるのです。ベストプラクティスな構成はわかりませんが、アプリケーションの規模やユーザのスキル・それに個々の好みにも左右されるはずです。

最初は慣れが必要ですが、ある程度開発を行っているうちに、僕の考えた最強のReact開発環境が出来上がっているはずです。