WEB+DB PRESS Vol.65にCoffeeScriptの記事を書きました

WEB+DB PRESS Vol.65|gihyo.jp … 技術評論社
今回は「CoffeeScriptから学ぶJavaScriptプラクティス」と題して、CoffeeScriptが生成するJavaScriptについて、どうしてこういったJavaScriptになるのかという点を中心に解説しました。
CoffeeScriptには様々なJavaScriptの(バッド)ノウハウが詰め込まれています。生成されるJavaScriptを読んでいると、「やっぱりそうなるよね」と思わせることが多くてニヤニヤしてしまいます。
そもそも、CoffeeScriptは生成されるJavaScriptの読みやすさにかなり気を使っている点が、CoffeeScriptを気に入っている一番の理由です。(その点、Dartは…)
今回も結構苦労しましたが、なかなか面白い記事に仕上がったと思うので、手にとって頂ければ幸いです。

WEB+DB PRESS Vol.65

WEB+DB PRESS Vol.65

  • 作者: 天尋左石,柄沢聡太郎,小野修司,ミック,みやけん,長野雅広,おにたま,中島聡,清水亮,角田直行,はまちや2,大塚弘記,松信嘉範,板垣貴裕,福岡博,大和田純,白土慧,太田昌吾,じゅんいち☆かとう,WEB+DB PRESS編集部
  • 出版社/メーカー: 技術評論社
  • 発売日: 2011/10/22
  • メディア: 大型本
  • 購入: 12人 クリック: 162回
  • この商品を含むブログ (11件) を見る

IE8のレンダリングモードと特定文字列によるクラッシュバグ

こんにちは、だいぶ久しぶりなブログ更新です。昨日、IE8のおもしろバグを発見してしまったので、さらしものにみなさんと共有したいと思います。
早速今回のバグの再現コードをみてみましょう。

<!DOCTYPE html>ܫ<br>1日

再現ページ
たったこれだけです。JavaScriptはおろか、CSSすらありません。ただのHTMLです。これだけでIE8の標準モードのレンダリングが壊れ、強制的に互換モードにフォールバックされます。
さらに次のようにmetaタグ(もしくはレスポンスヘッダ)でX-UA-CompatibleにIE=8やIE=edgeを指定して、標準モードでのレンダリングを強制していた場合はフォールバックが働かないので真っ白なページが表示されてしまいます。
<!DOCTYPE html>
<meta http-equiv="X-UA-Compatible" content="IE=8">ܫ<br>1日
<p>IE8では真っ白なページになる

再現ページ(IE=Edge指定)

このバグはIE8(正確にはWindows XPのIE8)だけで発生しており、IE6, 7, 9などでは発生しません。IE9のIE8モードでも発生しないので、MS的には修正済みなのか、たまたま直ったのかは、よくわかりません。
ちなみに、ܫというのはWindowsではf:id:os0x:20111004181507p:imageのように表示され、f:id:os0x:20111004181508p:imageのような顔文字によく使われています。これと1日のような普通の文字の組み合わせで発生するので、実際に起こり得る組み合わせです。

今回は、このバグが発生するページと発生しないページの差分からなんとか原因を特定できましたが、正直予想し得ないバグですね。もっと追えば、より短い文字列で発生するかもしれませんし、全く別の文字列パターンでも発生するかもしれません。その辺は文字コードやWindows・IEのアレコレに詳しい方に勝手に期待しています。

AutoPatchWorkのサーバー周りのこと

地味に色々と調整しているのでまとめておきます。見よう見まねの継ぎ接ぎばかりですが…。

AutoPatchWorkはwedata(About - wedata)のAutoPagerize用SITEINFOを使わせて頂いていますが、wedataのサーバーは負荷に弱いので、SITEINFOは自前のサーバー(といっても安心のhetemlですが)に置いています。
hetemlサーバーでcronを設定して1時間に1回、SITEINFOを更新するようにしていて、AutoPatchWorkユーザーは一日に1回hetemlサーバーからSITEINFOを取得しています。
ただ、AutoPagerizeのSITEINFOは2MB近くあるので、1万人が毎日アクセスしたら転送量が20GBに達します(全員が毎日更新にくるわけではないので、実際はもっと少ないですが)。hetemlの転送量は「目安として 1日 20GB まで」(2011年6月27日時点。ちなみに以前は10GBだった)で、ChromeのAutoPatchWorkユーザーだけで6万人以上いるので余裕でアウトです(といっても、一度問い合わせてみたところ、目安を超えたら即制限がかかるようなことはないそうです)。
ただ、容量を節約するポイントは色々あって、まずオリジナルのSITEINFOは次のようなJSONデータです。

