Ember.js はレガシー IE でも動くようになっています、を対応しましたという話

この記事は Ember.js Advent Calendar の1日目です。

やはり Ember.js は大人気のようで、 Advent Calendar もあとたった 22 枠しか空きがありません。(12/1現在)
すこしでも Ember.js に興味がある方は急いで登録されることをオススメします!

この記事では私が Ember.js に送ったのレガシー IE 対応のパッチをご紹介しつつ、IE での JavaScript の罠とその対応を紹介します。
今現在 IE と向き合っている方や、これから IE と向き合うことになる方のご参考になれば幸いです。

ちなみに、 Ember.js は 1.0.0 の時点で IE 6 ですべてのテストケースに通過しており、1.2.2 (現在の最新の安定版) でも IE6 上で動作します。

ここで、レガシーIE が何か、というのを定義しておきましょう。 本記事ではレガシーIE とは IE 6, 7, 8を指すことにします。
そして今回はレガシー IE についてのみ言及しているので、以降は単に「IE」と呼ぶことにします。

IE での罠たち

IE の JavaScript には多くの罠が存在します。
それもそのはず、 IE 9 未満は ECMA-262 ではなく JScript という JavaScript っぽい独自の言語だからです。

なので、まずは現在多くのプラットフォームで動作することが期待されている ECMA-262 3rd edition と互換性を目指すことで IE 対応を進めていきたいと思います。

Array にはメソッドが足りない

ECMA-262 3rd edition で定義されている Array のメソッドの中で、 JScript では定義されていないメソッドがいくつか存在します。

  • Array.prototype.forEach
  • Array.prototype.map
  • Array.prototype.indexOf

そこで、 Ember.js にはこれらの足りないメソッドのために、互換性のある関数群が用意されています。

  • Ember.ArrayPolyfills.forEach
  • Ember.ArrayPolyfills.map
  • Ember.ArrayPolyfills.indexOf

この関数群には、該当する Array にメソッドがすればそのメソッドが、なければ独自に定義した互換関数が格納されています。

ということで、この点についての IE 対応は比較的簡単です。 forEachmap が使われている箇所を、すべて互換関数に置き換えてしまえば対応完了です。

host object にはメソッドが足りない

ホストで提供されている Function には本来 Function オブジェクトが持っているはずのメソッドである toStringapply が定義されていません。

信じられないことに、次のコードは IE では実行時例外となります。

setTimeout.toString();
//=> throw 'Object doesn't support this property or method'

toString に関しては、

String(setTimeout);
//=> 'function() { [native code] }'

で回避できます。

ただし apply については一工夫必要です。

稀に、setTimeout を一時的にオーバーライドして引数をチェックした上で本来の setTimeout に渡したい、とったようなケースがあり、その際には apply をなんとかして使う必要があります。
私は Function.prototype.applysetTimeout に apply することで対応することにしました。

var apply = Function.prototype.apply;
apply.apply(setTimeout, [this, arguments]);

Object.prototype のプロパティと同名のプロパティが for in で列挙されない

通常 for ~ in ループでは、オブジェクト自身が持っているプロパティを列挙することができます。 しかし IE では、Object.prototype で定義されているプロパティと同じ名前のプロパティについては列挙されません。

var object = {
  toString: function() { return 'hi' }
};

for (var key in object) {
  // 一般的な JavaScript とは違い、toString が列挙されない。
}

この対応についてはかなり悩んだ結果、事前に Object.prototype のプロパティ名の配列を用意しておき、ひとつひとつプロパティをチェックするようにしました。

var keys = [
  'constructor',
  'hasOwnProperty',
  'isPrototypeOf',
  'propertyIsEnumerable',
  'valueOf',
  'toLocaleString',
  'toString'
];

for (var i = 0, length = keys.length; i < length; i++) {
  if (object.hasOwnProperty(key)) {
    // ここでは toString が取得できる
  }
}

暗黙の global 変数と window のプロパティは別

IE では 「global 変数 = window のプラパティ」というわけではありません。

greet = 'hi';

window.greet; //=> 'hi'
greet; //=> 'hi'

window.greet = 'bye';

window.greet; //=> 'bye'
greet; //=> 'hi' // ここで一般的なブラウザは 'bye' が変える

