Stencil FOUC styles should use @layer to avoid cascade conflicts with React 19
メンテナーはふだん 1 日以内に返信
評価
- 難易度
- 2/5
- 見積もり時間
- 1〜3時間
- 初心者へのやさしさ
- 68/100
- issue の種類
- バグ
- 明瞭さ
- 明確に書かれている
- 活発さ
- 静か
- 技術スタック
- css, typescript
調査の方向性
src/runtime/bootstrap-lazy.ts から始め、data-styles 要素がどのように生成され、head に挿入されるかを調べます。Issue で説明されている名前付き @layer に FOUC CSS を包み、その後、React 19 が head のスタイルの順序を変更した場合でも生成されたスタイルの優先度が低いままであること、また hydrated component と consumer の可視性ルールが正しく動作することを検証します。
索引モデルが issue の本文から書いたものです。
説明
Summary
Stencil's FOUC prevention CSS (<style data-styles>) can override consumer CSS when used with React 19, because React 19 reorders <head> elements and changes the cascade order. Wrapping the FOUC styles in @layer would make them lowest-priority by default, fixing the conflict without requiring consumers to patch Stencil.
Current behavior
In bootstrap-lazy.ts, Stencil injects a <style data-styles> tag into <head> with:
market-component-a, market-component-b, ... { visibility: hidden }
[hydrated] { visibility: inherit }
It's inserted via:
head.insertBefore(dataStyles, metaCharset ? metaCharset.nextSibling : head.firstChild);
This relies on source order in <head> — the FOUC styles are placed early so that component-specific and consumer CSS loaded later will override them.
Problem with React 19
React 19 introduced "hoistable" style management — it moves <link rel="stylesheet"> tags to head.firstChild. This pushes <meta charset> down in the head, so when Stencil inserts after metaCharset.nextSibling, the FOUC styles end up after component CSS <link> tags instead of before them.
The result: [hydrated] { visibility: inherit } and consumer class selectors (e.g. .popover { visibility: hidden }) have equal specificity (0,1,0). With the FOUC style now later in source order, it wins — causing components to inherit visibility: hidden from ancestors even after hydration.
This caused a production incident at Square where Market web component popovers became invisible but still captured pointer events, blocking user interaction across the entire dashboard.
Proposed fix
Wrap the FOUC CSS in @layer:
// src/runtime/bootstrap-lazy.ts
- dataStyles.textContent += cmpTags + HYDRATED_CSS;
+ dataStyles.textContent += `@layer stencil-hydration{${cmpTags}${HYDRATED_CSS}}`;
Per the CSS cascade spec, layered styles always lose to unlayered styles, regardless of source order or specificity. This is semantically correct — Stencil's FOUC styles are intentionally low-priority defaults that should yield to any consumer CSS.
Why @layer is the right solution
- Order-independent: Works regardless of where the
<style>tag lands in<head> - Semantically correct: FOUC styles are defaults, not overrides
- No consumer changes needed: Consumers don't need to patch Stencil
- Framework-agnostic: Fixes the issue for React 19 and any future framework that reorders
<head> - Browser support:
@layeris supported in all modern browsers (Chrome 99+, Firefox 97+, Safari 15.4+). Older browsers silently discard@layerblocks — but the FOUC bug only manifests with React 19 which itself requires modern browsers
Workaround
We're currently working around this by patching Stencil's output at runtime with a MutationObserver that wraps <style data-styles> content in @layer stencil-hydration { ... } before MFEs load.
Environment
- Stencil: 4.18.0
- React: 19.x
- Output target:
dist-custom-elements(via@stencil/react-output-target)
- 主要言語
- TypeScript
- スター
- 13.2k
- フォーク
- 865
- 平均マージ
- 2日 16時間
- マージ済み PR(30日)
- 29
環境構築
- Dockerfile・Docker Compose ファイルなし
- プルリクエストのテンプレートあり
- コントリビューションガイドを読む
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
stenciljs/core のほかの issue
-
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
メンテナーはふだん 1 日以内に返信
-
bug(mock-doc): `Element.toggleAttribute()` is missing, so components that use it fail during SSRオープンtriage
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
メンテナーはふだん 1 日以内に返信
-
難易度 3/5 1〜2日 初心者へのやさしさ 45/100
stenciljs/core#7016 · リアクション 1 件 ·
メンテナーはふだん 1 日以内に返信
-
難易度 4/5 3〜5日 初心者へのやさしさ 22/100
メンテナーはふだん 1 日以内に返信
-
難易度 3/5 1〜2日 初心者へのやさしさ 35/100
メンテナーはふだん 1 日以内に返信
似ている issue
-
難易度 2/5 1〜3時間 初心者へのやさしさ 74/100
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 66/100
open-webui/tether#52 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
voidzero-dev/void#835 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
AykutSarac/jsoncrack.com#616 ·
-
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
melgarafael/DeskcommCRM#4679 ·
メンテナーはふだん 1 日以内に返信