Humanity

Edit the world by your favorite way

React 入門以前 (Windows で環境構築)

犬さんにいろいろ教えてもらった。

というわけで

  • ES6 (ES2015)
  • React

で入門する。

進捗した結果 (リポジトリ)

環境構築メインで中身のコードほぼ書いてないので進捗ダメです。

github.com

調べたこと

React

旧石器時代JavaScriptを書いてる各位に告ぐ、現代的なJavaScript超入門

npm

npm で依存もタスクも一元化する - Qiita

npm run-scripts からは gulp のタスクを実行するだけ。

目に付いたメリットは

  • Windows でも動く
  • タスクが gulpfile.js に集約する

Nodist

複数バージョンの Node の運用には *nix 環境なら n とか nvm とか nave とか nodebrew とか色々あるみたいだけど、 不自由な Windows なので nodist というのを使う。

ちなみになぜ複数バージョン動かす必要が出たかというと、gulp のタスクを動かした時に以下のエラーが出たため。

(node:61377) fs: re-evaluating native module sources is not supported. If you are using the graceful-fs module, please update it to a more recent version.

fs: re-evaluating native module sources is not supported. If you are using the graceful-fs module, please update it to a more recent version. · Issue #291 · sequelize/cli · GitHub

その解決方法が以下の通り。まだ Node v6 をサポートしていないパッケージが沢山あるので古い Node v5 を使えとの有り難いアドバイス。

node v6 is still not supported in many packages. Consider going back to v5.

ちなみに Nodist 調べたので一応ここに書いたけど、結局複数バージョン入れるのもだるかったので、 おとなしく公式サイトから現時点の LTS (v4.4.6) をダウンロードして入れました (既存の v6 はアンインストールした)。

gulpfile.js

Browserifyの運用 〜 bundleを分ける - Qiita

全部をbundleすると太る

なるほど~

gulp で browserify - 新しい日記

最初これにしようかと思ったが、browserify().bundle() すると1つのJSファイルになってしまう。

Gulp+Browserifyでsrc/**/*.jsをdist/**/*.jsにする | 高橋文樹.com

この記事では1つのJSファイルになってしまう問題を解決するgulpの設定が書かれている。

…と思ったけど今実行したら下記の記事のエラーが出る。

TypeError: dest.write is not a function

Browserify + gulpではまったのでメモ - Qiita

結局上記記事に書かれてる through2 というパッケージを使う方法で解決…と思ったらまたエラー。

Error: File.contents can only be a Buffer, a Stream, or null.

該当箇所の変数を console.log() で出力したりしてみると、そもそも react パッケージとか babelify も入ってなくて Unexpected token 吐かれてた。

(JSX が有効になってない。結論から言うと .babelrc というものが必要)

$ npm run build

> hello-react@1.0.0 build C:\msys64\home\tyru\git\hello-react
> gulp build

