Service Workerでファイルアップロードをモックし、デモページに添付ファイル機能を追加

basecamp/lexxy

GitHub Pages上で動作するLexxyの「Try it」デモページに、Service Workerを使ってActive StorageのDirect Uploadエンドポイントをモックすることで、サーバーなしにファイルアップロードの完全なライフサイクルを体験できるようにした変更です。あわせて、:トリガーで起動する絵文字ピッカーも追加されました。

背景

これまで「Try it」ページではattachments="false"属性によって添付ファイル機能が無効化されており、「Attachment uploads are not supported in this demo」という注記が表示されていました。GitHub Pagesは静的ホスティング環境であるため、Active StorageのDirect Uploadが必要とするサーバーサイドのエンドポイントを実装できず、アップロード機能をそのまま有効化できなかったことが背景にあります。

この制約を解消するために採用されたのが、Service Workerによるエンドポイントのモックです。サーバーを一切変更せず、ブラウザ内でActive Storage互換のAPIレスポンスを再現することで、デモ環境の制約を克服しています。

技術的な変更

docs/attachments-sw.jsとして追加されたService Workerが、Active StorageのDirect Uploadに必要な3つのHTTPエンドポイントをインターセプトします。具体的には以下のリクエストを処理します:

  • POST /rails/active_storage/direct_uploads — Blobメタデータの登録とアップロード先URLの発行
  • PUT /rails/active_storage/disk/:signed_id — ファイル本体の保存(Mapオブジェクトにバイナリを格納)
  • GET /rails/active_storage/blobs/:signed_id/:filename — 保存済みファイルの配信

createBlob関数では、demo-signed-id-${++blobCounter}形式の署名付きIDを生成し、direct_upload.urlとしてディスクアップロード先を返します。これにより、Lexxy側のアップロードクライアントは本物のActive Storageと同じフローで動作します。

async function createBlob(request) {
  const { blob } = await request.json()
  const signedId = `demo-signed-id-${++blobCounter}`

  files.set(signedId, { metadata: blob })

  return new Response(JSON.stringify({
    id: blobCounter,
    key: `demo-key-${blobCounter}`,
    filename: blob.filename,
    content_type: blob.content_type,
    byte_size: blob.byte_size,
    checksum: blob.checksum,
    signed_id: signedId,
    attachable_sgid: `demo-sgid-${blobCounter}`,
    direct_upload: {
      url: `/rails/active_storage/disk/${signedId}`,
      headers: { "Content-Type": blob.content_type },
    },
  }), { status: 200, headers: { "Content-Type": "application/json" } })
}

docs/try-it.md側では、attachments="false"を削除し、data-direct-upload-urldata-blob-url-template属性を追加してActive Storage互換のURLを指定するよう変更されました。Service Workerの登録はnavigator.serviceWorker.register()1行で行われます。

さらに、<lexxy-prompt>コンポーネントを用いた絵文字ピッカーが追加されました。trigger=":"属性により:入力で起動し、insert-editable-text属性によって選択した絵文字がそのままエディタに挿入されます。各<lexxy-prompt-item>にはsearch属性でキーワードが定義されており、インクリメンタルサーチに対応しています。

設計判断

ブラウザのService Worker APIを使うことで、デモ用のモックサーバーやプロキシを別途用意することなく、静的ホスティング環境の制約を完全にブラウザ内で解決している点が注目されます。ファイルはページのライフタイム中のみMapオブジェクトに保持されるため、永続化は行われませんが、デモ用途としてはプログレスバーの表示やアタッチメントレンダリングを含む完全なアップロードフローを確認するには十分です。

Active StorageのAPIレスポンス形式(signed_idattachable_sgiddirect_uploadオブジェクトなど)を忠実に再現しているため、Lexxyのクライアントコードはモック環境であることを意識せずに動作します。これはLexxyとActive Storageのインターフェース仕様をそのままデモのモック仕様として利用した設計といえます。

まとめ

Service WorkerでActive Storage互換のエンドポイントをモックするというアプローチにより、静的ホスティング環境の制約を乗り越えてデモページの機能を大幅に拡充しています。本番環境のクライアントコードを一切変更せずにデモを強化できる点は、Lexxyのコンポーネント設計がサーバー実装から適切に分離されていることの証左でもあります。

記事メタデータ

Generated by:
Claude Sonnet 4.6 for DiffDaily
LLM Trace:
1e73b442

この記事はAIによって自動生成されています。内容の正確性については、必ずソースコードやPRを確認してください。

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
Gemini 2.5 Pro for DiffDaily

Review Criteria:

記事構成 ✓ PASS

Title, Context, Technical Detailの存在と明確さ

リード文(総論)→背景・技術詳細・設計判断(各論)→まとめ(結論)という「総論→各論→結論」の3部構成が明確に適用されています。必須要素はすべて満たされており、構成は非常に優れています。

カスタムMarkdown構文 ✓ PASS

シンタックスハイライト・GitHubリンク記法の正確性

ファイル名付きシンタックスハイライト(```javascript:docs/attachments-sw.js)およびGitHubのPRリンク記法([#832](URL))が正しく使用されています。

対象読者への適合性 ✓ PASS

エンジニア向けの適切な技術レベルと表現

Service Worker、Active Storage、Direct Uploadといった用語を前提としており、専門知識を持つエンジニアという対象読者に適した技術レベルと表現で書かれています。

パラグラフ・ライティング ✓ PASS

トピックセンテンス・1段落1トピック・段落長

各セクションが総論→各論の構成になっており、各段落はトピックセンテンスで始まっています。1段落1トピックの原則が守られ、段落長も適切であるため、非常に読みやすい文章構造です。

Diff内容との照合 ✓ PASS

コードブロックとDiff内容の一致

記事に引用されている`createBlob`関数のコードブロックは、提供されたDiff内の`docs/attachments-sw.js`の内容と正確に一致しています。ファイル名の指定も正確です。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

Service Worker, Active Storage, Direct Upload, signed_id, attachable_sgidなど、PR情報や文脈に沿った技術用語が正確に使用されています。

説明の技術的正確性 ✓ PASS

技術的主張の正確性と論理性

Service Workerが3つのエンドポイントをインターセプトする仕組みや、`createBlob`関数の役割など、技術的な説明はすべてDiff内のコードと整合性が取れており、正確です。

事実の突合 ✓ PASS

PR情報による主張の裏付け(ハルシネーション検出)

記事内の主張はすべてPRのDescriptionやDiffの内容に基づいています。「GitHub Pagesは静的ホスティング環境」という補足はPR外の一般知識ですが、変更の背景を説明するための妥当な文脈情報であり、ハルシネーションには該当しません。

数値・固有名詞の確認 ✓ PASS

PR番号・コミットID・バージョン等の正確性

PR番号(#832)やコード内で言及されている属性名(data-direct-upload-urlなど)はすべて正確に記載されています。

タイトル・説明との一致 ✓ PASS

記事タイトル・説明とPR内容の一致

記事のタイトルは、PRの主題である「デモページでの添付ファイル機能の有効化」を技術的アプローチ(Service Workerによるモック)と共に的確に表現しており、PR内容と完全に一致しています。

外部知識の正確性 ✓ PASS

PRに記載のない外部知識(LTS、サポート状況など)の不使用

記事には、バージョンのサポート状況やリリース日程など、PR情報に基づかない外部知識の捏造は見られません。

時間表現の正確性 ✓ PASS

時間表現がPR情報と一致しているか

「これまで無効化されていた」といった過去の状況や、今回の変更内容を説明する際の時制は、PRの文脈と一致しており正確です。