Yakstは、海外の役立つブログ記事などを人力で翻訳して公開するプロジェクトです。
5年以上前投稿 修正あり

レスポンシブ「ル」JavaScript: その1

JavaScriptは用法・用量に責任を持って正しく使いましょう

原文
Responsible JavaScript: Part I – A List Apart (English)
原文著者
Jeremy Wagner
原文公開日
2019-03-28
翻訳依頼者
A3e9a72544c34898bde469ab10cddc25
翻訳者
A3e9a72544c34898bde469ab10cddc25 meiq
翻訳レビュアー
D98ee74ffe0fafbdc83b23907dda3665 doublemarket B5aa4f809000b9147289650532e83932 taka-h
原著者への翻訳報告
2043日前 メールで報告済み 編集


数値から読み取れるように、JavaScriptはパフォーマンスを大きく左右する要素です。 この傾向が続けば、Webページのサイズの中間値は近いうちに、転送される量だけでも少なくとも400KBに達するでしょう。 他のテキストベースのリソースと同様、JavaScriptはほぼ常に圧縮されて提供されますが、配信時にいつも正しく実践されていることといえばそれくらいかもしれません。


リソースの転送時間を減らすこと自体はパフォーマンス全体から見て重要なことの一つですが、残念ながら、圧縮によってブラウザがスクリプトを処理するのにかかる時間を減らせるわけではありません。 もしサーバが400KBの圧縮済JavaScriptを送信してきたとしたら、ブラウザが受信を完了して展開した後に処理する実サイズは1MBを超えるでしょう。 この高い処理負荷をいかにして捌くかは、実にデバイス依存なのです。 様々なデバイスが大量のJavaScriptをどう上手く処理するかに関しては多くのことが書かれていますが、実際は、たとえ取るに足らないようなサイズのJavaScriptであっても、処理にかかる時間はデバイスによって大幅に異なります。

たとえば、私のちょっとしたプロジェクト (非圧縮で23KBのJavaScript) を見てみてください。 Mac Book Proの2017年半ばのモデルでは、この程度の小さなものであれば25msで読み込めます。 しかし、これがNokia 2のAndoroidスマホ端末では190msにまで膨れ上がるのです。 無視できる時間差ではないですが、いまいまは、どちらの場合もページは無理なくインタラクティブに動作します。

では、ここで問題です: このNokia 2が、ごく平均的なWebページを読み込もうとしたらどうなると思いますか? 固まってしまいます。 たとえ高速な接続環境下であっても、JavaScript山盛りのWebページによる処理時間の増大は、この手の端末をまともに使えなくしてしまうレベルであり、Nokia 2でWebを閲覧するのはしんどいものです。


図1. Nokia 2のパフォーマンスタイムライン

図1. Nokia 2のAndroidスマホでページを閲覧した結果、過剰なJavaScriptがメインスレッドを独占してしまったパフォーマンスのタイムライン概観。


Webを支えるデバイスやネットワークが大きく進歩する一方で、さきに紹介した傾向から見て取れるように、その恩恵を私たちは食いつぶしているのです。 私たちは、Javascriptを「レスポンシブルに」=「責任を持って」用いなければなりません。 それにはまず、自分たちが「を作っているのか」「どのように構築しているか」を理解することから始めましょう。

「サイト」vs「アプリ」のマインドセット

用語の体系というのは時におかしなもので、私たちは不正確な用語を使ってざっくりと物事を指定しながらも、周りの人とは暗黙のうちに意味が通じ合っている場合があります。 実際 bee (訳注: ハナバチ。ミツバチなど、花の蜜や花粉を集めるハチ) と wasp (訳注: カリバチ。スズメバチなど、昆虫や蜘蛛などを狩るハチ) はかなり異なるものですが、bee という言葉を拡大解釈して wasp を指すのに使ったりするケースが存在します。 この言葉の違いによって、私たちはそれぞれの扱いを差別化したくなることがあります。 たとえば、wasp の巣があれば破壊したくなりますが、bee は有益で、か弱い昆虫なので、どこかに移動させることにしたりするのです。

私たちは、このような大ざっぱなノリで「Webサイト」と「Webアプリ」という言葉を交換しがちです。 この二つの差異は、スズメバチとミツバチの違いよりも曖昧なものですが、これらを混同することはやっかいな結果をもたらす場合があります。 それは、単なる「Webサイト」を、フル機能装備の「Webアプリ」として捉えてしまうようなアフォーダンスの形として現れます。 本来、ビジネスに必要な情報を載せるWebサイトを作るだけなら、DOMの変更を管理したりクライアントサイドのルーティングを実装するような強力なフレームワークに頼る必要性は薄いはずですし、少なくとも私はそうあってほしいと思います。 タスクに対し不釣り合いなツールを用いることは、サイトを利用する人たちに不利益をもたらすだけでなく、ほぼ間違いなく非生産的なのです。

