👌

Plasmo CSUI + Tailwind CSSで他サイトの影響を受けない方法

に公開

Plasmo CSUI + Tailwind CSSで他サイトの影響を受けない方法

はじめに

Chrome拡張機能の開発でPlasmoとTailwind CSSを組み合わせて使っていると、特定のサイトで拡張機能のUIが意図しない表示になってしまうことがあります。

「CSUIを使ってるからShadow DOMでスタイルは完全に分離されているはず...」と思っていたのに、なぜかあるサイトではheaderの高さが40pxではなく25pxになってしまう。そんな経験はありませんか?

この記事では、Chrome拡張機能のContent ScriptsでTailwind CSSを使用する際に発生するCSS継承問題と、その根本的な解決方法を解説します。PlasmoのCSUIを例に説明しますが、他のContent Scripts実装でも同様の問題と解決策が適用できます。

問題の発生状況

期待する動作

// PlasmoOverlay.tsx
const PlasmoOverlay = () => {
  return (
    <div className="fixed top-4 left-4 bg-white shadow-md">
      <header className="h-10 border-b flex items-center px-4">
        <h1>拡張機能</h1>
      </header>
    </div>
  )
}

Tailwindのh-10クラスはheight: 2.5rem、つまり40pxになるはずです。

実際の問題

しかし、あるサイトで上記のコンポーネントを表示すると、headerの高さが25pxになってしまいます。

開発者ツールで確認すると:

  • Tailwindのh-10height: 2.5remとして適用されている
  • しかし実際の高さは25px

原因の究明

Content Scriptsの制約

まず、なぜこの問題が発生するのかを理解するために、Content Scriptsとiframeの違いを確認しましょう。

iframe拡張機能の場合:

<!-- 完全に独立したドキュメント -->
<iframe src="chrome-extension://abc/popup.html">
  <html> <!-- 独自のhtml要素 -->
    <head><title>拡張機能</title></head>
    <body>拡張機能のUI</body>
  </html>
</iframe>
  • 完全に分離されたhtml
  • ✅ サイトのCSS影響なし
  • ❌ 開発が複雑(postMessage通信が必要)

Content Scripts(CSUI)の場合:

<!-- サイトと同じhtmlドキュメント内 -->
<html style="font-size: 62.5%;"> <!-- Yahooのhtml -->
  <body>
    <div>サイトのコンテンツ</div>
    <plasmo-csui> <!-- ここに注入される -->
      #shadow-root <!-- 部分的な分離 -->
        <div class="h-10">拡張機能のUI</div>
      #shadow-root
    </plasmo-csui>
  </body>
</html>
  • サイトのhtmlに依存
  • ❌ 継承プロパティの影響あり
  • ✅ 開発が簡単(直接DOM操作可能)

Content Scriptsはサイトと同じhtmlドキュメント内で動作するため、拡張機能側でサイトのhtml要素を変更することはできません。

なぜCSUIを選ぶのか

Reactらしい開発体験:

// CSUI - 普通のReact開発と同じ
const PlasmoOverlay = () => {
  const [count, setCount] = useState(0);
  
  const handleClick = () => {
    // 直接ページ操作
    const element = document.querySelector('.target');
    element.style.border = '2px solid red';
    setCount(count + 1);
  };
  
  return (
    <button onClick={handleClick}>
      クリック数: {count}
    </button>
  );
};

iframeだと:

// iframe - 通信処理が必要
const IframeComponent = () => {
  const [count, setCount] = useState(0);
  
  const handleClick = () => {
    // 複雑な通信処理
    parent.postMessage({
      type: 'STYLE_ELEMENT',
      selector: '.target',
      styles: { border: '2px solid red' }
    }, '*');
    setCount(count + 1);
  };
  
  return <button onClick={handleClick}>クリック数: {count}</button>;
};

PlasmoがCSUIをデフォルト選択している理由は、Reactらしい直感的な開発を可能にするためです。軽微なCSS問題と引き換えに、大幅な開発効率向上を実現しています。

Shadow DOMでも継承される理由