{
  "name": "Google Search",
  "resource_url": "http:\/\/wedata.net\/items\/472",
  "updated_at": "2011-04-24T23:09:52+09:00",
  "created_by": "swdyh",
  "database_resource_url": "http:\/\/wedata.net\/databases\/AutoPagerize",
  "data": {
    "pageElement": "id(\"search\")\/div[ol or div]|id(\"res\")[not(@role)]\/div[ol or div]|id(\"ofr\")",
    "url": "^https?:\/\/[^.\/]+\\.google(?:\\.[^.\/]{2,3}){1,2}\/(?:c(?:se|ustom)|search|webhp|#)",
    "nextLink": "id(\"pnnext\")|id(\"navbar navcnt nav\")\/\/td[span]\/following-sibling::td[1]\/a|id(\"nn\")\/parent::a",
    "exampleUrl": "http:\/\/www.google.com\/search?q=AutoPagerize\r\nhttp:\/\/www.google.com\/cse?cref=http%3A%2F%2Fwww.guha.com%2Fcref_cse.xml&q=firefox&sa=Search&siteurl=www.google.co.jp%2Fcse%2Fdocs%2Fcref.html\r\nhttp:\/\/www.google.co.jp\/custom?q=firefox\r\nhttp:\/\/www.google.com\/webhp?hl=en#hl=en&expIds=17259,18167,25901,25980,26440,26459,26512&sugexp=ldymls&tok=So3knfbAtc2kJy-W-lXiWw&xhr=t&q=autopagerize&cp=10&pf=p&sclient=psy&safe=off&site=webhp&aq=f&aqi=g5&aql=&oq=autopageri&gs_rfai=&pbx=1&fp=6052204b889acdd8"
  },
  "created_at": "2008-04-16T12:35:37+09:00"
}

実際に使うのはdataの中身で、さらにexampleUrlなどはデバッグ用なので、普段は使用しません。つまり、こうできます。

{
"pageElement": "id(\"search\")\/div[ol or div]|id(\"res\")[not(@role)]\/div[ol or div]|id(\"ofr\")",
"url": "^https?:\/\/[^.\/]+\\.google(?:\\.[^.\/]{2,3}){1,2}\/(?:c(?:se|ustom)|search|webhp|#)",
"nextLink": "id(\"pnnext\")|id(\"navbar navcnt nav\")\/\/td[span]\/following-sibling::td[1]\/a|id(\"nn\")\/parent::a"
}

これだけで1.9MBから715KBまで節約できます(ただ、元のデータを引けなくなるので、IDだけ追加しています)。結構節約できますが、まだ3万ユーザー程度しかカバーできません。
これにgzipを加えてみます。cronバッチの中でgzファイルを静的に作成して、.htaccessで次のように指定します。

RewriteEngine on
RewriteCond %{HTTP:Accept-Encoding} gzip
RewriteCond %{REQUEST_FILENAME}\.gz -s
RewriteRule ^(.+)$ $1.gz
AddEncoding x-gzip .gz
  1. Accept-Encodingにgzipが含まれていて、
  2. リクエストされたファイル名+.gzなファイルが存在したら、
  3. URLを.gzつきに書き換え、
  4. ファイルタイプをjsonとして強制する

という指定です。
AddEncoding x-gzip .gz はレスポンスヘッダにContent-Encoging: gzipをつけて、ブラウザにgzipとして解釈させるようにしている(のだと思うのですが、あまり自信ない…)
gzip効果で、1.9MBのファイルは355KBに、715KBのファイルは152KBまで節約されます。
152KBならすくなくとも13万ユーザーまで大丈夫です。
実際、今のところ1日の転送量は8GB程度に抑えられています。

ついでにhetemlがmod_expiresにも対応していたので、ExpiresByTypeなんかも加えて、 http://ss-o.net/json/.htaccess はこんな感じになっています。

Options +Indexes
Header append Access-Control-Allow-Origin: *

