exchatのfrontendをやってみる

導入

昨日、tony612/exchatを扱いましたが、今回はexchatのfrontendの内容になります。

heorkuでは、通常、/app, /webにわけられdeployされます。

なので、phxのようなbackendの成否とreactのようなfrontendの成否は基本的に別々になっています。

つまり、phxのdeployは正常に成功したけど、webpackが失敗して、/webがうまく表示できないなんてこともありえます。

exchatは、backendをelixir(phoenix)で構成し、frontendは、react, reduxなどで構成しているようです。

ここで、frontend(js)のpreview, build, convertなどには、webpack, babelなどを使用しているようです。

今回は、exchatのfrontendの方を少しだけ見ていこうかなと思います。

webpack

buildされたものは、./priv/static/app.jsに置かれるようですね。しかし、.gitignoreに書かれていますので、pushしません。

$ cat webpack.config.js

$ mix deps.get

$ npm i

$ webpack

$ cat ./priv/static/app.js

$ mix phx.server

layoutを変えるには、./web/templates/layoutからhtml:headより上をいじることもできますが、基本的には、./client/以下を触ります。

./config/dev.exsを見ることで、どのようにpreviewされるのかわかります。

riotを触ってた頃、たまたまwebpackも使ってた気がするんですけど、もう忘れてる。

なお、cloudflareとかを利用してる場合、cacheなどが効いてるので、今回生成されるようなapp.jsみたいな一箇所に詰め込まれたファイルというのは、特に、なかなか反映されないことがあります。これは、purgeをするとか、もしくはherokuapp.comで確認するとかしたほうがいいわけですが、それでも反映するまでにしばらく時間がかかることが多いです。browserのprivate windowを利用しても同じ。frontendはこのあたり、大変ですね。あくまで初見の印象ですが。

previewは、$ bash ./compileしたあとに、$ mix phx.serverします。localhost:4000

