読者です 読者をやめる 読者になる 読者になる

やはり俺の AUTO INCREMENT を含むテーブルに対するリソースに PUT を使うのはまちがっている、または HTTP PUT の冪等性と AUTO INCREMENT の相性が最悪な件について (あるいは私が PUT を諦め POST を使うまで)

3行で伝える代わりにタイトルで説明してみた (親切心)。

悩み

DB の AUTO INCREMENT な値 (以下「連番」) をメッセージボディの JSON に含め、 そのリクエストを受け付けたバックエンドが DB の連番と比較し、 違っていればエラーを返すという設計の API があるとして、 PUT であるリソースを更新すると当然連番がインクリメントされるので連続で送ることができない。 つまり冪等性を保っておらず RESTful ではない。 では RESTful に実現しようとするなら一体どうすればいいんだろう?というので最近悩んでいた。

多分 REST を諦めるか設計か仕様を変えない限りどうしようもないと思うので、 「連番を渡して違ったらエラーにする」という設計で「本当は何がしたいのか?」から考えてみる。

何がしたいの?

「何がしたいの?」はぶっちゃけ単純で 「(フロントエンド等の) クライアントで持っているリソースが古くなっていた (=別の誰かがすでに更新していた) らエラーにしたい」 というもの。 つまり仕様としてリソースのインスタンスが1つであることを強制している (=1つのリソースを複数のユーザが更新する可能性がある)。 ではこれを実現するために REST の原則を崩さずに PUT してリソースに書き込む*1 ためにはどうすればいいか?

RESTful な PUT

まず DB の AUTO INCREMENT の値をクライアントから受け付ける設計を考えたけど、これは間違っている気がする。 というのは、受け付けたデータの連番はそのままリソースのデータとして書き込まれるのではなく、インクリメントされてから実際の DB に書き込まれる。 なのでクライアントから見れば手元のリソースのデータ + 副作用が加えられて書き込まれることになる (手元のリソースのデータと差異が生じる)。

連番なり更新日などは DB が管理するためのメタデータなので、 それをクライアントに送信させることを強制する設計がおかしい。 ではこの「管理」とは何で、何のためのものなのか?というと、 冒頭で挙げた以下の仕様を満たすためです。

「(フロントエンド等の) クライアントで持っているリソースが古くなっていた(=別の誰かがすでに更新していた)らエラーにしたい」

では WebSocket で常に最新版をプッシュすれば皆がハッピーなのか?というと リソースの項目を編集中に「リソースが更新されました」っつってページがリロードされたら確実にキレる。 ではどうすればいいのか。

答え:POST にする

ズコー (AA略)

これでいいのか?とは思うけど、自分としてはこれぐらいしか思いつかない… けど URL 設計を考えている内にこれでいいんでは?という気になってきた。

例えば以下のようなエンドポイントがあるとする。

/tweets/{ID}

すると

POST /tweets/1234?seq=1

ID=1234 のリソースを連番 1 で登録して、(連番が同じで) 成功すれば /tweets/1234/1 というリソースが作られる。 もちろんこの URL の末尾の 1 は連番。 (ちなみに連番をメッセージボディで渡すとブログで書く都合上面倒なので、URL のクエリパラメータで連番を渡すことにしている)

つまりアイデアとしては 「いっそ連番はリソースを示す ID と同じようなものと考えて URL に含めてしまう」 というもの。 ただ仕様による制限故、普通のリソース(?)とはいくつか違う点がある。

  1. /tweets/1234/1 への PUT はない (POST で登録する)
  2. 最新版は /tweets/1234/latest で取得
    • これは別に/tweets/1234 で最新版が取得できてもいいかもしれない
  3. 最新版の連番が 2 の時、GET /tweets/1234/1 は 404*2 を返す

つまり API で言うと以下の3つ (実質2つ) のみ提供する。

  1. POST /tweets/{ID}
  2. GET /tweets/{ID}/latest or GET /tweets/{ID}
  3. GET /tweets/{ID}/{seq}
    • これを使う意義は無いような…

(追記) 答え その2:ETag を使う

KoRoN さんから教えてもらった ETag を使うと条件付きで更新することができます。 こちらの方が正道っぽいですね。