[02:51:46] Using gulpfile C:\msys64\home\tyru\git\hello-react\gulpfile.js
[02:51:46] Starting 'build'...
[02:51:46] Finished 'build' after 15 ms
Error : C:/msys64/home/tyru/git/hello-react/src/app.jsx: Unexpected token (6:6) while parsing file: C:\msys64\home\tyru\git\hello-react\src\app.jsx
SyntaxError: C:/msys64/home/tyru/git/hello-react/src/app.jsx: Unexpected token (6:6)
  4 |   render: function() {
  5 |     return (
> 6 |       <div className="container">Hello {this.props.name}</div>
    |       ^
  7 |     );
  8 |   }
  9 | })
    at Parser.pp.raise (C:\msys64\home\tyru\git\hello-react\node_modules\babelify\node_modules\babel-core\node_modules\babylon\lib\parser\location.js:22:13)
    at Parser.pp.unexpected (C:\msys64\home\tyru\git\hello-react\node_modules\babelify\node_modules\babel-core\node_modules\babylon\lib\parser\util.js:89:8)
    at Parser.pp.parseExprAtom (C:\msys64\home\tyru\git\hello-react\node_modules\babelify\node_modules\babel-core\node_modules\babylon\lib\parser\expression.js:522:12)
    at Parser.pp.parseExprSubscripts (C:\msys64\home\tyru\git\hello-react\node_modules\babelify\node_modules\babel-core\node_modules\babylon\lib\parser\expression.js:277:19)
    at Parser.pp.parseMaybeUnary (C:\msys64\home\tyru\git\hello-react\node_modules\babelify\node_modules\babel-core\node_modules\babylon\lib\parser\expression.js:257:19)
    at Parser.pp.parseExprOps (C:\msys64\home\tyru\git\hello-react\node_modules\babelify\node_modules\babel-core\node_modules\babylon\lib\parser\expression.js:188:19)
    at Parser.pp.parseMaybeConditional (C:\msys64\home\tyru\git\hello-react\node_modules\babelify\node_modules\babel-core\node_modules\babylon\lib\parser\expression.js:165:19)
    at Parser.pp.parseMaybeAssign (C:\msys64\home\tyru\git\hello-react\node_modules\babelify\node_modules\babel-core\node_modules\babylon\lib\parser\expression.js:128:19)
    at Parser.pp.parseParenAndDistinguishExpression (C:\msys64\home\tyru\git\hello-react\node_modules\babelify\node_modules\babel-core\node_modules\babylon\lib\parser\expression.js:596:26)
    at Parser.pp.parseExprAtom (C:\msys64\home\tyru\git\hello-react\node_modules\babelify\node_modules\babel-core\node_modules\babylon\lib\parser\expression.js:481:19)

ちなみにこうしたら上のような詳細なエラーを吐いてくれるようになった。

.on("error", function (err) {
  console.log("Error : " + err.message);
  console.log(err.stack);
})

Unexpected token while parsing file - kubotti’s memo

によると次のパッケージを入れるといいらしい (勘)。

  • babel-preset-es2015
  • babel-preset-react
  • react
  • react-dom

今からはじめるReact.js〜React ver0.14〜 - Qiita

最新だと React の API が変わってるらしい。

0.14でReactDOMがReactから分離されました。 そのため、render()やfindDOMNode()はReactDOMのメソッドを使用しなければなりません。

//React.renderをReactDOM.renderに変更。
ReactDOM.render(
  <Index />,
  document.getElementById('content')
);

.babelrc

{
  "presets": ["es2015", "react"]
}

Browserify エラーを出す - Qiita

package.json

{
  "name": "hello-react",
  "version": "1.0.0",
  "description": "",
  "main": "dist/app.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "gulp build"
  },
  "author": "tyru <tyru.exe@gmail.com>",
  "license": "ISC",
  "devDependencies": {
    "babel-preset-es2015": "^6.9.0",
    "babel-preset-react": "^6.5.0",
    "babelify": "^7.3.0",
    "browserify": "^13.0.1",
    "gulp": "^3.9.1",
    "react": "^15.1.0",
    "react-dom": "^15.1.0",
    "vinyl-source-stream": "^1.1.0"
  }
}

gulpfile.js

これだと結局

全部をbundleすると太る

問題が解決してないのですが力尽きました。 gulp 職人の方誰か助けてください。

var gulp = require('gulp');
var browserify = require('browserify');
var babelify = require('babelify');
var source = require('vinyl-source-stream');

gulp.task('build', function() {
  browserify('./src/app.jsx', { debug: true })
    .transform(babelify)
    .bundle()
    .on("error", function (err) {
      console.log("Error : " + err.message);
      console.log(err.stack);
    })
    .pipe(source('bundle.js'))
    .pipe(gulp.dest('./dist'))
});

今度は bundle.js を読み込むタイミングが早すぎてエラーが出た。 Angular でもある例のやつ。jQuery だったら $.ready() で処理すべきなやつ。