globa 変数を使わなければ問題ないように見えますが、もとから存在する global 変数を一時的にスタブしたいといった場合には致命的な挙動となります。 なので、そういったケースであれば、常に window 付きで変数にアクセスするというのが有効です。

その他

他にも、Object.create が不完全な状態で実装されていたり<a> の href に必ずホスト名が含まれたり<input type=checkbox>は focus して blur しないと change イベントが発火しなかったりといった DOM 由来の奇妙な動作がいくつかあるんですが、今回は詳しい説明は割愛します。

感想

苦行とされるIE対応も、やってみると意外な発見がありました。

  • 納期も制約もない IE 対応は意外と楽しい
  • 得られる知見が多く、マルチプラットフォームで動くコードを維持する大変さを体感できる(ただし未来へつながる知見はない)

ちなみに、わたしがなぜ Ember.js の IE 対応を行なっているかというと、もちろん Ember.js 自体の価値になるというのもあるんですが、 一番の理由は過去に経験した IE 対応の案件で大変苦い思いをしたからです。
当時 IE をあまく見すぎていたために、時間・体力・精神力の大部分を持って行かれてしまいました。
案件が始まる前からちゃんと IE と向き合っていられれば、そして当時 IE に対応した Ember.js があれば、何かが違っていたかもしれません。

そこで、「過去の自分を救いに行く作業」を経験された hmsk センパイを見習うことにしたのです。

まとめ

今回ご紹介したのは、実は JavaScript の IE 対応のほんの一部の Tips です。
実際に web サイト/アプリを IE 対応させようとした場合には、 JavaScript 以外にも DOM や CSS も対応させる必要がありますし、 さらにそれを統合した状態で期待通りに動くよう調整を行なう必要があります。

というわけで、IE と向き合っているみなさまにとって、本記事が少しでも手助けになれば幸いです。

では、引き続き Ember.js Advent Calendar をお楽しみくださいませ :-)

東京Node学園際2013に参加して発表してきました

東京Node学園際2013」に参加して「Node.js を選ぶとき 選ばないとき」というタイトルで発表してきました。

ぼくが今まで作った web アプリケーションの例を挙げつつ Node.js と Rails を比較するという内容です。
みなさまが Node.js を選択する(かどうかの)際の参考となれば幸いです。

また、開催日直前まで台風の接近で開催が危ぶまれていましたが、台風の進路変更で無事開催できることとなりました。
このあたりの事前の判断そして告知など運営のみなさまは大変な思いをされたのではないかと推察します。大変お疲れさまでした。

Ruby勉強会@札幌-27 で Ember.js について発表してきました

「もっとはじめる Ember.js!!」というタイトルでお話してきました。

Rubyist が Ember.js を始めるためにいろいろと便利なライブラリの紹介をしつつ、Ember.js のいいところを簡単に紹介する、という内容となっています。

前回の発表 の続編となることを意識して作ってあるので、Ember.js 自体が始めての方はこちらもみてみてくださいませ!

OSC Hokkaido 2013 で Ember.js について発表してきました

「はじめる Ember.js!!」というタイトルでお話してきました。

デモに使ったアプリ(+ソースコード)はこちら

Ember.js 可愛いよ Ember.js。

「Middleman で作った web サイトを Travis + GitHub pages でお手軽に運用する」でのセキュリティ上の不具合がありましたのお知らせ

以前書いていたブログ記事で、致命的なセキュリティ上の不具合があったのでその訂正についてお知らせします。

どのような不具合かというと、本ブログ記事にしたがって middleman の自動ビルドを Travis に設定している場合、GitHub トークンが Travis のログに残ってしまい第三者に悪用されるという可能性があるというものでした。
該当の箇所はすでに修正してありますので、今後の設定については問題ありません。

今まで参照してくださっていたみなさま、大変申し訳ありませんでした。
今利用中の Token については expire していただいて、新しい Token を再設定していただけるとありがたいです。

不具合について連絡してくださった @azu_re さん、対応方法を連絡してくださった @hokaccha さん、大変ありがとうございましたっ!!

Sapporo.js - 2013.07.27に参加しました / Angular.js/Knockout.js についての感想など

Sapporo.js - 2013.07.27 に参加してきました。

今回は、 @tmaeda さんが Angular.js@iakio さんが Knockout.js について発表をしてくださいました。