PlasmoのCSUIはShadow DOMを使用してスタイルを分離していますが、CSS仕様上、継承可能なプロパティはShadow DOM境界を越えて継承されるという特性があります。

特にfont-sizeは継承プロパティのため、以下のような影響を受けます:

Yahooサイトの設定:

html {
  font-size: 62.5%; /* 16px × 62.5% = 10px */
}

remの計算への影響:

  • 通常:2.5rem = 2.5 × 16px = 40px
  • Yahoo:2.5rem = 2.5 × 10px = 25px

Shadow DOM内でも影響を受ける継承プロパティ

  • color
  • font-family
  • font-size今回の問題
  • line-height
  • visibility
  • など

継承されないプロパティ

  • background
  • border
  • margin
  • padding
  • width
  • height(直接指定の場合)

解決策:postcss-rem-to-responsive-pixel

この問題を根本的に解決するには、Tailwind CSSが生成するrem単位をpx単位に変換します。

パッケージのインストール

pnpm add -D postcss-rem-to-responsive-pixel

PostCSS設定の変更

// postcss.config.js
/**
 * @type {import('postcss').ProcessOptions}
 */
module.exports = {
  plugins: {
    tailwindcss: {},
    'postcss-rem-to-responsive-pixel': {
      rootValue: 16,        // 1rem = 16px
      propList: ['*'],      // 全プロパティ変換
      transformUnit: 'px',  // px出力
      replace: true,        // remを置き換え
      mediaQuery: true      // メディアクエリ内も変換
    },
    autoprefixer: {}
  }
}

変換結果

変換前(Tailwind生成CSS):

.h-10 {
  height: 2.5rem;
}

変換後(postcss-rem-to-responsive-pixel適用後):

.h-10 {
  height: 40px;
}

設定オプションの解説

重要な設定項目

  • propList: ['*']: デフォルトは['font', 'font-size', 'line-height', 'letter-spacing']のみですが、['*']で全プロパティを変換
  • rootValue: 16: 1remの基準値(通常は16px)
  • transformUnit: 'px': 出力単位をpxに指定
  • replace: true: remを完全に置き換える

他の解決策との比較

1. CSS上書きによる対処

:host {
  font-size: 16px !important;
}

:host .h-10 {
  height: 40px !important;
}

デメリット:

  • 個別対応が必要
  • メンテナンスが困難
  • すべてのTailwindクラスに対応できない

2. Tailwind設定でpx化

// tailwind.config.js
module.exports = {
  theme: {
    spacing: {
      '10': '40px', // remの代わりにpx
      // 全サイズを個別定義...
    }
  }
}

デメリット:

  • 設定が膨大になる
  • Tailwindのアップデートに追従困難

3. PostCSSプラグインによる変換(推奨)

メリット:

  • 設定が簡単
  • 全自動変換
  • メンテナンス不要
  • Tailwindのアップデートにも対応

検証:実際の効果

変換前の問題

/* Yahooサイトでの実際の値 */
.h-10 { height: 2.5rem; } /* = 25px (10px × 2.5) */
.p-4 { padding: 1rem; }   /* = 10px (10px × 1) */

変換後の解決

/* どのサイトでも一定 */
.h-10 { height: 40px; }   /* = 40px */
.p-4 { padding: 16px; }   /* = 16px */

まとめ

PlasmoのCSUIは優れたスタイル分離機能を提供しますが、CSS仕様上の継承プロパティについては完全な分離ができません。特にfont-sizeの継承によるrem計算への影響は、拡張機能の表示に予期しない問題を引き起こします。

postcss-rem-to-responsive-pixelを使用してrem単位をpx単位に変換することで:

  • ✅ サイト依存のCSS問題を根本解決
  • ✅ 設定が簡単で保守性が高い
  • ✅ Tailwind CSSの利便性を維持
  • ✅ どのサイトでも一貫した表示

Chrome拡張機能開発でPlasmo + Tailwind CSSを使用している方は、ぜひこの対策を導入してみてください。

参考リンク

Discussion