はじめに

プログレッシブエンハンスメントは、Webデザインの手法で、まず機能のミニマリストかつ堅牢なバージョンを作成します。つまり、2000年代のブラウザでも使用可能なものです。その後、その上に現代的な機能を段階的に追加していきます。

これを達成するために、まず最小限かつ標準的なHTMLコードでページを書き、同様に最小限かつ標準的な指示を備えたCSSスタイルシートを関連付けます。これにより、接続が不十分でJavaScriptが無効化されていても、瞬時に読み込まれる機能的なページが得られます。

ユーザーエクスペリエンスを向上する非必須機能(フォント、インタラクティブな動作、ビジュアルエフェクト)は段階的に追加され、これらの強化機能が有効でなくても基本的なページ機能が動作するようにします。

2024年の現状

プログレッシブエンハンスメントを使用する一般的に受け入れられている動機は、悪化した状況でも許容できるユーザーエクスペリエンスを提供することです。

しかし2024年には、悪化した状況での少数の人々の体験を気にせずにサービスを開始することが完全に可能です。このため、20年以上にわたり多くの開発者がJavaScriptを有効にすることを要求し、一般にユーザーがサイトと対話できるようになる前に大量のJavaScriptをダウンロードする必要がある技術を選んできました。

このアプローチは疑いなくビジネスの成功を許します。たとえ一部のユーザーが取り残されたとしても、それがサービスの成功を妨げるわけではありません。

もちろん、私は誰も取り残さないことを開発における方針としていますが、それに比べても私にはこのアプローチを採用するもっと重要な理由があります。

プログレッシブエンハンスメントの理由

正しい理由とは、あなた自身を納得させるか、またはチーム全体や役員を説得してこのアプローチに切り替えさせる理由です。

全ユーザーの対象を98%から99%に引き上げられると言ってこれらすべての人々を説得しようとしているならば、それが非常に確立された多国籍企業でない限り、成長のための最後のレバーとなる可能性があります。そうでない場合、納得させる可能性は低いです。

それでも、特定の大部分のユーザーが時折悪化した状況に直面する可能性があると納得させても(公共の交通機関で携帯電話を使用する人を考えてみてください)、それで彼らの優先度が変わるわけではありません。

しかし、私にとって最も興味深いことは、この技術がよりシンプルで維持しやすいコードに繋がることです。

プログレッシブエンハンスメントはコードを改善する

私にとって良い理由は、このアプローチがコード品質を改善し、アプリケーション内の多くの問題を回避することです。それをここで説明します。

実際のデリバラブル媒体で — Webで — サイクル/イテレーション/スプリント作業を行い、根本にプログレッシブエンハンスメントを取り入れることにより — 私が約束します — コードベースは小さく、UIはシンプルになり、ユーザーは幸福になります!

Andy Bell

JavaScriptの量を減らします

まず、JavaScriptは既存の体験を強化するためだけに使用します。 このようにして、その体験はWebの標準機能だけで実現されます。 デフォルトでは、リンクとフォームを使用してサーバーと通信します。大部分の機能ではこれで十分で、満足のいく体験を実現するために何も追加する必要はありません。

しかし、時には体験を最適化したい場合もあります。例えば、オートコンプリート付きのフィールドを考えます。 この原則は、入力中に提案リストを表示することです。 このような機能はJavaScriptなしでは実現不可能です。JavaScriptを頻繁に要求します、各入力で動作をトリガーする必要があるためです。サーバーとのやり取りが必要な場合、単純なHTMLでは不十分です。

しかし、プログレッシブエンハンスメントの原則に従ってこの種の機能を実装することで、コードアーキテクチャが簡素化されます。

JavaScriptなしでこのような機能がどのように見えるかを想像してみてください。 入力フィールドと最初は空の提案リストを表示できます。 ユーザーが入力するたびに、提案を確認したいと思います。 これを行うために、提案を瞬時に表示する代わりに、ユーザーは専用の提案読み込みボタンをフォームで有効化して表示することができます。 JavaScriptなしの場合、リストを含むページ全体がリロードされます。 ユーザーはリストからアイテムを選び、フォームを最終的に完成させます。

class Order < ApplicationRecord
  belongs_to :product
  attr_accessor :product_name
end

class Product < ApplicationRecord
  def self.search(query)
    return none if query.blank?

    where('name like :query', query: "#{query}\%")
  end
end
class OrdersController < ApplicationController
  def new
    @order = Order.new
  end

  def create
    @order = Order.new(params[:order].permit!)

    if params[:commit] && @order.save
      redirect_to @order
    else
      render :new
    end
  end