ExpiresActive on
ExpiresByType application/json "access plus 12 hours"
ExpiresByType application/x-javascript "access plus 2 days"
<Files ~ "\.json">
  ForceType "application/json; charset=utf-8"
</Files>

RewriteEngine on
RewriteCond %{HTTP:Accept-Encoding} gzip
RewriteCond %{REQUEST_FILENAME}\.gz -s
RewriteRule ^(.+)$ $1.gz
AddEncoding x-gzip .gz

IndexIgnore .htaccess *.bak *.gz
IndexOptions FancyIndexing HTMLTable NameWidth=* XHTML

WEB+DB PRESS Vol.63からJavaScriptの連載始めました

WEB+DB PRESS Vol.63|gihyo.jp … 技術評論社
id:uupaaさんの後任な感じで、JavaScript周りのホットな話題・最新情報を中心に、書いていこうと思っています。
初回は例によってデバッグ周りの話を書きました。といってもまだブログでは触れていないスマートフォンでのデバッグ方法や、 prototype.jsからjQueryに移行するたったひとつの冴えたやりかた - 0xFFでちょっとだけ触れたonerrorの具体的な使い方などを解説しています。よろしければ手にとってみてください。って発売は24日でした…紛らわしい書き方してしまってごめんなさい。。

WEB+DB PRESS Vol.63

WEB+DB PRESS Vol.63

  • 作者: 竹迫良範,和田卓人,じゅんいち☆かとう,太田昌吾,小野修司,ミック,嶋田裕二,個々一番,みやけん,清水亮,おにたま,中島聡,角田直行,はまちや2,上谷隆宏,青木俊介,大塚知洋,生尾剛士,大和田純,WEB+DB PRESS編集部
  • 出版社/メーカー: 技術評論社
  • 発売日: 2011/06/24
  • メディア: 大型本
  • 購入: 19人 クリック: 426回
  • この商品を含むブログ (22件) を見る

あわせて読みたい

ウェブオペレーション ―サイト運用管理の実践テクニック (THEORY/IN/PRACTICE)

ウェブオペレーション ―サイト運用管理の実践テクニック (THEORY/IN/PRACTICE)


NoSQLデータベースファーストガイド

NoSQLデータベースファーストガイド

君は3つのリロードを知っているか?

はい、今さら聞けないウェブ開発者の基礎知識のお時間です。
ブラウザには3つの読み込みモードがあることはご存知ですか?
2つくらいはわかるけど、3つ目が出てこないって方は少なくないかもしれません。

  1. リロード
    • 一番オーソドックスなのがブラウザのリロードボタンを押したときの挙動ですね。普通ですね。
  2. スーパーリロード
    • ブラウザによっては、スーパーリロードという機能を備えています。IEの場合Ctrl+F5(Ctrl+更新ボタン)、Firefoxの場合Ctrl+F5(Shift+更新ボタン)、Chromeの場合Shift+更新ボタン(Ctrl+Shift+R)などでスーパーリロードを呼び出すことができます。スーパーですね。
  3. ページ遷移(リフレッシュ)
    • 3つ目はちょっと良い名前が思いつかないのですが、リロードではなく、通常の画面遷移での読み込みのことです。アドレスバーにフォーカスしてenterするといった方法で実現できます。むしろこれこそが普通です。

id:edvakfさん曰く、Operaにはリクエストを投げずに更新するRefresh Displayというアクションがあるらしい。確かにリフレッシュというのは適切な名前な気がする。

それぞれどう違うのか、特に1つ目と3つ目はどう違うのか不思議でしょうか?
ちゃんとリクエストの様子を眺めてみると違いがよく分かります。

リロード

f:id:os0x:20110617120324p:image

スーパーリロード

f:id:os0x:20110617120326p:image

ページ遷移

f:id:os0x:20110617120325p:image

リロードは大元のHTMLを受け取ったら、そのページ内のリソースについてレスポンスヘッダを確認しに行っています。その結果304 Not Modifiedが帰ってきて、実際にはキャッシュを使っています。
対してスーパーリロードはキャッシュに関係なく、すべてのリソースを取得しなおしています。
そしてページ遷移時は、キャッシュがあり、Expireが効いている場合はそもそもリクエストを送っていません。完全にローカルキャッシュだけを使っています。
(ちなみに、ページを進んで(ブラウザの戻る機能で)戻ったときは大元のHTMLを含めてキャッシュが使われたりします。)