それでもWebアプリを作るときには、注意してください。 数百、あるいは数千に至る依存関係を持ったパッケージ群を、ときには安全性もよくわからないものも含めてインストールするわけです。 また、モジュールのバンドラには複雑な設定を書きますよね。 混迷を極めた、しかしどこにでもあるこの手の開発環境において、高速でアクセシブルなものが出来上がることを確かにするには、知識だけではなく警戒心も必要です。 疑問に思うのであれば、プロジェクトのルートディレクトリで npm ls --prod を実行して出てくるリストにある全てが何だか分かるか確かめてみてください。 仮に分かったとしても、サードパーティーのスクリプトの内容までは把握できません。少なくともいくつかは、あなたのサイトにもあると思うのですが。

また忘れがちなことは、WebサイトとWebアプリが占める環境は同一であるということです。 どちらも、幾層にもわたる広大なネットワークとデバイスによってもたらされる同じ強制力が働いています。 このような制約は、私たちが作るものを「アプリ」と呼んだところでいきなり消えてなくなったりはしないし、ユーザーのスマホ端末が魔法の力を手に入れるわけではありません。

誰が、自分たちが作ったものを利用するのかを考え、彼ら彼女らが想定外の条件でインターネットにアクセスしてくる可能性を受け入れることは、私たちの責任です。 私たちは、自分たちが果たそうとする目的について知らなければなりませんし、そうやって初めて目的を立派に叶えるものを構築することができるのです。たとえそれが、作っていてワクワクするようなものでなかったとしても

それはすなわち、自分たちのJavaScript依存を振り返り、パフォーマンスやアクセシビリティを害するような持続不可能なパターンを受け入れないよう、その用法、とりわけHTMLやCSSの排除を見直すことを意味します。

フレームワークを用いた持続不可能なパターンに陥らないように

以前、生産性を高めるためにフレームワークに依存していたチームと仕事をしていた際、コードベースにちょっと奇妙な発見がありました。 そんなチームにみられた典型的な特徴の一つに、アクセシビリティもパフォーマンスもしばしばイマイチな結果に終わるということがあったのです。 たとえば、以下のReactコンポーネントを見てみましょう:

import React, { Component } from "react";
import { validateEmail } from "helpers/validation";

class SignupForm extends Component {
  constructor (props) {
    super(props);

    this.handleSubmit = this.handleSubmit.bind(this);
    this.updateEmail = this.updateEmail.bind(this);
    this.state.email = "";
  }

  updateEmail (event) {
    this.setState({
      email: event.target.value
    });
  }

  handleSubmit () {
    // emailが有効なら、送信する
    if (validateEmail(this.state.email)) {
      // ...
    }
  }

  render () {
    return (
      <div>
        <span class="email-label">Enter your email:</span>
        <input type="text" id="email" onChange={this.updateEmail} />
        <button onClick={this.handleSubmit}>Sign Up</button>
      </div>
    );
  }
}

このコードには重大なアクセシビリティの問題があります:

  1. <form> 要素を使わないフォームは、フォームではありません。実のところ、親の <div>role="form" を指定することでそれっぽく見えるものができるわけですが、もしフォームを作るのであれば (きちんとフォームらしく見えるやつですよ) <form> 要素とともに適切な actionmethod 属性を使いましょう。 action 属性は、JavaScriptがない状況下でもフォームが何かを行うことを保証する上で欠かせません。もちろん、コンポーネントがサーバーレンダリングされたとしてもです。

  2. <span><label> 要素の代替ではありません。<label> のもたらすアクセシビリティの恩恵が受けられなくなってしまいます。

  3. クライアントサイドでフォーム送信前に何かしようとするのであれば、<button> 要素の onClick ハンドラにバインドされているアクションを <form> 要素の onSubmit ハンドラへ移すべきです。

  4. ついでに言うと、HTML5によるフォームバリデーションはIE10以降のほとんどのブラウザでサポートされるというのに、なぜJavaScriptをemailのバリデーションに使うのでしょうか? これこそ適切なinput typerequired 属性を使ってブラウザに頼るときです。ただし、これをスクリーンリーダーでうまく機能させるためには多少のノウハウが必要です。

  5. アクセシビリティの問題ではありませんが、このコンポーネントは状態やライフサイクルのメソッドに依存していないので、フルのReactコンポーネントよりも少ないJavaScriptの記述で済ませられる、ステートレスなコンポーネントにリファクタすることができます。

これに気づけば、以下のようにコンポーネントをリファクタできるでしょう:

import React from "react";