簡単にですが、お二方の発表についてのぼくの感想をまとめておきます。(主に自分のメモ用)

AngularJSのご紹介

  • "Angular.js はフレームワークではない! HTML の再実装だッ!(バーン"

    • なるほど!という印象。今までは、HTML に独自拡張の属性をつけるのがあまり好きになれなかったんですが、こういう方針なら頷けます。
  • JSON の serialize/deserialize やコミットのタイミングなど、外部 API とのやり取りは特にサポートしていないのでプログラマが自分でなんとかする必要があるとのこと。

    • (Ember Data は自前でなんとかしようとしているんですが、今一歩力及ばずな印象。そもそもこのあたりはフレームワークで解決できない、ドメインに特化した領域ななんですねぇ...)
  • 変数名で DI を実現しているとことで、minify とは相性が悪そう -> 書き方で回避できるよ!

    • ちょっとしんどいですよねぇ... minifier のオプション(あるのかな..?)とかで設定できるとよいんでしょうか?

Getting start with knockout.js

  • 生の JavaScript の延長線上に存在している気がして、メソッド名を見るだけでもなんとかくどうなるかわかりやすい

  • "Knockout.jsはフレームワークではなくライブラ リだ(ズギャーン"

    • なるほど、こういう Knockout.js はこういう位置づけなんですね
  • ko.computed は依存性をプログラマが明示する必要がない、っていうのはかなりすばらしいと思いました。 -> "定義時に一回実行してみる"

    • こういう割り切りは大好きです。(Ember.js だと、自分で明示する必要があるので若干面倒です。そこまで人間が管理するの?っていうのはちょっとストレスフル。)
  • あまり大それたことをやっているライブラリではないので、Backbone.js を触ったことがあるひとならすぐに馴染めるのかな、という感じでした。ぼくは Knockout.js は触ったことがないですが。

その他

ちなみに、この MV* の空気を読まずぼくは Ruby の Middleman について発表してきました。

発表資料はこちら:

当日いただいた感想:

今回、ぼくからの発表お願いを快く引き受けてくださったお二人には感謝の気持ちでいっぱいです。

今後共よろしくお願いいたします。

Ember.js を Middleman から簡単に使うために ember-middleman を作りました

今巷で人気の Ember.js を Middleman で簡単に使うために ember-middleman という gem を作りました。

どんなことができるか、簡単にご紹介します。

gem によるライブラリの管理

Ember.js とそれが依存する Handlebars を gem で管理できるようになります。

# Gemfile

gem 'ember-source', '1.0.0.rc5'

これで、Asset pipeline のパスに Ember.js と Handlebars が追加されます。

//= require handlebars
//= require ember

Ember.js の頻繁なアップデートに対応するのが楽になります。

handlebars サポート

source/javascripts/templates 以下に handlebars でファイルを作成することができるようになります。

例えば、source/javascripts/templates/ok_button.js.handlebars というファイル名で以下の内容を保存したとします。

<div>
  <button {{action someAction}}>OK</button>
</div>

すると、JavaScript の中からは、 Ember.TEMPLATES['ok_button'] という名前でコンパイル済のテンプレートにアクセスできます。

Ember の view に対しても、 templateName: 'ok_button' という設定を行うことが可能になります。

これで、テンプレートファイルを html の中に埋め込まなくてよくなるのでメンテナンスしやすくなります。

ジェネレータ

Ember.js で使うであろうファイル/ディレクトリ群を自動生成することができます。

準備のために、以下の Gemfile を用意します:

# Gemfile
source 'https://rubygems.org'

gem 'middleman'
gem 'ember-middleman'

この状態で、以下のコマンドを入力します:

$ bundle exec middleman init . --template=ember

これでテンプレートが自動生成されます。

プロジェクト作成の際の手間が減ります。

使い方

使い方はいたってシンプルです。 middleman 用の Gemfile と confing.rb に以下の一行を追加するだけです:

# Gemfile
gem 'ember-middleman'
# config.rb
activate :ember

最後に

以上、ember-middleman のご紹介でした。

具体的に ember-middleman を利用しているプロジェクトはこちらにあるので、使い方に迷った方はご参照くださいませ。

Ember.js + Middleman の組み合わせで開発をしたいと思った方は選択肢のひとつに加えていただけると幸いです:D