うっかり <head> タグに <script> タグで bundle.js を読み込んでしまったんだけど、その際 React では以下のようなエラーが出る。

Uncaught Invariant Violation: _registerComponent(...): Target container is not a DOM element.

とうとう上記のエラーも解消して、肝心の HTML と JS は以下の通り。

index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Hello React</title>
  </head>
  <body>
    <div id='app'></div>
  </body>
  <script type="text/javascript" src='dist/bundle.js'></script>
</html>

src/app.jsx

const React = require('react');
const ReactDOM = require('react-dom');

const Hello = React.createClass({
  render: function() {
    return (
      <div className="container">Hello {this.props.name}</div>
    );
  }
})

ReactDOM.render(<Hello name="React" />, document.getElementById("app"));

フロントエンド周りって慣れないと環境構築にほんと時間かかるな…

gulpプラグインの作り方 | js | Horic Design

through2というパッケージは、gulpのstreamまわりをサポートしてくれるプラグインです。

vinyl-source-stream やら through2 やら本来やりたいことと関係ないプラグインがいっぱい出てくるので初心者にはつらい… と思ってたらそこら辺の事情が以下の記事にまとまっていた (ブクマしとるやんけ)。

gulp と browserify と vinyl の話 - <body>


[gulp] 効率的にプラグインを読み込む - Qiita

gulp で利用するプラグインをひとつひとつ読み込むと大量の require が並ぶことになる。gulp-load-plugins を利用すると package.json から自動で読み込み利用できるようになり、require する必要がなくなる。

Flowtype

犬さんに教えてもらったのですが力尽きました。

追記Windows じゃ動きませんでした。つらい。

JavaScript でスマホから気軽に遊べる糸通しゲーム作った

糸通し

たまにものすごくやりたくなる。 ちょうど HTML5 Fullscreen API で遊びたいと思ってたので作った。

最初データはランダムじゃないけど一通り動くところまで作ったあと一旦興味を失ってしまった。 ので、そこからちゃんと遊べる状態にするまでずいぶん間が開いてしまった。

ただこれで最近中途半端に進めてたことは一旦片付けたのでスッキリした(仕事の方はスッキリしてない。忙しい時ほど寝る時間を削って趣味のコードを書いてしまう法則)。

ほしかった機能

  • スマホから遊べること
    • スマホの場合、「タッチしっぱなし・離す」で操作できること
  • PC から遊べること
    • PC の場合、「クリック押しっぱなし・離す」で操作できること
  • ゲームのステージ部分のみをフルスクリーンで拡大できること

TODO & FIXME


  • これは直さなくていいや


実装中に調べたこと

本文ここまで。以下は調べたことです (自分用メモ)。

HTML5 Fullscreen API

これ知ってゲーム作りたくなった。

SVG

D3.js

しかし、D3はデータを基に座標系に変換するなどの計算は行ってくれますが、実際に図を描画する機能はありません。 (中略) 実際にツリーマップを描画する処理は製作者が自身でコードに記述する必要があります。 これは一見不親切なように思えますが、Chartライブラリが提供するグラフのテンプレートデザインをカスタマイズするだけでは足りず、自ら凝ったデータビジュアライゼーションを作成したい製作者にとっては描画する方法を自由に選択できる利点となります。

React や Angular みたいにテンプレート&コンポーネント指向に作れないのはちょっと面倒だと感じた… んだけど次の記事を読んで気が変わった。D3.js 便利。

SVG, Canvas, WebGL の使い分け

requestAnimationFrame

Protractor + WebDriver で楽天銀行と Gmail にログインして入出金明細を保存

motemen.hatenablog.com

上記のリポジトリに Pull Request を出させてもらった。それがこちら。

