SSR 時の style ポリフィルがプロパティ存在チェックを欠いた不具合を修正

shoelace-style/webawesome

SSR 環境で this.style が未定義の場合に、<wa-progress-bar><wa-color-picker> が正しくシリアライズできない問題を解消しました。属性レベルでスタイルを扱うフォールバックロジックを追加し、サーバーサイドでも UI コンポーネントが期待通りに動作するようになっています。

背景

SSR(サーバーサイドレンダリング) ではブラウザの style オブジェクトが利用できないため、WebAwesome は polyfill を通じて属性操作でスタイルを設定していました。webawesome-element.ts の旧実装は this.style が falsy の場合に直接属性を書き換えていましたが、プロパティ名が既に属性に存在するかどうちょうど確認せずに文字列を追加していたため、style が空文字列や末尾にコロンが無いケースで重複や構文エラーが発生していました。この欠陥が原因で <wa-progress-bar> がサーバー側で正しくシリアライズされず、また <wa-color-picker> の初期値が SSR 時に反映されませんでした。

技術的な変更

WebAwesomeElement のスタイルフォールバックロジックを強化

webawesome-element.ts に正規表現 HAS_ENDING_COLON とヘルパー関数 buildStyleAttribute を導入し、属性文字列の組み立てを安全に行うようにしました。

+const HAS_ENDING_COLON = /;\s+$/;
+
+function buildStyleAttribute(options: { property?: string | null; value?: unknown; element: HTMLElement }) {
+  const { property, value, element } = options;
+  if (value) {
+    let style = element.getAttribute('style') || '';
+    if (style) {
+      if (!style.match(HAS_ENDING_COLON)) {
+        style += ';';
+      }
+      style += ' ';
+    }
+    const str = `${property}: ${value}`;
+    if (style.includes(str)) return;
+    return `${style}${str};`;
+  }
+  return null;
+}

setStyle メソッドは this.style が無いケースで上記関数を呼び出し、属性を書き換える処理に置き換えられました。

@@
-    if (!this.style) {
-      if (value != null) {
-        let style = this.getAttribute('style') || '';
-        if (style) {
-          style += ' ';
-        }
-        this.setAttribute('style', `${style}${camelToKebab(property)}: ${value};`);
-      }
-      return;
-    }
+    if (!this.style) {
+      const attr = buildStyleAttribute({ property: camelToKebab(property), value, element: this });
+      if (attr) this.setAttribute('style', attr);
+      return;
+    }

ColorPicker コンポーネントの初期化ロジックを追加

color-picker.ts では状態プロパティの順序を整理し、isEmpty の定義位置を @state() デコレータの下部へ移動しました。また、コンストラクタで handleValueChange('', this.value || '') を呼び出し、サーバー側でも初期値が正しく反映されるようにしました。

@@
-  @state() private isEmpty = true;
   @state() private inputValue = '';
   @state() private hue = 0;
+  @state() private isEmpty = true;
   @state() private saturation = 100;
   @state() private brightness = 100;
   @state() private alpha = 100;
@@
-    this.addEventListener('focusout', this.handleFocusOut);
-  }
+    this.addEventListener('focusout', this.handleFocusOut);
+    // need to set initial values on the server. looks funky, but it works.
+    this.handleValueChange('', this.value || '');
+  }
@@
-  protected willUpdate(changedProperties: PropertyValues<this>): void {
-    super.willUpdate(changedProperties);
-
-    // Its kind of bizarre, but this is required to get SSR to play nicely.
-    // if (changedProperties.has('value')) {
-    //   this.handleValueChange(changedProperties.get('value') || '', this.value || '');
-    // }
+  willUpdate(changedProperties: PropertyValues<this>) {
+    // this is for the server to be honest, but it needs to be here to get properly synced values. Without this, the server just sees nothing for a value
+    if (changedProperties.has('value') || changedProperties.has('defaultValue')) {
+      this.handleValueChange(changedProperties.get('value') || '', this.value || '');
+    }
+    super.willUpdate(changedProperties);
   }

Changelog の記載更新

docs/resources/changelog.md に本 PR で修正された SSR 関連のバグが明示的に記載され、利用者が変更点を把握しやすくなっています。

@@
-:::fixed
+- Fixed a bug in SSR polyfills for the style property. This fixes `<wa-progress-bar>` not properly serializing on the server. [pr:2517]
+- Fixed a bug in `<wa-color-picker>` where initial values in SSR would not be set properly. [pr:2517]

設計判断

属性レベルでのフォールバック を選択したのは、SSR 環境で style オブジェクトが利用できないことを前提に、最小限の侵入で既存コンポーネント全体に適用できる手段を提供するためです。buildStyleAttribute は重複チェックと末尾コロンの補正を行うことで、既存コードの文字列結合ロジックを安全に置き換え、将来の属性操作におけるバグリスクを低減します。さらに WaColorPicker の初期化ロジックをコンポーネント内部に集約したことで、SSR でも状態同期が保証され、外部からの特別な呼び出しが不要になりました。これらの変更は API 互換性を保ちつつ、SSR の信頼性を根本的に向上させています。

まとめ

今回の PR では、SSR 時に style が未定義になるケースを想定した安全な属性操作ロジックと、カラー ピッカーの初期化をサーバー側で確実に行う仕組みを導入しました。結果として <wa-progress-bar><wa-color-picker> がサーバーサイドでも正しく描画・状態同期できるようになり、開発者は SSR 環境での挙動を意識せずにコンポーネントを利用できます。

記事メタデータ

Generated by:
gpt-oss-120b for DiffDaily
LLM Trace:
366651ce

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

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
gpt-oss-120b for DiffDaily

Review Criteria:

記事構成 ✓ PASS

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

リード文、背景、技術的変更、設計判断(任意)、まとめが明確に分かれており、総論→各論→結論の構成が保たれています。

カスタムMarkdown構文 ⚠ WARNING

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

コードブロックはファイル名付きシンタックスハイライトが正しく使用されていますが、PR リンクのテキストが "PR #2517" となっており、要件の "[#2517](URL)" 形式と完全に一致していません。

対象読者への適合性 ✓ PASS

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

専門的な Web コンポーネントや SSR の知識を前提とした記述で、初心者向けの過度な説明はありません。

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

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

各セクションは総論パラグラフ→各論パラグラフ→結論パラグラフで構成され、段落はトピックセンテンスで始まり、1段落1トピック、長さも適切です。

Diff内容との照合 ✓ PASS

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

記事内のコードブロックは提供された Diff と完全に一致しており、追加・削除箇所も正確に示されています。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

SSR、polyfill、属性操作、style などの用語が正しく使用され、PR の内容と一致しています。

説明の技術的正確性 ✓ PASS

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

技術的な説明は Diff に基づく根拠があり、因果関係も論理的です。

事実の突合 ✓ PASS

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

全ての主張は PR タイトル、説明、Diff で裏付けられており、捏造や推測はありません。

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

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

PR 番号 #2517 などの数値は正確です。

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

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

記事タイトルは PR タイトルの内容を適切に日本語化しており、一致しています。

外部知識の正確性 ✓ PASS

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

バージョンサポートやリリース情報など、PR に記載されていない外部知識は含まれていません。

時間表現の正確性 ✓ PASS

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

時間表現の歪曲はなく、PR の記述と整合しています。