const SignupForm = props => {
  const handleSubmit = event => {
    // サーバーにXHRでデータを送信する場合に必要
    // (JavaScript無効でサーバーレンダリングされた場合でも動く)
    event.preventDefault();

    // 続く...
  };

  return (
    <form method="POST" action="/signup" onSubmit={handleSubmit}>
      <label for="email" class="email-label">Enter your email:</label>
      <input type="email" id="email" required />
      <button>Sign Up</button>
    </form>
  );
};

コンポーネントのアクセシビリティが上がっただけでなく、JavaScriptの記述量も減りました。 JavaScriptに溺れた世界においては、数行コードを消すだけでもてきめんな治療効果が感じられるはずです。 ブラウザはこれだけ多くのものを無償で提供してくれているわけですから、最大限活用しない手はありません。

なお、これはフレームワークを使ったときだけアクセシビリティの低いパターンが発生すると言っているのではなく、むしろJavaScriptに頼りきりの発想が、いずれはHTMLやCSSへの理解の欠落を表面化させるということです。 こういった知識の欠落はしばしば、自分では気づくことすらないかもしれない失敗として現れます。フレームワークは生産性を向上させる便利なツールですが、どんなツールを用いることにしたとしても、幅広いユーザーが利用可能な体験を作る上では、コアなWeb技術の教育を続けることは欠かせません。

Webプラットフォームを信頼すれば、もっと先へ、より速くいける

フレームワークの話題でいえば、Webプラットフォーム自体が強固なフレームワークであることに言及すべきでしょう。 先のセクションで見たとおり、確立されたマークアップのパターンやブラウザの機能に頼れるのであれば、そのほうがはるかに良いのです。 それ以外の方法は再発明であって、そんな努力をしても面倒が増えるばかりか、余計な弊害をもたらします: それは、自分がインストールするどのJavaScriptパッケージの作者も、問題を包括的に、注意深く解決したのだろうだと想定してしまうことです。

シングルページアプリケーション

開発者が作りがちなトレードオフの一つに、たとえプロジェクトに合致しないケースであっても、シングルページアプリケーション (SPA) のモデルを採用してしまうことがあります。 たしかに、体感的なパフォーマンスはSPAのクライアントサイドのルーティングによって上がりますが、代わりに失うものは何でしょうか? ブラウザ自身のナビゲーション機能は、同期的ではありますが、多くの恩恵をもたらしています。 一つは、履歴が複雑な仕様にしたがって管理されている点です。 この履歴管理のおかげで、JavaScriptなしのユーザが、本人の選択かどうかに関わらずアクセスを失うことはありません。 JavaScriptが使えない状況下でもSPAを利用できるようするには、突如としてサーバーサイドレンダリングについて考えなければならなくなることでしょう。


図2. 低速な接続環境下でサンプルアプリが読み込みを行う状況の比較

図2. 低速な接続環境下でサンプルアプリが読み込みを行う状況の比較。左のアプリではページのレンダリングを完全にJavaScriptに依存している。右のアプリではサーバー上でレスポンスをレンダリングしているが、サーバーでレンダリングされたマークアップにコンポーネントを紐付けるため、クライアントサイドでのハイドレーションを利用している。


クライアントサイドのルーターが、ページ上のどのコンテンツに変更があったのかを通知するのに失敗した場合、アクセシビリティも損なわれます。ページ上でどんな変化が起きたかを突き止める支援技術に頼ることができていたのが、困難な仕事になる可能性があります。

さらに現れるのが、昔からの私たちの宿敵: オーバーヘッドです。 クライアントサイドのルーターには小さなものもありますが、React本体React Router、それに状態管理のライブラリ (Redux) まで入れると、どうやっても最適化しようのない約135KBものコードを受け入れることになります。 自分が作っているものが何なのかとともに、クライアントサイドのルーターの利用が、不可避なトレードオフに値するものなのかをよく考えましょう。 多くの場合、無いほうがよいのです。

もし体感的なナビゲーションのパフォーマンスを気にしているなら、同一オリジンからひとりでにドキュメントを取得してくる rel=prefetch に頼るのもアリでしょう。 こうするとキャッシュ上でドキュメントがすぐ利用できるようになるので、ページがロードされる際に感じられるパフォーマンスが劇的に向上する効果があります。 また、プリフェッチは低い優先度で行われるので、帯域に対し致命的なリソースを消費することは少ないです。


図3. プリフェッチの様子

図3. 初期ページ上で writing/ のURLが指すHTMLがプリフェッチされている様子。実際にユーザーが writing/ のURLをリクエストした際は、HTMLはブラウザのキャッシュから即座に読み込まれている。


プリフェッチの主な難点は、それが無駄になりうる可能性を意識していなければならない、という点です。 QuicklinkはGoogleが開発した小さなプリフェッチ用スクリプトですが、現在のクライアントが低速な接続環境下にあるかどうかを何らかの方法でチェックして (またはLiteモードを有効にして) デフォルトでクロスオリジンのプリフェッチを避けることで、この無駄を減らします。

