背景: 我正在使用 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 实用程序类添加到影子根目录中,以便可以从注入的组件访问这些类。
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);
脚本元素永远不会被渲染,我想可能是因为它违反了内容安全策略
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);
});
到目前为止,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);
由于shadcnui,我也遇到了同样的问题, 我正在使用 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 路径作为链接注入到影子根中。 很像你的解决方案,对吗?所以你走在正确的道路上。
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 类正常工作。
如果您想像 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**/
}
}