26
27

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

ReactAdvent Calendar 2016

Day 22

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

Last updated at Posted at 2016-12-22

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
webpack.config.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をつける。

package.json
  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

設定

webpack.config.prod.babel.js
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

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>に入れたくなったらマニュアル見てね。

webpack.config.babel.js
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',
    }),
  ],

出力サンプル

public/index.html
<!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]つけない。

webpack.config.js
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',
+    }),
  ]
webpack.config.prod.js
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も吐き出されるので、なにかと連携したければそれを読み込むといいかも。

src/index.html
<!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

webpack.config.babel.js
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

設定

src/index.jsx
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'));

所管

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

26
27
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
26
27

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?