再訪ユーザの体感的なパフォーマンスを改善するには、クライアントサイドのルーティングを利用するかどうかに関わらず、コツを押さえればService Workerもたいへん有益です。Service Workerを使ってルートをプリキャッシュした場合、リンク経由でのプリフェッチと同様の多くの利益が得られますが、そればかりではなく、リクエストとレスポンスに関しはるかに高度なコントロールができるようになります。あなたが自分のサイトを「アプリ」と考えるかどうかに関わらずService Workerを追加することは、おそらく現在もっとも「レスポンシブルな」JavaScriptの用法だといえるでしょう。

JavaScriptでレイアウトの問題を解決するのは止めよう

パッケージを導入してレイアウトの問題解決をしようとしているときは、「自分が成し遂げようとしていることは何なのか?」という自問とともに、注意して進んでください。 それは本来CSSがやるように設計された仕事ですし、効果的に利用するための抽象化も不要です。 たいていのJavaScriptのパッケージが解決しようとするレイアウトの課題は、ボックスの配置、並び、サイズ調整テキストあふれの管理、あるいはレイアウトのシステム全体に至るものまで、現在のCSSなら解決できるのです。 FlexboxやGridのようなモダンなレイアウトエンジンは十分にサポートされており、レイアウトフレームワークを使用してプロジェクトを開始する必要はないはずです。いわば、CSSがフレームワークです。 featureクエリがあれば、新しいレイアウトエンジンを取り入れたプログレッシブなレイアウトの拡張も、たちまちさほど難しいことではなくなります。

/* モバイルを第一に考えた、CSS gridではないレイアウトをここに書く */

/* CSS gridをサポートしない、あるいは@supports文を
   サポートしないブラウザには、@supportsルールは無視される */
@supports (display: grid) {
  /* 大きなスクリーン用のレイアウト */
  @media (min-width: 40em) {
    /* 漸進的なgridレイアウトのスタイルは以下に書く */
  }
}

JavaScriptをレイアウトや表示の問題解決に用いるのは、目新しいことではありません。 2009年、どんなWebサイトもIE6上で当時の先進的な他ブラウザと同じように見えるようにしなければならなかった当時、まさにやっていたことです。 もし2019年の現在も、Webサイトをどのブラウザでも同じように見えるように開発しているんだとしたら、開発の目標を見直すべきでしょう。 モダンで普遍的なブラウザができる全てのことをできないブラウザは、これから先もいつだって存在するし、サポートしていかなければなりません。 全てのプラットフォームで同じような見かけを目指すことは、無駄な追求であるばかりでなく、プログレッシブ・エンハンスメントの最大の敵なのです。

JavaScriptを殺したいわけではない

誤解のないように言っておくと、私はJavaScriptに対して悪感情を持っているわけではありません。 JavaScriptは私にキャリアを与えてくれましたし、自分に正直に述べるならば、10年以上にわたって楽しみの源泉でありつづけてきました。 どんな長期間の関係もそうであるように、時間を共に過ごすほどにJavaScriptのことが分かるようになってきたのです。 年を経る毎により強力でエレガントになり、成熟した、機能豊富な言語だといえます。

それでも、JavaScriptとうまくいかない時だってあるのです。 私JavaScriptに批判的です。 いや、おそらくもっと正確にいうならば、自分たちがJavaScriptをWeb構築の第一の手段とする傾向を作ってきてしまったことに批判的なのです。 いわば、これからクリスマスツリーの飾りつけをしようとライトの束をほどいているところだというのに、Webは既にJavaScriptで酔っぱらってしまった。 私たちはJavaScriptをあまりに多くの用途に使いすぎて、本来必要ないはずの場においても頼ってしまっている。 だからときどき思うのです。この二日酔いはどこまでタチの悪いものになりうるのだろうか、と。

今後の記事においては、押し寄せる過剰なJavaScriptの潮流をせき止め、私たちがWebのために構築したモノをどこの誰でも、少なくとも現状よりも利用できるようにするために、より実践的なアドバイスを展開していこうと思います。アドバイスには予防的なものもあれば、二日酔いを和らげる「迎え酒」的なものもあるはずです。 いずれにしても、願わくば効果は同じものになるでしょう。 私は、自分たちの誰もがみなWebを愛し、正しいことをしたいはずだと信じていますが、Webをより変化に強く、全ての人にとってインクルーシブなものにするために、私たちはどうすればよいかを考えてみてほしいと思います。


Translated with the permission of A List Apart and the author[s].

次の記事
MySQLのコネクションハンドリングとスケーリング
前の記事
MySQLの準同期レプリケーションに関する質問への回答と詳細

同じタグの付いた翻訳済み記事
JavaScript

Feed small 記事フィード

新着記事Twitterアカウント