Azure API は ETag 使ってるみたいです (kamichidu さんありがとうございます)。

ETag での条件付き更新が失敗になった場合のエラーコードは 409 Conflict が適切だそうです。

あと 412 Precondition Failed というのもあるようです。

以上

こんな感じでどうでしょうか? とりあえず少なくとも REST の砦は守れたのでよかった (小並)。

冒頭に挙げたような複数人が同じリソースを更新するケースはよくありそうなので、 ぜひ自分ならこうするよ(してるよ)って方いたら反応くれるとうれしいです。


という結論を出した後で同じ結論に至った人を発見。

I'd suggest that you use POST, not PUT, for an auto-increment key, or do not use the auto-increment key in the resource ID.

http://stackoverflow.com/questions/9881085/rest-how-would-a-put-request-handle-auto-incremented-resource-identifiers

こちらも参考リンク。

http - PUT vs POST in REST - Stack Overflow

*1:ここでは PUT としていますが仕様によっては DELETE も該当しますね

*2:もっと他に良いエラーコードあるかな?

CSS3 アニメーションで猿 (2)

昨夜ちょっと CSS いじったせいでまたサルを動かしたくなった。

という訳で前回に引き続き、ブログ上部にある怪しげな Vim アイコンをクリックするとサルが動き出すようになります。

注意:もう一回動かしたい時は2回クリックしてください。

昨日夜遅くまでこれをやってる最中は一人で爆笑してたけど、今はもうちょっと有意義な休日の使い方ができなかったのかと悔やんでも悔やみきれない。

最近読んだ漫画 (と CSS で感想の表示・非表示を切り替えてみるテスト)

あまり人に漫画を勧める時に感想って言いたくない。 けど自分はしょっちゅうブログのデザインを変えるので、単純に背景色と同じにするだけだとテーマを変えた場合に隠したはずの文章が見えてしまう。 それはあまりにもあんまりなので、CSS で感想の表示・非表示を切り替えられるようにしてみた。

案1:シンプルにチェックボックスで切替え

チェックボックスをオンにしてみてください。

はてなブログの場合、設定画面の「デザイン」→「デザインCSS」から追加の CSS を定義できるのでそこに以下の記述を追加。 あと Markdown じゃないと HTML は書けないかも (たぶん)。

label.toggle-opacity > span {
  opacity: 0;
}
label.toggle-opacity > input[type=checkbox]:checked + span {
  opacity: 1;
}

でブログ本文に書くときはこう。

<label class='toggle-opacity'><input type='checkbox'><span>テスト</span></label>

ちょっと書くのが面倒だけどそう使わないだろうし、コピペするなり辞書登録するなりしてしまえばいい。

案2:<details> + <summary>

…でさらにちょっとリッチなボタン形式で表示させてみるかーといろいろ HTML5 のタグを調べてたら、 こういう場合(?)に便利な <details> タグ + <summary> タグというものを発見。こう使う。

<details>
<summary>(感想は省略されました。読むにはここをクリックしてください)</summary>
主人公がかっこよかった(小並)
</details>

で、こうなる。

(感想は省略されました。読むにはここをクリックしてください) 主人公がかっこよかった(小並)

HTML5 のタグなので CSS は一切使ってない。 感想部分の文章にインデントを加えたい場合とかは CSS でごにょごにょすればいいだろうけど、自分はもうこれでいいや。

案3:リッチなボタン風

…と思ってたんだけど、自分が考えてた「リッチなボタン形式」のデザイン案もそれなりに実現してみたかったのでちょっとやってみた (完全にやりたいだけ)。 正直デザインより機能性を求めてしまう自分はもう <summary> タグでいいやって気分になってたけど、 せっかく作ったのでこの記事で使ってみたら結構いい感じだったので良かった。

案としてはこんな感じ。

  • チェックボックスじゃなく代わりに自由なテキストを表示させる
    • テキストは表示時/非表示時で切り替えられる
  • ちょっとリッチなボタン形式で表示

そのデモがこちら。

HTML はこう (長い)。

<label class='toggle-opacity-text'>
  <input type='checkbox'>
  <span class='tot-enable'>感想を読むにはクリック</span>
  <span class='tot-disable'>感想を非表示にする</span>
  <span class='tot-body'>ねーねー本の感想は?</span>