なお、当然ですがすべてのブラウザがこの3つのモードを持っているわけではありません。Operaにはスーパーリロードに相当する手段がなさそう(知らないだけかもしれませんが)だったりします。

というわけで、3つのリロードの違いを意識して、開発時に使い分けるのがウェブ開発者のたしなみです。

Google ChromeのJavaScriptデバッガの進化がすごい

Chrome版のFirebugことGoogle Chrome Developer Toolsですが、以前gihyoで解説したときよりさらに便利になっているので、少し紹介します(元はWebKitなので、そのうち(近いうちに)Safariでもそれなりに使えるようになるはずです)。

圧縮されたコードの整形

まず、目立つところからいきましょう。ちょうど先日更新されたChromeのdev版(12.0.742.0)に搭載されたばかりの機能で、minifyされているJavaScriptコードを読みやすいように整形して表示してくれるというものです(IE9の開発者ツールにも実装されている機能です)。
例えば、Google Analyticsのコードは圧縮されていて普通は読めません。
f:id:os0x:20110422140725p:image
しかし、Chromeデベロッパーツールなら、
f:id:os0x:20110422140726p:image
このように整形してくれます。
やり方は簡単で、デベロッパーツールのScriptパネルの左下に { } というアイコンがあるのでそれをクリックするだけです。*1
f:id:os0x:20110816081411p:image
このチェックはデベロッパーツールを起動している間だけ有効で、リロードしても維持されます。
つまり、整形した状態でブレークポイントを設定して、実際にブレークすることができます。
f:id:os0x:20110422144852p:image
これで圧縮されているライブラリのデバッグがものすごく簡単になります。
なにより大事なのは、これまでは開発者にとってJavaScriptを圧縮するかどうかは、デバッグしやすさとパフォーマンスとの間のトレードオフで悩ましい問題だったわけです。しかし、それはもう過去の話で、圧縮してもデバッグに支障がなくなったので、圧縮しない理由の1つがなくなった訳です。
とは言ったものの、今のところ実装されたばかりの機能なので安定していません。実際に使えるようになるにはまだ少し時間が必要です。今後が非常に楽しみですね。

JavaScriptのインスタント編集

Scriptパネルのコードエリアでダブルクリックすると編集モードに入り、自由にコードを書き換えることができます。残念ながら(2011年4月22日時点では)編集した内容を保存できないので、デバッグには使いにくいですが、あとからデバッグコードを埋め込むことなどが可能で、こちらも今後が楽しみです。こちらはstable(10.0)とdev(12.0)で少し挙動が違いますが、stableでも使えます。

柔軟なブレークポイント

様々なブレークポイントを設定することが可能で、通常の行単位でのブレーク以外に以下のようなものがあります(こちらはstable版でも使えます)。

  • 条件付きブレークポイント
    • 任意の条件式がtrueを返すときだけブレークする
  • DOMブレークポイント
    • DOMの内容や、属性、削除されたときにブレークする
  • XHR ブレークポイント
    • XHR送信時にブレークする、送信先が特定の文字列を含む時だけブレークすることも可能
  • Event Listenerブレークポイント
    • キー入力、マウス、タイマー(setTimeout)などのイベント時に自動でブレークする
  • 例外発生時にブレーク
    • 例外が発生したときにそこでブレークすることも可能です。catchしているときはブレークしないようにもできます。

パネルでの表示はこんな感じです。
f:id:os0x:20110422185223p:image
特に、XHRでのブレークとタイマーでのブレークがすごく便利です。非同期処理はコードを追いにくいので、そこでブレークできるのは非常に助かります。


他にも色々な改良が行われていて、個人的には(特にJavaScriptに関しては)FirefoxFirebugよりChromeデベロッパーツールの方が使いやすくなったと思っています。ただ、概してツールというのは慣れによる使いやすさが大きな比重を占めてしまうので、最初は使いにくいと感じるかもしれません。が、そういった固定観念に囚われてしまうのは勿体無いので、是非デベロッパーツールをアレコレ試してもらえればと思います。


ところで、ちょうど4/22から4/23にかけて、第2回 開発コンテスト24というイベントをやっています。ものづくりに情熱のある方が是非ご参加ください!クックパッドオフィスのラウンジを開発スペースとして提供もしているので、是非ご利用ください。

