勉強のためにPython django + Reactの構成で実験アプリを作っていて、その時に知ったwebpackの設定などをまとめておきたいと思います。
ちなみに、もしPython django + Reactの構成でWebアプリを作成するのなら、非公式ですが以下にプロジェクトのボイラープレートを公開してくださっているものがあるのでこれを使うのが楽そうです。
- Django, React, Bootstrap 4 with Python 3 and webpack project boilerplate
今回は、同じような構成を作ろうとすると何が必要なのかを勉強するため、上記を参考にさせてもらいつつ、自分で整備してみました。
create-react-app
まず、そもそもreactを今から始めるのであれば、webpackやbabelを自分でいろいろ整備するより、create-react-app
を使うのが簡単です。
- facebookincubator/create-react-app: Create React apps with no build configuration.
これは、npmでインストールできるreactの初期状態作成用のコマンドです。
これで作成されたReactアプリはreact-scripts
というコマンドでアプリの起動、ビルドなどを行うようになっています。
このreact-scripts
コマンドが、内部的にwebpackやbabelを使って開発用サーバー起動や本番用のビルドなどをうまい具合に行ってくれます。
ただ、ちょっと困る点として、webpackに独自に設定を追加しようと思ったときにそのままでは対応方法がありません。
対応するには、react-scripts
を使うのをやめて独自でwebpackなどの設定をメンテすることになります。
独自メンテにするにあたっての救済措置として、npm eject
を実行すると元々react-scripts
で管理していたwebpack.config.js
やenv
ファイル群などの各種設定ファイルを生成することができます。
react-scripts
をそのまま使いつつ、独自のwebpack設定をすることについては、以下のissueで提案があったようですが対応不可としてクローズされています。
- Custom additional webpack config? · Issue #99 · facebookincubator/create-react-app
なお、本記事の以降の部分は、このeject
を使わず自前で書こうとした上での記述になります。
webpack設定
webpack.config.js
の記述方法については、公式の文書をまずは見ておくのがいいと思います。
というのも、webpackのバージョンがv2に上がって設定ファイルの書き方が変わっていっているようです。
ネット上で調べものをしたときに、同じ設定なのに記事ごとに書き方が違ったりする場合もあるので、旧式の表記と最新の表記を把握しておく必要がありそうです。
- webpack document
私が見つけた範囲では、module.loaders
やmodule.loaders.query
などのパラメータは非推奨になっています。
- https://webpack.js.org/configuration/module/#rule-loaders
- https://webpack.js.org/configuration/module/#rule-options-rule-query
v1 旧表記
// パターンA: loaderのオプション設定をGETクエリ表記で書く方法
module: {
loaders: [
{
test: /\.png$/,
loader: "url-loader?mimetype=image/png"
}
]
}
// パタンーンB: loaderのオプション設定を query で指定する方法
module: {
loaders: [
{
test: /\.png$/,
loader: "url-loader",
query: { mimetype: "image/png" }
}
]
}
v2 新表記
module: {
rules: [
{
test: /\.png$/,
use: [
{
loader: "url-loader",
options: {
mimetype: "image/png"
}
}
]
}
]
}
対象のファイル条件をtest
で指定した上で、複数のloaderの定義をuse
の中で列挙する方式のようです。
また、オプション設定はoptions
という項目で指定します。
babel
Reactを使うにあたってのbabelの設定をシンプルに書くと以下のようになるようです。
module.exports = {
context: __dirname,
entry: [
'babel-polyfill',
'./src/index.js',
],
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: [
{
loader: 'babel-loader',
options: {
// react用表記と、es2015表記を有効化
presets: ['react', 'es2015'],
// 開発時に変換結果をキャッシュする
// 本番用のビルドにはこの設定は不要
cacheDirectory: true
}
}
]
}
]
}
}
なお、上記の設定を実行するには以下のnpmのインストールが必要です。
npm install --save react babel-polyfill
npm install --save-dev babel-loader babel-preset-es2015
上記で基本的には問題ないですが、最新のjavascriptの記述方法のいくつかを利用したい場合は個別にplugins
として導入します。
babel追加設定: class内の変数定義
classの定義内に直接変数を定義したい場合はtransform-class-properties
を使います。
class SomeClass extends Component {
// class変数
static classVar = "class var";
// インスタンス変数
instanceVar = 10;
}
例えば、React Componentの作成時にpropTypesを以下のように書くことができます。
class Some extends Component {
// class変数
static propTypes = {
tweets: PropTypes.array
}
}
babel追加設定: 配列やハッシュの展開表記
transform-object-rest-spread
を使うと、配列やハッシュを簡単に複製するための表記が使えるようになります。
以下のような stateに新規パラメータをマージした新しいハッシュを生成するような処理があるとします。
var state = { a: 10, b: 20 };
console.log(Object.assign({}, state, { b: 2000, c: 30 }));
//=> { a: 10, b: 2000, c: 30 }
これを、以下のように表記できるようになります。
var state = { a: 10, b: 20 };
console.log({...state, b: 2000, c: 30 });
//=> { a: 10, b: 2000, c: 30 }
上記、2つのpluginを加えたbabelの設定は以下のようになります。
// rules内の記述
{
test: /\.js$/,
exclude: /node_modules/,
use: [
{
loader: 'babel-loader',
options: {
cacheDirectory: true,
presets: ['react', 'es2015']
plugins: [
'transform-class-properties', // class 変数定義を可能にする
'transform-object-rest-spread', // {...rest} 表記を可能にする
]
}
}
]
}
npm install --save-dev transform-class-properties transform-object-rest-spread
.babelrc
ファイル
babel-loader
で指定している options
の内容はプロジェクトのトップディレクトリの.babelrc
ファイルに記述することもできます。
これまでの例を.babelrc
を使うようにすると以下のような記述になります。
// rules内の記述
{
test: /\.js$/,
exclude: /node_modules/
use: [
{ loader: 'babel-loader' }
]
}
{
"cacheDirectory": true,
"presets": ["react", "es2015"]
"plugins": [
"transform-class-properties",
"transform-object-rest-spread"
]
}
Web上のデモなどを参考にするときはwebpack.config.js
以外のこういったファイルも確認しておく必要があります。
個人的には、暗黙で利用されるファイルに設定を分散させると後から見落として理解しにくくなるような気がするのと、.bashrc
はjsonファイルであるためコメントを書いたりすることができないので、webpack.config.js
に書く方がいいかなと思っています。
開発用サーバー
次に、開発用サーバー(ファイルを修正したら即時ブラウザを自動リロードしてくれる)を起動できるようにする設定を追加します。
+// pluginsで使用する
+var webpack = require('webpack')
module.exports = {
// 略
entry: [
+ 'webpack-dev-server/client?http://localhost:3000',
+ 'webpack/hot/only-dev-server',
+ 'react-hot-loader/patch',
'babel-polyfill',
'./assets/src/index.js',
],
// 略
// module.rules
{
loader: 'babel-loader',
options: {
cacheDirectory: true,
presets: ['react', 'es2015'],
plugins: [
+ 'react-hot-loader/babel',
'transform-class-properties', // class 変数定義を可能にする
'transform-object-rest-spread', // {...rest} 表記を可能にする
]
}
// 略
+ plugins: [
+ // webpack moduleに含まれる
+ new webpack.HotModuleReplacementPlugin(),
+ new webpack.NoEmitOnErrorsPlugin(), // don't reload if there is an error
+ ],
+ // webpack-dev-server向け設定
+ devServer: {
+ hot: true,
+ inline: true,
+ historyApiFallback: true,
+ port: 3000,
+ headers: { 'Access-Control-Allow-Origin': '*' } // 必要があれば設定する
+ },
+ // jsのsource mappingファイルを作成する
+ // https://webpack.js.org/configuration/devtool/#devtool
+ devtool: 'cheap-module-eval-source-map'
}
追加で以下のnpmモジュールが必要です。
$ npm install --save-dev react-hot-loader webpack-dev-server
上記の設定を追加した上で、以下のコマンドで開発用のサーバーが起動できます。
$ webpack-dev-server
これを、package.json
のscripts
に定義しておくのが良いと思います。
"scripts": {
"start": "webpack-dev-server"
}
暗黙で全てのjsファイルにモジュールをimportする
webpack.ProvidePlugin
を使うと、すべてのjsファイルでグローバルな定義として暗黙でモジュールを定義することができます。
var webpack = require('webpack')
module.exports = {
// 略
plugins: [
// 全js内にimportを暗黙定義
new webpack.ProvidePlugin({
Paper: ['material-ui/Paper', 'default'],
RaisedButton: ['material-ui/RaisedButton', 'default'],
TextField: ['material-ui/TextField', 'default'],
List: ['material-ui/List', 'List'],
ListItem: ['material-ui/List', 'ListItem'],
Colors: ['material-ui/styles', 'colors'],
})
]
// 略
}
上記は、material-uiというUI Componentライブラリを使った例です。
これで、importで明示せずともUI用のコンポートネントを普通のHTMLタグと同レベルで使えるようになります。
定義の書き方は以下のような書き換えになります。モジュール側でのexport default
に対応して, 'default']
の部分が必要になるのに注意です。
import Paper from 'material-ui/Paper';
↓
Paper: ['material-ui/Paper', 'default']
import { ListItem } from 'material-ui/List';
↓
ListItem: ['material-ui/List', 'ListItem']
まとめ
今回の内容は、最初に紹介した django + react boilerplateや、react-scripts eject
で生成されたものなどを参考にさせていただいて、気になるところを調べていったものです。
Javascriptの開発環境がwebpack + babelにおおよそ収束している感じですが、いざそれらについて調べ物をすると、記載されている内容が断片的だったり、バージョンによって書き方が違ったり、そもそも複数の表記方法があるため書き方が違ったり、色々と違いがあって辛いところです。