end
<%= form_with model: @order do |f| %>
  <fieldset>
    <legend>Todo</legend>

    <%= f.text_field :name %>

    <%= f.text_field :product_name,
                     list: 'product-suggestions',
                     value: params.dig(:order, :product_name) %>

    <datalist id="product-suggestions">
      <% Product.search(@order.product_name).each do |product| %>
        <%= content_tag :option, '', value: product.name %>
      <% end %>
    </datalist>

    <%= f.submit 'Search', name: 'autocomplete' %>
  </fieldset>

  <%= f.submit %>
<% end %>

その後、この体験を強化するためにJavaScriptを使用できます。このアプローチを使用する利点は、追加するJavaScriptが非常に少ないことです。Hotwire Turboのようなテクノロジーを使用することで、自分でJavaScriptを追加する必要さえなく、フォームでほんの少し設定するだけです。JavaScriptはボタンを非表示にし、入力に基づいた動作をトリガーします。この動作にはリストの読み込みが含まれ、次に現在のページでリストが置き換えられます。リストの読み込みは、更新された現在のページを取得することで行われます(サーバー部分の更新を要求せず)、または提案を含むページの部分的なバージョンを取得します。

注目すべきは、この例では、コントローラーが商品提案を表示するために適応を必要とせず、params[:commit]が定義されているかどうかをチェックしてインタラクティブな動作を処理するだけです。

このアプローチがアプリケーション内のJavaScript量をどのように減らすことができるかを示しました。次に、JavaScriptの削減がどのようにアプリケーションの保守を容易にするかを見ていきます。

コードの保守が容易になります

Railsアプリケーションでは、JavaScriptコードはWebブラウザでの実行を必要とするため、テストが難しく、複雑になります。一方で、バックエンドのコードはサーバー上でブラウザを介さずにネイティブに実行されるため、テストが簡単です。このため、JavaScriptコードをテストする際はテストの実行が長くなります。

プログレッシブエンハンスメントを使用すると、生成されるJavaScriptコードはより汎用的でテストが容易になります。ビジネスロジックへの依存がなくなり、コードはアプリケーション内の他の場所で再利用される可能性があります。この再利用のスノーボール効果により、アプリケーション内のJavaScriptコードがさらに削減され、正のスパイラルが生まれます。

もちろん、JavaScriptコード(より正確にはブラウザで直接実行されるコード)が本質的に問題であるわけではありません。ただ、その系統的な使用がシステムを複雑にします。この複雑性を減らすことで、アプリケーションのメンテナンス性が向上します。

第2の例

テキストをクリップボードに自動でコピーするためには、JavaScriptが必要です。

しかし、JavaScriptなしで同等の機能を設計することができます。具体的には、コピーしたいテキストを表示し、ユーザーに自分でオペレーティングシステムの機能を使ってコピーするよう提案することです。

<% if @order.product %>
  URL <%= text_field_tag '', url_for(@order.product) %>
<% end %>

これはユーザーのニーズを満たします。この提案された解決策は非常に基本的ですが、驚くほど効果的です。

その後、この基本機能を拡張して、テキストの選択とコピーが自動で行われるようにJavaScriptを用いることができます。

その原則は、テキストを(オプションで)非表示にしながら、ページのコード内に保持し、ユーザーのクリップボードにコピーをトリガーするボタンと、成功メッセージを表示することです。

したがって、JavaScriptが利用できない場合、サイトは単純にテキストを表示し、自分でコピーするように指示メッセージを表示します。機能は依然としてアクセス可能です。そしてJavaScriptが利用可能になると、ユーザーはテキストを自動でコピーする単純なボタンを目にすることができます。

この例でも原則は同じです:JavaScriptなしで機能的な基本バージョンを作成し、JavaScriptを用いてユーザー体験を強化してインタラクティビティを追加することです。先述した利点すべてがここでも見られます:機能は常にアクセス可能であり、拡張されたバージョンは再利用可能で、容易に保守でき、非常に少ない追加コードが必要です。

複雑なほど利点が大きくなる

前述の例は非常に簡単でしたが、このアプローチの興味を示すのに十分でした。

サーバーとのインタラクションを追加することで、もう少し例を押し進めてみましょう。

今度は、ユーザーがオートコンプリート機能を利用してデータベースから商品を名前で選択することができるようにします。その後、顧客にメールで送信するためにその商品のリンクをコピーしたいと考えるでしょう。

最初の例と同様に、ユーザーの入力はサーバーに送信され(フォームボタンを介して、または自動的に)、ブラウザは結果のリストを取得します。

今回は、ユーザーが結果を選択した際に、その結果に対応するリンクをコピーするオプションを提供したいと思います。

これを行うために、同じアプローチを使用してフォームに「リンクを表示する」ボタンを追加してインタラクションをトリガーします。商品が選択されると、ユーザーはこのボタンを有効化し、リンクが含まれるページがリロードされ、ユーザーはそれをコピーすることができます。

