SSR 時の style ポリフィルがプロパティ存在チェックを欠いた不具合を修正
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 環境での挙動を意識せずにコンポーネントを利用できます。