2016/07/08更新

[フロントエンド] Reactを古いブラウザに対応させようと色々と苦労したけど、なんとか対応できた話

このエントリーをはてなブックマークに追加      

こんにちは、Reactに戯れ中の@yoheiMuneです。
仕事で担当しているアプリケーションについて、一部React化した際に、古い端末たちでも動く対応をしたので、それをブログに残しておきたいと思います(これは絶対に今後も参照しそうな気がします、個人的に)。

画像

目次




今回対応した古い端末たち

Reactはモダンな環境で動くように実装されているので、何も対応しないと以下のような古い端末で起動すらしないという状況でした。

  • iOS6やiOS7のSafari
  • Android4.0の標準ブラウザー
  • HTCなど一部の古めの端末

これらでも動くようにするために、いろいろと試行錯誤した結果、最終的には動くようになりました。後述の3つでした。



ES5のポリフィル導入

まずはこんな感じのエラーが出ました。
TypeError: Result of expression 'Object.freeze' [undefined] is not a function ?
これはReactの説明(日本語)でも記載されていることですが、古いブラウザ対応のためにShimを読み込む必要があります。具体的には、以下のような機能を使えるようにするために、
Array.isArray
Array.prototype.every
Array.prototype.forEach
Array.prototype.indexOf
Array.prototype.map
Date.now
Function.prototype.bind
Object.keys
String.prototype.split
String.prototype.trim

Object.create
Object.freeze
以下のように、各種ポリフィルを読み込みます(詳細は上記リンクを参照ください)。
<script src="https://cdnjs.cloudflare.com/ajax/libs/es5-shim/4.5.7/es5-shim.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/es5-shim/4.5.7/es5-sham.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/json3/3.3.2/json3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/es6-shim/0.34.2/es6-shim.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/es6-shim/0.34.2/es6-sham.min.js"></script>
<script src="https://wzrd.in/standalone/es7-shim@latest"></script>
余談ですが、es5-shim.min.jses5-sham.min.jsは別物なんですね。最初見た時は同じ行が2つあるぞー、と思って少し惑わされましたw。

またh、必要に応じてHTML5のポリフィルも導入します(必要あれば)。
<script src="https://cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv-printshiv.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv.min.js"></script>
これでReactが動くと思いきや、動かない端末たちがありました。具体的にはiOS6や一部のAndroid4.0系です。残念><。。



Promiseのポリフィルを導入する

なぜ動かないのかを、iOSサファリのデバッガで確認したところ、Promiseがなくてエラーになっていることがわかりました。
Uncaught ReferenceError: Promise is not defined
なので、Promiseのポリフィルを必要に応じて読み込むようにしました。
if (window.Promise === undefined) {
    document.write('<scr'+'ipt src="https://cdnjs.cloudflare.com/ajax/libs/es6-promise/3.2.2/es6-promise.js"></scr'+'ipt>');
}
この対応で古いiOS系は動くようになりました。しかし、それでも一部動かないAndroid端末たちがあり、次の対応を行いました。



Babelのlooseコンパイル

主にAndroid4.0の標準ブラウザで発生していたエラーですが、以下のようなJSエラーが発生していました。
Uncaught TypeError: Cannot assign to read only property '__esModule' of #<Object>
これをよくよく調べていくと、(Babelでコンパイルしたソースコードで)React内の以下の箇所でエラーになっていることがわかりました。
Object.defineProperty(exports, "__esModule", {
    value: true
});
definePropertyメソッドで、__esModuleという変数は追加できないぞ、というエラーのようです。Object.defineProperty | MDN(英語)によるとwritabletrueにしないとできないようで、その部分のエラーとのことでした。

これをどう対応するかを調べていると、以下のようにコードを書けば動くことがわかりました。
exports.__esModule = true;
ただ、上記のコードはBabelでコンパイルした時にできたものなので、今回は書き換えることはできません。Babelで対応するにはどうしたらいいのか調べたら、以下のlooseコンパイルできるものを利用する方法がありました(Babel自体にもlooseオプションはありましたが、v6.0.0から削除されました)。

babel-preset-es2015-loose | npm

これを導入して、
$ npm install --save-dev babel-preset-es2015-loose
そして、.babelrcを以下のようにすることで、ルーズコンパイルができるようになりました。
{
  "presets": ["es2015-loose"]
}
この結果、無事に問題を起こしていたAndroid端末でも動くようになりました。



今回学んだ教訓

大きく2点学びました。

1つ目は、アプリケーションのサポート対象をこまめに見直していく必要があるということ。今回は全て対応できた(と思いたい)ですが、今後同じような技術バージョンアップをする際に、必ずや足かせになる時があるなと感じています。技術的な負債で、サービスの成長を止めないようにしていけたらと思います。

2点目は、技術を大きく切り替える時には、20個くらいの環境の違いでテストした方がいいなと思いました。iOS7系でエラーが出るとは思っていなかったです。

いろいろと反省点はありますが、学ぶところも多かったなと感じています。



最後に

今回はReactにおける、古い環境への対応についてブログを書きました。いろいろな試行錯誤ができてなかなか楽しいものでもありました。途中、中国人のブログに答えがあった時とかは、インターナショナルをなぜか感じてましたw。

最後になりますが本ブログでは、フロントエンド・Swift・Python・Java・機械学習など雑多に情報発信をしていきます。自分の第2の脳にすべく、情報をブログに貯めています。気になった方は、本ブログのRSSTwitterをフォローして頂けると幸いです ^ ^。

最後までご覧頂きましてありがとうございました!





こんな記事もいかがですか?

[取り組み] フロントエンドでコーディングスピードをアップさせる6つの方法!と思って書いてたら30個も書いちゃった。
[フロントエンド] フロントエンドの入社試験99問!難しいですよ〜w。
[フロントエンド] Webページを表示するテストの際に、通信速度を3Gに制限して表示してみよう
[フロントエンド] スマホ実機でのデバッグ手段を増やす!Macのプロキシを利用して、通信内容を確認する。
[フロントエンド] Chrome 35 Beta の変更点。Touch制御、新しいJavaScript機能、プレフィックスなしのShadowDOM
[フロントエンド]複数アカウントでのテストには、Chromeのユーザー管理を使って、Cookieを切り替えると便利
[フロントエンド] Chrome36βが出た。変更点など。element.animate、HTML Imports、Object.observe、他。
RSS画像

もしご興味をお持ち頂けましたら、ぜひRSSへの登録をお願い致します。