ここでも、商品が選択された際にこの動作を自動的にトリガーすることで、ユーザー体験を向上させることができます。その後、ユーザーはリンクをクリップボードにコピーするためのボタンを目にします。

JavaScriptがない場合、ユーザーはフォームのボタンを使用してインタラクティブな動作をトリガーします。JavaScriptがある場合、動作はユーザーのアクションによって自動的にトリガーされ、より自然な体験ができます。

私たちのアプローチでは、これらのボタンを既存のフォームに追加しています。サーバーが情報を取得し、ユーザーの入力が新しいページにリロードされた際も保持されるようにするために、これを行うことが重要です。JavaScriptでは、ページを部分的にリロードして問題を回避することができますが、JavaScriptがない場合、ページのリロードは必要不可欠です。

また、各動作には独自のボタンがあります。このアプローチにより、異なる動作の再利用や組み合わせができます。サーバーが必要に応じて適応できる場合もありますが、通常は、どのボタンを有効化しても、利用可能なすべてのインタラクションがトリガーされます。

ブラウザの通常のリクエスト/レスポンスプロセスを利用することで、エラー管理がより適切に行えます。例えば、サーバーエラーやネットワーク問題でページの読み込みができなくなった場合、デフォルトのJavaScriptインタラクションでは単に失敗してしまうかもしれないところで、クライアントに明確なメッセージが表示されます。

別のアプローチを取っていた場合、同時に動作を組み合わせ、サーバーにリクエストを送り、JavaScriptでしか処理できない形式のレスポンスを使用する方が自然だと考えられたかもしれません。最終的に、その結果は堅牢性が低く、信頼性が低く、メンテナンスしづらいものであったと思われます。

結論として、この例は、ブラウザ内で非常に少ないJavaScriptコードを追加することで複雑なインタラクションを設計するためにこのアプローチを容易に使用できることを示しています。しかし、クライアント側で節約できる分、サーバー側で必要な対応が増えます。

たしかに、フォームは複数の機能的ニーズに対応しなければならない:

  • 異なるアクション(オートコンプリート、リンク表示、最終バリデーション)を区別する。
  • フォームを最終的にバリデートしない場合、ユーザー入力を保持しながら再表示し、関連するオブジェクトを実際に作成しない。
  • 商品が選ばれた際、サーバーが返すページは、その商品のリンクをコピーするための設定と、クリップボードコピーJavaScript動作を有効化する設定を含んでいなければならない。

これらの追加動作をハンドリングするためのサーバー側の適応が確かに必要です。しかし、これは管理可能な欠点です:

  • 適応は実際にはシンプルに実装でき、ビューコードへのシンプルな追加が異なるインタラクションのための新しいデータを伝えるのに十分です。
  • サーバーが受け取ったパラメーターでフォームを再活性化させ、入力を維持します。
  • 個々のアクションを区別する必要はなく、最終的なバリデーションと単なるインタラクション要求を区別するだけで十分です。このアプローチは簡単に一般化され、追加コードの量はインタラクティブな動作の数に応じて増加することはありません(すべて1つのコントローラー変更でハンドルされます)。
  • 別のアプローチであってもサーバー側の適応が必要です(たとえば、リストやコピーするリンクを返すためのAPI)。プログレッシブエンハンスメントが要求する適応はより汎用的で、したがって保守しやすいです。

このアプローチは機能の種類に依存せず、どの種類のインタラクティブな動作にも一般化できます。さらに、機能自体が汎用的であり、異なるコンテキストで再利用でき、ユーザー環境の品質に応じた最適な体験を提供するために組み合わせることができます。

結論

プログレッシブエンハンスメントは新しい概念ではありませんが、依然として重要です。標準のウェブ技術を使用して必要なすべての機能を設計し、JavaScriptを追加することでユーザー体験を豊かにすることができます。特にモバイルでユーザーが悪化した状態を体験することを考慮せずに多くのサービスが開始されているにもかかわらず、プログレッシブエンハンスメントを採用することで、より広範囲のユーザーにリーチし、満足のいくユーザー体験を提供することができます。

このアプローチの利点は多数あります。JavaScriptを削減することで、コードは軽量でシンプルになり、保守が容易になります。メンテナンスが容易になるだけでなく、JavaScriptコード自体がより汎用的で再利用可能になります。この方法は、標準のブラウザのリクエスト/レスポンスサイクルを使用することで信頼性が高くなり、デフォルトでエラーハンドリングが改善されます。

最後に、プログレッシブエンハンスメントを採用することは、サーバー側の調整を要求しますが、それらはシンプルで汎用的であり、コードのスケーラビリティと保守性を促進します。これらの原則を統合することで、開発者は高性能でアクセス可能なアプリケーションを設計することが可能になります。