You are currently using minified code outside of NODE_ENV === ‘production’. This means that you are running a slower development build of Redux. You can use loose-envify (https://github.com/zertosh/loose-envify) for browserify or DefinePlugin for webpack (http://stackoverflow.com/questions/30030031) to ensure you have the correct code for your production build.

react

Warning: It looks like you’re using a minified copy of the development build of React. When deploying React apps to production, make sure to use the production build which skips development warnings and is faster. See https://fb.me/react-minification for more details.

$ cat ./compile 
info "Building Phoenix static assets"
NODE_ENV=production webpack -p
mix phoenix.digest

$ bash ./compile 

どうやら、buildは、NODE_ENV=production webpack -pすればいいらしいですね。

heroku

./clientを書き換えて、heroku pushしても、全く変更が反映されずにおかしいなと思っていましたが、heroku cacheが効いていたのかもしれません。

./priv/static/js/app.js./client/js/app.jsに置き換えることで何故か対応できましたが、errorが色々出るので。

$ heroku plugins:install heroku-repo
$ heroku repo:purge_cache

update

色々なパッケージをupdateしていきます。

$ npm list --depth=0 | grep webpack

# package.json update
$ npm install -g npm-check-updates
$ ncu
$ ncu -u

$ npm update

webpackを1 -> 4にしたので、だいぶ手こずりました。

一応、成果物を上げておきます。

追記 : 動くようになりました。react-router@2.xを指定すると動くのですが、3.x ~ 4.xでは正常に動きません。多分、versionの書き方が必要なのでしょう。特に、prop-typesあたりのコードです。ただし、react 16.xにupdateすると、react-router 2.xには_react.PropTypeで書かれた箇所があります。node_modules/react-router/lib/PropType.jsなのですが、現在のsrcでreactをupdateしたい場合は、それを書き換えるしかないですね。

forkしてgithubに上げると、こんな感じで取ってこれます。ただし、webpackで反映されないんで、一旦、npm iしたあとに、再度、本家のversionを指定して、npm iすると、なぜかforkしたlibが読み込まれました。

package.json

"react-router" : "git:https://github.com/syui/react-router#v2.8.2"

追記終わり。

では、updateした設定ファイルを見ていきます。

.babelrc

{
  "presets": ["react", "es2015", "stage-2"],
  "plugins": [
    "transform-function-bind"
  ]
}

package.json

{
  "name": "exchat",
  "version": "2.0.0",
  "description": "phx chat",
  "main": "index.js",
  "directories": {
    "test": "test"
  },
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "syui",
  "license": "MIT",
  "repository": {},
  "devDependencies": {
    "babel-core": "^6.26.3",
    "babel-loader": "^7.0.0",
    "babel-plugin-transform-function-bind": "^6.22.0",
    "babel-preset-es2015": "^6.5.0",
    "babel-preset-react": "^6.5.0",
    "babel-preset-stage-2": "^6.5.0",
    "webpack": "^4.17.1",
    "webpack-cli": "^3.1.0"
  },
  "dependencies": {
    "css-loader": "^1.0.0",
    "sass-loader": "^7.1.0",
    "resolve-url-loader": "^2.3.0",
    "style-loader": "^0.23.0",
    "url-loader": "^1.1.1",
    "file-loader": "^2.0.0",
    "bootstrap-sass": "^3.3.6",
    "gravatar": "^1.5.2",
    "history": "^4.7.2",
    "humps": "^2.0.1",
    "isomorphic-fetch": "^2.2.1",
    "jquery": "^3.3.1",
    "jwt-decode": "^2.0.1",
    "lodash": "^4.11.2",
    "normalizr": "^2.3.0",
    "phoenix": "file:./deps/phoenix",
    "phoenix_html": "file:./deps/phoenix_html",
    "prop-types": "^15.6.2",
    "react-bootstrap": "^0.32.3",
    "react-select": "^2.0.0",
    "react-router-redux": "^4.0.8",
    "redux": "^4.0.0",
    "redux-thunk": "^2.0.1",
    "redux-logger": "^3.0.6",
    "react-redux": "^5.0.7",
    "react-router": "^2.8.1",
    "react": "^15.5",
    "react-dom": "^15.5"
  }
}

webpack.config.js

var path = require('path');
var webpack = require('webpack');
var config = {
  mode: 'production',
  entry: [
    path.resolve(__dirname, './client/js/app.js')
  ],
  output: {
    path: path.resolve(__dirname, './priv/static/js'),
    filename: 'app.js',
    publicPath: '/',
  },
  module: {
    rules: [
      {
	test: /\.jsx?$/,
	use: {
	  loader : 'babel-loader',
	}
      },
      {
	test: /\.woff(\?v=\d+\.\d+\.\d+)?$/,
	use: [{
	  loader: 'file-loader',
	  options: {
	    name: '[name].[ext]',
	    mimetype: 'application/font-woff'
	  }
	}]
      },
      {
	test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/,
	use: [{
	  loader: 'file-loader',
	  options: {
	    name: '[name].[ext]',
	    mimetype: 'application/font-woff'
	  }
	}]
      },
      {
	test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/,    
	use: [{
	  loader: 'file-loader',
	  options: {
	    name: '[name].[ext]',
	    mimetype: 'application/octet-stream'
	  }
	}]
      },
      {
	test: /\.eot(\?v=\d+\.\d+\.\d+)?$/,    
	use: 'file-loader'
      },
      {
	test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,    
	use: [{
	  loader: 'file-loader',
	  options: {
	    name: '[name].[ext]',
	    limit: '10000',
	    mimetype: 'image/svg+xml'
	  }
	}]
      },
      {
	test: /\.scss$/, 
	loader: 'style-loader!css-loader!sass-loader'
      },
      {
	test: /\.css$/, 
	loader: 'style-loader!css-loader'
      }
    ]
  },

  devServer: {
    contentBase: path.resolve(__dirname, './client'),
    inline: true,
    open: true
  },

  resolve: {
    extensions: ['.js', '.jsx', '.scss', '.css']
  },
  plugins: [
    new webpack.ProvidePlugin({
      $: 'jquery',
      jQuery: 'jquery',
      'window.jQuery': 'jquery'
    })
  ]
}

module.exports = config
$ mix deps.get
$ npm i

$ cat ./compile
./node_modules/webpack-cli/bin/cli.js -p

$ bash ./compile

$ mix phx.server

あとは、以下の箇所のコードを修正しなければいけません。

react-select

Module not found: Error: Can’t resolve ‘react-select/dist/react-select.css’

jsで読み込まれるので最新では必要なくなったらしいです。

./client/js/app.js

- import 'react-select/dist/react-select.css'
$ mix deps.update postgrex
$ bash ./compile
$ mix phx.server

prop-types

React.PropTypes.func : Uncaught ReferenceError: Component is not defined

https://github.com/brigand/babel-plugin-flow-react-proptypes/issues/187

- import React, { PropTypes, Component } from 'react'
+ import React, { Component } from 'react'
+ import PropTypes from "prop-types";

Settings.propTypes = {
  - dispatch: React.PropTypes.func
  + dispatch: PropTypes.func
}

こんな感じで直していけばいいです。ファイルは、grep -R PropType . | sort | uniqとかしましょう。

react-codemodを使って修正できたりもするようです。しかし、今回のsrcは、skipでした。

Warning: Accessing PropTypes via the main React package is deprecated, and will be removed in React v16.0. Use the latest available v15.x prop-types package from npm instead. For info on usage, compatibility, migration and more, see https://fb.me/prop-types-docs

$ npm install -g jscodeshift
$ git clone https://github.com/reactjs/react-codemod.git
$ echo `pwd`/react-codemod/transforms/React-PropTypes-to-prop-types.js | pbcopy
$ find ./client/js -name "*.js" | xargs jscodeshift -t "`pbpaste`"
28 ok
Time elapsed: 0.000seconds

# skipされるなら以下のコマンド
$ find ./client/js -name "*.js" | xargs jscodeshift --extensions jsx -t "/Users/syui/git/exchat/react-codemod/transforms/React-PropTypes-to-prop-types.js"

$ npm i react react-dom prop-types

normalizr

Uncaught TypeError: normalizr.Schema is not a constructor

これはバージョン下げた。

redux

Uncaught TypeError: (0 , reduxLogger.createLogger) is not a function

redux-logger : ^2.x -> ^3.x

- import  createLogger from 'redux-logger'
+ import { createLogger } from 'redux-logger'

TypeError: Cannot read property ‘listen’ of undefined

unsubscribeFromHistory = history.listen(handleLocationChange);

"react-router": "^3.0.5",
"react-router-redux": "^4.0.8",

ただし、react-router@2.xでないとnpm i -S prop-typesに対応したcodeはうまく動作しない。

react-dom, redux-logger

Uncaught RangeError: Maximum call stack size exceeded

https://github.com/erikras/redux-form/issues/2629

webpack

webpackのversionをいろいろいじってると、config/dev.exsやcompileのwebpackを./node_modules/webpack/bin/webpack.jsから./node_modules/webpack-cli/bin/cli.jsなどに変更しなければならないことがあります。もちろん、環境変数でやってもいいですが。

感想

node packageのupdateとかやりましたが、webpackのbuildするのは成功しましたが、やはり./client以下のreactで書かれたcodeが古いので、そちらを書き換えないと表示されない感じでした。残念です。特に、prop-typesあたりがめんどくさそうでした。

追記 : いけました。ただし、react-router@2.xでなければならないのと、react@15.x -> react@16.xにしたい場合は、react-router@2.xのlib/PropTypesを書き換えなければならない感じでした。