有没有办法将 tailwind 类注入到影子元素中,以便 React 组件获得正确的样式?

问题描述 投票:0回答:1

背景: 我正在使用 React、Typescript、Tailwind 和 Craco 构建一个 chrome 扩展。 Tailwind 类在 chrome 扩展弹出窗口中正确应用,但我也希望能够通过内容脚本将 React 组件添加到网页上。 (我正在使用 example.com 来测试我的组件注入)

问题: 我正在动态渲染一个shadow-root(以便将任何样式与页面的其余部分隔离)并在shadow-root内渲染React组件。组件的 html 结构和 js 工作正常,但是应用的 tailwind 没有被渲染,即使它们在组件中被引用。

// Test.tsx
import React, { FC, useState } from "react";

const Test: FC = () => {
  const [msg, setMsg] = useState<string>("");
  
  return (
    <div className="bg-purple-300" onClick={() => setMsg("i have been clicked")}>
      {msg.trim().length > 0 ? <p>{msg}</p> : <p className="italic">TEST TEST TEST TEST</p>}
    </div>
  )
}

export default Test;
//content.ts
const shadowRoot = document.createElement('div').attachShadow({ mode: 'open' });
const tailwindSheet = new CSSStyleSheet();
tailwindSheet.replaceSync(`
  .bg-purple-300 {
    background-color: #d8b5e5;
  }
`);
shadowRoot.adoptedStyleSheets = [tailwindSheet];
ReactDOM.render(React.createElement(Test), shadowRoot);
document.body.appendChild(shadowRoot.host);

我不确定这是否重要,但是:

//craco.config.js
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
  webpack: {
    configure: (webpackConfig, { env, paths }) => {
      return {
        ...webpackConfig,
        entry: {
          main: [
            env === "development" &&
              require.resolve("react-dev-utils/webpackHotDevClient"),
            paths.appIndexJs,
          ].filter(Boolean),
          background: paths.appSrc + "/scripts/background.ts",
          content: paths.appSrc + "/scripts/content.ts"
        },
        output: {
          ...webpackConfig.output,
          filename: "static/js/[name].js",
        },
        optimization: {
          ...webpackConfig.optimization,
          runtimeChunk: false,
        },
        plugins: [
          ...webpackConfig.plugins,
          new HtmlWebpackPlugin({
            inject: true,
            chunks: ["options"],
            template: paths.appHtml,
            filename: "options.html",
          }),
        ],
      };
    },
  },
};

manifest.json:

{
  "name": "Custom Chrome Extension",
  "description": "Template for creating Crome extensions with React",
  "version": "1.0",
  "manifest_version": 3,
  "action": {
      "default_popup": "index.html",
      "default_title": "Open the popup"
  },
  "icons": {
      "16": "logo192.png",
      "48": "logo192.png",
      "128": "logo192.png"
  },
  "permissions": ["activeTab", "scripting"],
  "host_permissions": ["https://*/*", "http://*/*"],
  "background": {
    "service_worker": "static/js/background.js",
    "type": "module"
  },
  "content_scripts": [
    { 
      "matches": ["https://*/*", "http://*/*"],
      "js": ["static/js/content.js"]
    }
  ]
}

我知道问题在于需要将 tailwind 实用程序类添加到影子根目录中,以便可以从注入的组件访问这些类。

  1. 我尝试使用 tailwind cdn 添加脚本标签:
const shadowRoot = document.createElement('div').attachShadow({ mode: 'open' });
const twScript = document.createElement('script');
twScript.src = "https://cdn.tailwindcss.com";
shadowRoot.appendChild(twScript);
ReactDOM.render(React.createElement(Test), shadowRoot);
document.body.appendChild(shadowRoot.host);

脚本元素永远不会被渲染,我想可能是因为它违反了内容安全策略

  1. 我的另一个想法是尝试使用 fetch 来获取原始的 tailwind 类定义作为一个巨大的字符串,然后将其用作 ReplaceSync() 的参数,但我无法找到可以从中获取的网站。