*1:記事を書いた当初は右クリックメニューにDe-obfuscate Sourceという項目があったのですが、今はアイコンになっています

prototype.jsからjQueryに移行するたったひとつの冴えたやりかた

どうもこんにちは、os0xです。
実は(Twitterに書いただけで)ブログに書いてなかったのですが、3ヶ月ほど前からクックパッドで働いています*1。なんかもう今更ですよね、すみません。
さてさて、クックパッドですが、つい一昨日までprototype.jsを使っていました。で、昨日jQueryへの移行をリリースしたところだったりします。
というわけで、その辺の話を少し書いてみたいと思います。

そもそも、なんでjQueryに移行するのか

まあ、prototype.jsjQueryどちらを使うかと問われたら、大抵の人はjQueryと答えますよね。確かにjQueryの使いやすさは魅力的です。使いやすいということは、みんなでjQueryを使ってサービスを作ることができます。特定の誰かに依存してボトルネックになったりすることがないなら、それは素晴らしいですね。
しかし、ライブラリを変えるのは簡単なことではありません。JavaScriptのテストがしっかり書かれているなら安全に移行できるかもしれませんが、まあ、それは期待できないでしょう(というか、そのテストがライブラリに依存してたり)。
本当にそのコストをかけてまでjQueryへ移行するメリットはあるのか、というのが実際的な問題です。
ただ、コストが問題というのなら、簡単にprototype.jsからjQueryに移行できるなら、それをしない理由はないというわけです。

prototype.js依存をなくす

jQueryprototype.jsは共存できますが、共存させるということはつまり両方に依存するということです。一度両方に依存してしまうと分離はまた面倒なことになります。なので、動くようになるまで多少大変でも完全に切り替えてしまったほうが良いでしょう。
prototype.jsからjQueryへの書き換えは割と単純で、例えばAjax周りだと、次のようになります。

new Ajax.Request(url, {
  onSuccess: function(response) {
    // 
  }
});
jQuery.ajax({url:url, success:function(response){
  // 
});
new Ajax.Updater('id', url, {
  parameters: params
});
jQuery.ajax({
 url:url,
 data:params,
 success:function(response){
  jQuery('#id').html(response);
});

他にも、

  • Element → jQuery().show/hide...
  • Event → jQuery().bind
  • $('id') → jQuery('#id')
  • $$('#id .class') → jQuery('#id .class')
  • $A(arguments).each(function(){}) → jQuery.each(arguments,function(){})
  • $H({}).each(function(){}) → jQuery.each({},function(){})

といった感じで割と簡単に書き換えできます。$とかEventとかAjaxとかキーワードがあるので、割と機械的にいけるはずです。
ちなみに、あえてjQueryは$を使わずにjQueryと書いたほうが書き換えたことが分かりやすくなるのでお薦めです。
コードの量にもよりますが、この作業自体はそれほど大変ではないと思います。

jQueryで正しく動いているかの検証

さて、最大の問題はjQueryに書き換えたコードが正しく動いているかどうかの検証です。どうしてもバグを作ってしまうはずなので、以下にしてそれを検知するかが課題となるわけです。
そこで、役に立つのがwindow.onerrorです。FirefoxとIEではwindow.onerrorを定義しておくと、ページ内で起きたすべてのJavaScriptエラーを捕まえてくれます。Firefoxでは(new Errorで)スタックも取れますし、IEでは呼び出し元関数を取れたりするので、デバッグに必要な情報を集めることができます*2

まとめ

というわけで、まとめとしては両方依存は避ける・なるべく機械的に書き換える・エラーを検出してデバッグといった感じです。

さて、クックパッドのjQuery移行は今後のための布石に過ぎません。これからユーザーのためのサービスをどんどん開発して行きたいと思っています。
[PR] クックパッドではエンジニアを絶賛募集中です
というわけで、クックパッドで一緒に開発しませんか?もし興味を持って頂けたなら、まずはクックパッドに遊びに来たりしてみるといいんじゃないでしょうか。お待ちしています!

*1:この展開、最近どこかで見ましたか?きっと気のせいです。

*2:ちなみにちょうど今日、WebKitもエラーを捕まえられるようになりました[http://trac.webkit.org/changeset/76216:title]