React.jsでプロダクション環境を見据える

Jey
11 min readJan 3, 2017

--

reactjsでアプリケーション構築した際、 production環境へのデプロイを見据えてwebpack周りの設定を見直したので それをまとめました。

webpack.config.jsのリネーム

webpack.configでbabelなコードが書きたかったのでリネーム あわせて、constじゃなくて、importでライブラリを読み込むように変更

リネーム & 書き換え

% mv webpack.config.js webpack.config.babel.js// before
const webpack = require('webpack');
// after
import webpack from 'webpack';

webpack-merge

webpack-merge

webpack.configをマージするためのモジュール。 共通設定ファイルと環境別の設定ファイルをマージするみたいな用途で利用する。

環境別でwebpack.configをかき分けたい。 手動でオブジェクトのdeep mergeするのはいやだ!みたいな人は使おう。

インストール

npm i -D webpack-merge

設定

  • 共通設定: webpack.config.babel.js
  • NODE_ENV=production: webpack.config.prod.babel.js
  • NODE_ENV=development: webpack.config.dev.babel.js
import merge from 'webpack-merge';// NODE_ENVで分岐
const config = process.env.NODE_ENV === 'production' ?
require('./webpack.config.prod.babel.js') :
require('./webpack.config.dev.babel.js');
const common = {
context: __dirname,
entry: './src/index.jsx',
output: {
path: `${__dirname}/public`,
publicPath: '/',
filename: 'bundle.js',
},
module: {
preLoaders: [
{ test: /\.jsx?$/, exclude: /node_modules/, loader: 'eslint' },
],
loaders: [
{ test: /\.jsx?$/, exclude: /node_modules/, loaders: ['react-hot-loader/webpack', 'babel'] },
{ test: /\.css$/, loaders: ['style', 'css'] },
{ test: /\.json$/, loader: 'json' },
{ test: /\.(jpe?g|png|gif)$/, loader: 'url?limit=10000' },
],
},
resolve: {
extensions: ['', '.js', '.jsx', '.json'],
},
eslint: {
configFile: './.eslintrc',
},
};
module.exports = merge(common, config);

npm runコマンドでwebpack呼ぶときは適宜NODE_ENVをつける。

scripts: {
start: webpack-dev-server --history-api-fallback --hot --inline --progress --colors,
build-debug: webpack --display-error-details --progress --colors,
build: NODE_ENV=production webpack --progress --colors,
},

UglifyJsPlugin などでjsを最適化・圧縮

UglifyJsPluginで圧縮して、DedupePlugin, AggressiveMergingPluginで最適化する。

詳細が知りたければこちら list of plugins

設定

import webpack from 'webpack';module.exports = {
debug: false,
devtool: false,
plugins: [
new webpack.optimize.DedupePlugin(),
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false,
},
comments: false,
}),
new webpack.optimize.AggressiveMergingPlugin(),
],
};

html-webpack-plugin

html-webpack-plugin

index.htmlに直接、bundle.jsを書き込んでいたところに、 ハッシュコード付きのscriptタグを挿入したかったのでこのプラグインを導入した。

ハッシュコードってのはこういうやつね <script src="/bundle.js?4398c4ce46af21776b47"></script>

インストール

npm i -D html-webpack-plugin

テンプレートファイルの用意

index.htmlをejsファイルにリネームし、<script src="/bundle.js" />なコードを除去する。

mv src/index.html src/index.template.ejs

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<title>title</title>
</head>
<body>
<div id="root"></div>
- <script type="text/javascript" src="/bundle.js"></script></body>
</body>
</html>

設定

pluginsに下記のように設定すると、npm run buildした時にいい感じでindex.htmlを吐いてくれる。 他にもscriptとかmetaタグを<head>に入れたくなったらマニュアル見てね。

import HtmlWebpackPlugin from 'html-webpack-plugin';  plugins: [
new HtmlWebpackPlugin({
hash: true,
filename: 'index.html',
favicon: './src/favicon.jpg',
template: './src/index.template.ejs',
inject: 'body',
}),
],

出力サンプル

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<title>title</title>
<link rel="shortcut icon" href="/favicon.jpg"></head>
<body>
<div id="root"></div>
<script type="text/javascript" src="/bundle.js?4398c4ce46af21776b47"></script></body>
</html>

webpack-manifest-plugin

webpack-manifest-plugin

bundle.jsって名前だとCDNに上げた時、世代管理できない。 自動生成されたファイル名にしたいってなったらこれ。

インストール

npm i -D webpack-manifest-plugin chunk-manifest-webpack-plugin

設定

下記設定だと、entryのキーが出力後のファイル名になるので注意。 hot loadしたいのでproduction環境以外は[chunkhash]つけない。

import ManifestPlugin from 'webpack-manifest-plugin';
import ChunkManifestPlugin from 'chunk-manifest-webpack-plugin';
module.exports = {
context: __dirname,
entry: {
+ bundle: './src/index.jsx',
},
output: {
path: `${__dirname}/public`,
publicPath: '/',
- filename: 'bundle.js',
+ filename: '[name].js',
},
plugins: [
+ new ManifestPlugin(),
+ new ChunkManifestPlugin({
+ filename: 'chunk-manifest.json',
+ manifestVariable: 'webpackManifest',
+ }),
]
module.exports = {
context: __dirname,
entry: {
+ bundle: './src/index.jsx',
},
output: {
path: `${__dirname}/public`,
publicPath: '/',
- filename: 'bundle.js',
+ filename: '[name].[chunkhash].js',
},
]

npm run buildしたらこういうのが出力される。 manifest.jsonも吐き出されるので、なにかと連携したければそれを読み込むといいかも。

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<title>title</title>
</head>
<body>
<div id="root"></div>
<script type="text/javascript" src="/bundle.b00a9f3a29b791781cc5.js?7490b7329f7ad1443141"></script></body>
</html>

copy-webpack-plugin

copy-webpack-plugin

本番環境にデプロイして、いざ動作確認してみたら、 react-router使ってるせいで、404出ちゃったとかありませんか。

そしたら、apacheの設定ファイル置きたくなったり、sass用のリダイレクトファイルを public配下に設置したくなりますよね。 そいういう時にこれ使いました。 例 .htaccess, _redirects

import CopyWebpackPlugin from 'copy-webpack-plugin';
// 〜略〜
plugins: [
new CopyWebpackPlugin([
{ from: { glob: './src/static/**', dot: true }, to: '[name].[ext]' },
]),

Google Analytics

react-ga

react-router使ってたら、画面遷移時にアナリティクスに取れなくなるのでこれを導入

インストール方法

npm i -S react-ga

設定

import React from 'react';
import ReactDOM from 'react-dom';
import ReactGA from 'react-ga';
import { Router, Route, IndexRoute, browserHistory } from 'react-router';
import App from '../containers/App';
window.React = React;
ReactGA.initialize('UA-123456-7');
const logPageView = () => {
ReactGA.set({ page: window.location.pathname });
ReactGA.pageview(window.location.pathname);
};
ReactDOM.render((
<Router history={browserHistory} onUpdate={logPageView}>
<Route path="/">
<IndexRoute component={App} />
<Route path="/:userName" component={App} />
</Route>
</Router>
), document.getElementById('root'));

所管

あとやり残してることあるかな。気が付いたら追記する。

--

--

Jey

脳筋ぷろぐらまー。フロントエンドからインフラまで色々やるよ。/React/Ruby/Rails/AWS/Docker/GGXXAC/LoL/ https://jey3dayo.net