</label>

CSS はこう (長い)。 クラス名はさっきのと変えてます。

label.toggle-opacity-text > .tot-disable,
label.toggle-opacity-text > .tot-body,
label.toggle-opacity-text > input[type=checkbox] {
    display: none;
}

label.toggle-opacity-text > input[type=checkbox]:checked ~ .tot-enable {
    display: none;
}
label.toggle-opacity-text > input[type=checkbox]:checked ~ .tot-disable {
    display: inline;
}
label.toggle-opacity-text > input[type=checkbox]:checked ~ .tot-body {
    display: block;
}

/* ちょっとリッチなボタン風 */
label.toggle-opacity-text > .tot-enable,
label.toggle-opacity-text > .tot-disable {
    color: #fff;
    font-weight: bold;
    background-color: aqua;
    padding: 5px 10px;
    border-radius: 1em;
}

/* ついでに枠もつけてみた */
label.toggle-opacity-text {
    padding: 10px;
    display: inline-block;
    border: 1px solid white;
}

  • (2016/07/18 23:42 追記) 「ボタンのテキストの選択を無効」を追加。
  • (2016/07/19 23:15 追記) スマホで感想が丸見えになっていたのでブログをレスポンシブ化した。
  • (2016/07/19 23:23 追記) Android Chrome でテキスト部分のタップが認識されなくなっていたので、やはり「ボタンのテキストの選択を無効」を削除。削除したコードは以下の通り。
/* ボタンのテキストの選択を無効 */
label.toggle-opacity-text > .tot-disable,
label.toggle-opacity-text > .tot-enable {
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
}

結局 HTML が <span> タグの嵐になってしまった。 さすがにブログの記事で気軽に使えない *1 ので、 それを避けるために適切な HTML タグを探していて偶然 <summary> タグに行き当たった訳だけど、 目的は叶えられたのでとりあえずこれはこれでよしとする。

漫画の感想

という訳でおまけみたいな感じになりつつある漫画の紹介。横道ばかりの人生。

感想だけだと非表示にしてしまったらジャンルも分からなくなるので、 せめてジャンルだけ1行で紹介するようにしてみようかなーと思ったけど、 ジャンルというくくりも微妙にしっくり来ない… ジャンルで興ざめされたり敬遠されてしまうのは悲しいし、 個人的に作品に感じるジャンルやテーマは人それぞれだと思うので、いっそジャンルも隠すようにしてみた。

あとジャンルも各成分の % で表記するようにしたので、「それは違う」ってジャンルが含まれてるのもあるかも。 あと % って言っても適当なのであまり気にしないでください。

なのは洋菓子店のいい仕事

ジャンル ファンタジー(10%) 青春(20%) ラブ(25%) コメ(25%) 菓子(20%)

ねじの人々

ジャンル 青春(10%) ラブ(10%) コメ(30%) 哲学(50%)

いかづち遠く海が鳴る

いかづち遠く海が鳴る(1) (ビッグコミックス)

いかづち遠く海が鳴る(1) (ビッグコミックス)

ジャンル ファンタジー(70%) 恋愛(30%)

ノラガミ

Kindle 版が今0円だった。

ジャンル ファンタジー(40%) 恋愛(30%) 友情(20%)

キレる私をやめたい

ジャンル ノンフィクション(50%) メンタルヘルス(50%) ※ノンフィクション 50% は嘘という訳ではない

さびしすぎてレズ風俗に行きましたレポ