github.com

  1. (Gmail等に) IMAP でログインしてメールを監視
  2. 楽天銀行にログインしてワンタイムパスワードのメールを送信
  3. ワンタイムパスワードが書かれたメールが来るまで browser.wait() + protractor.promise.defer() *1 で待つ
  4. ワンタイムパスワードでログインして入出金明細を TSV ファイルとして保存 *2

みたいなことしてます。

正直 Protractor のデバッグが一番大変だった。 変な場所で expect() してて全然関係ないテストでコケたりしてた。 デバッグは正直勘で進めてしまったのでよくない。 ちゃんと詳細なログとか取れる方法があるかもっと調べればよかった。

Windows 対応

ついでに既存の npm run-scripts の Windows 対応もした。

github.com

本文終わり。以下は調べたことです。

実装中に調べたこと

参考にしたページ

iconv を Windows でビルドするための環境構築

この2つのサイトがとっかかりとしてはとても助かったんだけど、一つまずい箇所がある。 それは {stream}.on('data', ...) に来たバッファから文字列を検索してるけど、dataの時に渡されるデータは細切れなので、 iconv の変換時に不正なバイトとしてエラーになる可能性がある (なった)。 あと潜在的に検索文字列の途中で切れてたら検索に引っかからない。 なのでこのようにした。

      stream.on("data", function(chunk) {
        body += chunk;
      });
      stream.on("end", function() {
        body = converter.convert(body).toString();
        // FIXME: body にはヘッダ部も含まれているため RFC822 に則ってちゃんとパースする?
        if (/ワンタイムキー[  ]*[::][  ]*([a-zA-Z0-9]+)/.test(body)) {
          var otKey = RegExp.$1;
          console.log('ワンタイムキーを本文から取得成功:' + otKey);
          deferred.fulfill(otKey);
        } else {
          console.log('ワンタイムキーを本文から取得失敗');
          deferred.reject();
        }
      });

以下は init.cmd の Node 関連の内容。

rem # Node
set PATH=C:\Program Files\nodejs;%USERPROFILE%\AppData\Roaming\npm;%PATH%

rem # ================ Prerequisites for Node ================
rem # https://github.com/Microsoft/nodejs-guidelines/blob/master/windows-environment.md

rem # Visual Studio
rem # set PATH=C:\Program Files\Microsoft SQL Server\110\Tools\Binn\;%PATH%
set PATH=C:\Program Files (x86)\Windows Kits\8.1\Windows Performance Toolkit\;%USERPROFILE%\.dnx\bin;C:\Program Files\Microsoft DNX\Dnvm\;C:\Program Files\Microsoft SQL Server\130\Tools\Binn\;%PATH%

rem # Python 2.7
set PATH=C:\Python27;C:\Python27\Scripts;%PATH%

rem # ================ Prerequisites for Node ================

browser.wait()

If your page does manual bootstrap Protractor will not be able to load your page using browser.get. Instead, use the base webdriver instance - browser.driver.get. This means that Protractor does not know when your page is fully loaded, and you may need to add a wait statement to make sure your tests avoid race conditions.

(2段階認証を使っている場合) アプリパスワードを生成

2段階認証を使っている場合はパスワードをそのまま入力したのではログインできない。 アプリパスワードというアプリ固有のパスワードを発行する必要がある。

node.js - Authentication issue with node imap and node mail lister - Stack Overflow

https://support.google.com/accounts/answer/6010255

この設定は、2 段階認証プロセスが有効になっているアカウントでは利用できません。こうしたアカウントで安全性の低いアプリにアクセスするには、アプリケーション固有のパスワードが必要です。 ヘルプ

https://www.google.com/settings/security/lesssecureapps

発行されたパスワードをそのままアカウントのパスワードとして渡せば認証される。

*1:最初 new Promise() 使ってたけど Protractor で用意されてるこっち使った方が良さそうだったのでこっち使った

*2:ここらへんは元のサンプルコードに則らせてもらったというかコピペさせてもらった