const shadowRoot = document.createElement('div').attachShadow({ mode: 'open' });
fetch('magicurlthatgivesmetailwindcss').then(response => response.text()).then(cssText => {
  const tailwindSheet = new CSSStyleSheet();
  tailwindSheet.replaceSync(cssText);
  shadowRoot.adoptedStyleSheets = [tailwindSheet];
  ReactDOM.render(React.createElement(Test), shadowRoot);
  document.body.appendChild(shadowRoot.host);
});
  1. 我的最后一个想法是在渲染它们之前以某种方式提取我在 React 组件中使用的 tailwind 类的名称,然后使用 css 类定义手动生成一个字符串。然后我可以使用该字符串作为replaceSync() 的参数来预加载原始CSS。我什至不知道如何开始做这件事,或者是否可能。

到目前为止,replaceSync() 是在网页上获取 CSS 样式的唯一成功方法:

const shadowRoot = document.createElement('div').attachShadow({ mode: 'open' });
const tailwindSheet = new CSSStyleSheet();
tailwindSheet.replaceSync(`
  .bg-purple-300 {
    background-color: #d8b5e5;
  }
`);
shadowRoot.adoptedStyleSheets = [tailwindSheet];
ReactDOM.render(React.createElement(Test), shadowRoot);
document.body.appendChild(shadowRoot.host);

reactjs typescript google-chrome-extension tailwind-css craco
1个回答
0
投票

由于shadcnui,我也遇到了同样的问题, 我正在使用 ViteReact 但我确信这不是您面临这个问题的原因。

使用 Vite 和 React 渲染内容

你可以看到我渲染内容的代码:


import browser from 'webextension-polyfill';

export default async function renderContent(
  cssPaths: string[],
  render: (appRoot: HTMLElement) => void
) {
  const appContainer = document.createElement('div');
  const shadowRoot = appContainer.attachShadow({
    mode: import.meta.env.DEV ? 'open' : 'closed',
  });
  const appRoot = document.createElement('div');
  if (import.meta.hot) {
    const { addViteStyleTarget } = await import(
      '@samrum/vite-plugin-web-extension/client'
    );

    await addViteStyleTarget(shadowRoot);
  } else {
    cssPaths.forEach((cssPath: string) => {
      const styleEl = document.createElement('link');
      styleEl.setAttribute('rel', 'stylesheet');
      styleEl.setAttribute('href', browser.runtime.getURL(cssPath));
      shadowRoot.appendChild(styleEl);
    });
  }

  shadowRoot.appendChild(appRoot);
  document.body.appendChild(appContainer);

  render(appRoot);
}

此代码使用 webextension-polyfill 包在浏览器之间使用标准化 API。

如您所见,此代码创建了一个影子根并将 CSS 路径作为链接注入到影子根中。 很像你的解决方案,对吗?所以你走在正确的道路上。

使用 renderContent 功能

import React from 'react';
import ReactDOM from 'react-dom/client';
import renderContent from '../renderContent';
import App from './App';

renderContent(import.meta.PLUGIN_WEB_EXT_CHUNK_CSS_PATHS, (appRoot) => {
  ReactDOM.createRoot(appRoot).render(
    <React.StrictMode>
      <App />
    </React.StrictMode>
  );
});

这里真正的魔力在于如何获取这些路径,因为我正在使用 Vite,我可以使用

import.meta.PLUGIN_WEB_EXT_CHUNK_CSS_PATHS
感谢 @samrum/vite-plugin-web-extension,这是一个用于查找所有内容的 Vite 插件出现
import.meta.PLUGIN_WEB_EXT_CHUNK_CSS_PATHS
并传递我的 CSS 的所有路径。 这将确保您的 tailwind CSS 类正常工作。

使用CSS变量或ShadcnUI

如果您想像 shadcnui 那样使用 CSS 变量来主题化您的扩展,您不能使用

:root
指令!

知道原因真的很酷,它与整个层叠样式表的工作方式有关。您可以查看另一个相关主题以了解更多信息。 问题是,你可以使用

:host
来使用样式封装方法,或者你也可以使用唯一的 id 来实现。

@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base{
 :host{
  --background: 0 0% 100%;
    --foreground: 222.2 84% 4.9%;
    --card: 0 0% 100%;
    --card-foreground: 222.2 84% 4.9%;
    /**other CSS variables that you want**/

 }
}
© www.soinside.com 2019 - 2024. All rights reserved.