ジャンル ノンフィクション(40%) メンタルヘルス(30%) 性(30%) ※ノンフィクション 40% は嘘という訳では(ry

僕が私になるために

僕が私になるために (モーニングコミックス)

僕が私になるために (モーニングコミックス)

ジャンル ノンフィクション(50%) 性(50%)

MAJOR 2nd

ジャンル 青春(50%) 野球(50%)

週刊少年ガール

週刊少年ガール(1) (週刊少年マガジンコミックス)

週刊少年ガール(1) (週刊少年マガジンコミックス)

ジャンル ファンタジー(34%) 青春(33%) 恋愛(33%)

スペシャ

ジャンル ギャグ(50%) 青春(50%)

彼とカレット。

彼とカレット。<彼とカレット。> (―)

彼とカレット。<彼とカレット。> (―)

ジャンル ギャグ(40%) エロ(ネタ)(30%) かわいい(30%)

ぱら☆いぞ

ジャンル エロ(100%) 頭おかしい(100%) 天才(100%)

AIの遺電子

ジャンル SF(50%) 未来(25%) 友情とか恋愛とか(25%)

木根さんの1人でキネマ

ジャンル ギャグ(40%) 映画(40%) 友情(10%) シェアルーム(10%)

服を着るならこんな風に

ジャンル ファッション(70%) 妹かわいい(20%) こんな妹いるか(10%)

*1:まぁ冒頭で辞書登録すれば良いとは自分でも言ってるけど

*2:たぶんと多分をかけています

*3:この表現が適切だったかどうかはまた読んでみないと分からない

setInterval() を requestAnimationFrame() に変えてもカクカクさせない方法

前提知識

まず requestAnimationFrame() は setInterval() と同じで定期的にコールバックを実行する API

requestAnimationFrame() を使うことによるメリットは以下の通り。

  • タブがバックグラウンドになった時に fps を落として実行される
  • ブラウザの描画更新単位と同じ単位で呼び出される

よって

  • 低メモリ消費
  • 省電力

しかし setInterval() で書いていた処理を requestAnimationFrame() に書き換えるに当たって一つ問題がある。 それは実行する間隔を指定できないこと。 setInterval() は第2引数でミリ秒でコールバックが呼ばれる間隔を指定できる。 しかし requestAnimationFrame() が受け付けるのはコールバックのみ。 ブラウザの描画更新単位と同じ単位で呼び出されるため効率の良い API だが、呼ばれる間隔はまちまちになってしまう問題がある (大体 60 fps と言われているが、バックグラウンドになった際にはもっと低 fps になるらしい)。

コールバックを実行するタイミング指定できない問題をどうにかする

自分は今回糸通しのゲームで requestAnimationFrame() 対応したかった。 このゲームはPCとスマホ両対応といいつつ自分が暇な時に触りたいので圧倒的にスマホの需要のほうが大きい。 しかし FPS を制御できないのはアクションゲームにとっては致命的。 だけど冒頭に挙げたメリットはスマホにとってうれしいことばかりだったのでそれなりに対応したい問題だった。

そこで検索したら以下の記事がヒットした。

requestAnimationFrame のタイミングにたよって値を変えるするというのではなく、経過時間を管理し再描画のタイミングで「経過時間に合わせたフレーム」を表示してやればよさそうです。

requestAnimationFrame でフレームと再描画更新を制御する

上記の記事では「経過時間に合わせたフレーム」を表示するために描画の対象を「コマ割り」し、フレームごとのコマを描画している。 ただ、自分の場合はゲームのメインループをカクつかせずに回す方法が知りたかった。 ゲームはユーザの入力が絡むシーンが大半なので単純にコマ割できず、 毎フレームごとにじりじり座標を更新していかなければならない場合が大半なのでどうすべきか考えた結果、 その方法が割と上手くいったので共有してみる。

解決策

やりたいことは以下のような感じです。

setInterval() を使うなどして FPS を固定させた場合のそれぞれのオブジェクトの移動量を100%とした時、 前回との経過時間から「実際の FPS」を計算し、「想定している FPS」と比較して割合を出します (70%とか120%とか)。 そしてその割合を移動量に掛けることで描画のタイミングがバラついても移動量は FPS 固定の時と同じ移動量になる(はず)なので、 カクつくことなく描画できます。

より分かりやすく実際のアルゴリズムで書いてみると以下の通りです。

  1. 1回の移動量に対する割合を計算する
  2. 描画した時の時間を覚えておく
  3. その計算量に応じてオブジェクトを移動させる

実際のコードはこんな感じです (色々省略してます)。

ito-to-shi/app.js at 65634edaa427a53649f8ac825e71c97b05c36097 · tyru/ito-to-shi · GitHub

  update() {
    // 1. 1回の移動量に対する割合を計算する
    // 注意:この関数では経過時間しか計算していないので嘘コメントです。
    // 実際はそれぞれのオブジェクトで経過時間から割合を計算しています。
    const now = Date.now();
    const elapsedMs = now - this._prevUpdatedTime;

    // 2. 描画した時の時間を覚えておく
    this._prevUpdatedTime = now;

    // 3. その計算量に応じてオブジェクトを移動させる
    // (計算量である elapsedMs をそれぞれの画面のオブジェクトに渡して座標を移動して描画してもらっている)
    screen.update(elapsedMs);
  }

で呼ばれた update() メソッドの冒頭で割合を出しています。

ito-to-shi/running.js at 65634edaa427a53649f8ac825e71c97b05c36097 · tyru/ito-to-shi · GitHub

  update(elapsedMs) {
    const movePercent = elapsedMs / constant.THE_FPS;

    // (省略)
  }

ちなみに 30 FPS の想定ですが、constant.THE_FPS = 1000.0 / 30.0 となっていて非常に紛らわしい変数名になってました… *1

なので上の処理は movePercent = 経過時間(ミリ秒) / 1フレームにかかるミリ秒 になります。 100 ms かかる想定が 200 ms かかったら 2.0 (=200%) です。 その場合は2フレーム分オブジェクトを移動させればいいですね。

失敗例

逆に失敗した方法は、想定している FPS 分の経過時間が経っていなかったら座表計算や描画をスキップする方法です。 これだとスキップした時には当然描画は行われないため、カクついてしまいます (この件で描画全体をスキップするのではなく毎回描画は行う方法でないとカクつくんじゃないかと思って冒頭の方法に変えた)。

ito-to-shi/app.js at f29fae827b02d238ca753ff20f60ad69cbfb3a6e · tyru/ito-to-shi · GitHub

  update() {
    // Skip if main loop was called too early.
    const now = Date.now();
    const stepFrames = Math.floor((now - this._prevUpdatedTime) / constant.THE_FPS);
    if (stepFrames > 0) {
      this._prevUpdatedTime = now;
    }
    // Update screen.
    const dispatcher = this._screenDispatcher;
    const screen = dispatcher.screens[dispatcher.screenId];
    if (screen && screen.update) {
      screen.update(stepFrames);
    }
  }

stepFrames > 0 の時のみ「描画した時の時間を保存&呼ばれた update() メソッドで描画」しているため、小数点以下が考慮されていません。

呼ばれた update() メソッドは以下の通り。

ito-to-shi/running.js at f29fae827b02d238ca753ff20f60ad69cbfb3a6e · tyru/ito-to-shi · GitHub

  update(stepFrames) {
    for (let i = 0; i < stepFrames; ++i)
      if (!this._doUpdate())
        break;
  }

前回の経過時間から描画するフレーム数ごとに描画関数を呼ぶようにしただけ。 名前は変わってますが movePercent と stepFrames は同じ値ですね。 この方法だとstepFrames=0.7(70%)とかの場合、ループは1回も回りません。

実際のデモ

(この記事のデモのつもりじゃないけど) 実際に以下のリンクから遊べます。

一応前のバージョンと比較してプレイしてみたかったので、前のバージョンもプレイ可能にしてみた。

前のバージョンと比べると普段の操作はむしろ前よりもヌルヌルになった気がします (ブラウザからすると setInterval() の方が非効率な API なんでしょうね…)。 ちなみに前のバージョンは色々バグっていたのでそこはあまりツッコまないでください。

*1:今は修正済み。あとついでに movePercent もダサい名前で変えたい…けど良い名前が思いつかない

現代的な React の書き方へのリンク

React 初心者を対象とした記事です。昨日の記事からの続き。

ES6版React.jsチュートリアル - Qiita もオススメ。

差分で見る現代化のようす

Rewrite in ES6 style · tyru/hello-react@22d4e7e · GitHub

-const React = require('react');
-const ReactDOM = require('react-dom');
+import React from 'react'
+import ReactDOM from 'react-dom'
 
-const Hello = React.createClass({
-  render: function() {
+class App extends React.Component {
+  render() {
     return (
-      <div className="container">Hello {this.props.name}</div>
+      <div className="container">Hello React in ES6 style</div>
     );
   }
-})
+}
 
-ReactDOM.render(<Hello name="React" />, document.getElementById("app"));
+ReactDOM.render(
+  <App />,
+  document.getElementById("app")
+);

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