chrome-extension-boilerplate-react-vite
chrome-extension-boilerplate-react-vite copied to clipboard
Error when importing tailwind css file into content script
Describe the bug
https://github.com/Jonghakseo/chrome-extension-boilerplate-react-vite/issues/263#issuecomment-1818199732 I am trying to integrate shadcn/ui into this template.
It is totally fine for popup, option, newtab and all other pages except for content script.
The repository containing all reproduction processes is here : bug-content-script-css-import
Desktop
- OS: macOS silicon sonoma
- Browser: chrome (Version 120.0.6099.129 (Official Build) (arm64))
- Node Version: v18.17.1
To Reproduce
Steps to reproduce the behavior:
1. Clone repo
git clone https://github.com/Jonghakseo/chrome-extension-boilerplate-react-vite.git
2. Init shadcn/ui
pnpm dlx shadcn-ui@latest init
The selected config options are as follows.
All the options are default except for
- global css file : src/globals.css
- components : @src/components
- utils : @src/lib/utils
- react server component : no
options.
3. Install shadcn related packages
Referenced guide for shadcn Referenced guide for tailwindcss with postcss
pnpm add -D tailwindcss postcss autoprefixer
pnpm add tailwindcss-animate class-variance-authority clsx tailwind-merge
pnpm add lucide-react @radix-ui/react-icons
4. Follow twind setup guide
pnpm install -D @twind/core @twind/preset-autoprefix @twind/preset-tailwind
Add twind.config.ts
and twind.ts
files at right place following twind guide.
I just copied and pasted below.
// twind.config.ts
import { defineConfig } from '@twind/core';
import presetTailwind from '@twind/preset-tailwind';
import presetAutoprefix from '@twind/preset-autoprefix';
export default defineConfig({
presets: [presetAutoprefix(), presetTailwind()],
});
// src/shared/style/twind.ts
import { twind, cssom, observe } from '@twind/core';
import 'construct-style-sheets-polyfill';
import config from '@root/twind.config';
export function attachTwindStyle<T extends { adoptedStyleSheets: unknown }>(
observedElement: Element,
documentOrShadowRoot: T,
) {
const sheet = cssom(new CSSStyleSheet());
const tw = twind(config, sheet);
observe(tw, observedElement);
documentOrShadowRoot.adoptedStyleSheets = [sheet.target];
}
5. Add postcss config
Add postcss.config.cjs
at the root of project.
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
}
}
6. Add Button from shadcn for testing
pnpm dlx shadcn-ui@latest add button
It should genrate button.tsx
file in src/components/ui
path.
7. Edit src/pages/content/ui/index.tsx
properly
I basically copied and pasted https://github.com/Jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/src/pages/content/ui/index.tsx . But this part is added.
import '@src/globals.css'; // added
import App from '@pages/content/ui/app'; // added
...
attachTwindStyle(rootIntoShadow, shadowRoot); // added
createRoot(rootIntoShadow).render(<App />);
Full version is here.
import { createRoot } from 'react-dom/client';
import { attachTwindStyle } from '@src/shared/style/twind';
import '@src/globals.css';
import App from '@pages/content/ui/app';
import refreshOnUpdate from 'virtual:reload-on-update-in-view';
import injectedStyle from './injected.css?inline';
refreshOnUpdate('pages/content');
const root = document.createElement('div');
root.id = 'chrome-extension-boilerplate-react-vite-content-view-root';
document.body.append(root);
const rootIntoShadow = document.createElement('div');
rootIntoShadow.id = 'shadow-root';
const shadowRoot = root.attachShadow({ mode: 'open' });
shadowRoot.appendChild(rootIntoShadow);
/** Inject styles into shadow dom */
const styleElement = document.createElement('style');
styleElement.innerHTML = injectedStyle;
shadowRoot.appendChild(styleElement);
/**
* https://github.com/Jonghakseo/chrome-extension-boilerplate-react-vite/pull/174
*
* In the firefox environment, the adoptedStyleSheets bug may prevent contentStyle from being applied properly.
* Please refer to the PR link above and go back to the contentStyle.css implementation, or raise a PR if you have a better way to improve it.
*/
attachTwindStyle(rootIntoShadow, shadowRoot);
createRoot(rootIntoShadow).render(<App />);
8. Edit src/pages/content/ui/app.tsx
for testing
import { useEffect } from 'react';
import { Button } from '@src/components/ui/button';
export default function App() {
useEffect(() => {
console.log('content view loaded');
}, []);
return (
<div className='bg-white'>
<Button>Shadcn Button</Button>
<h1 className='text-yellow-500'>Content View</h1>
</div>
);
}
Expected behavior
Reproducing the process as described above will create the following in the bottom left corner of the browser.
If hot-reload is not working or any content script showing up in browser, please comment out in index.tsx below part which we added before.
// import { attachTwindStyle } from '@src/shared/style/twind';
// import '@src/globals.css'
// attachTwindStyle(rootIntoShadow, shadowRoot);
then uncomment it again. I don't know why this thing happens but this workaround has worked.
However, in the case of "newtab," it works correctly.
I commented added
at the end of or above line.
// src/pages/newtab/index.tsx
import React from 'react';
import { createRoot } from 'react-dom/client';
import { attachTwindStyle } from '@src/shared/style/twind'; // added
import '@src/globals.css'; // added
import Newtab from '@pages/newtab/Newtab';
import '@pages/newtab/index.css';
import refreshOnUpdate from 'virtual:reload-on-update-in-view';
refreshOnUpdate('pages/newtab');
function init() {
const appContainer = document.querySelector('#app-container');
if (!appContainer) {
throw new Error('Can not find #app-container');
}
attachTwindStyle(appContainer, document); // added
const root = createRoot(appContainer);
root.render(<Newtab />);
}
init();
// src/pages/newtab/Newtab.tsx
import React from 'react';
import logo from '@assets/img/logo.svg';
import '@pages/newtab/Newtab.css';
import '@pages/newtab/Newtab.scss';
import useStorage from '@src/shared/hooks/useStorage';
import exampleThemeStorage from '@src/shared/storages/exampleThemeStorage';
import withSuspense from '@src/shared/hoc/withSuspense';
import withErrorBoundary from '@src/shared/hoc/withErrorBoundary';
import { Button } from '@src/components/ui/button'; // added
const Newtab = () => {
const theme = useStorage(exampleThemeStorage);
return (
<div
className="App"
style={{
backgroundColor: theme === 'light' ? '#ffffff' : '#000000',
}}>
<header className="App-header" style={{ color: theme === 'light' ? '#000' : '#fff' }}>
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/pages/newtab/Newtab.tsx</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
style={{ color: theme === 'light' && '#0281dc', marginBottom: '10px' }}>
Learn React!
</a>
<h6>The color of this paragraph is defined using SASS.</h6>
<button
style={{
backgroundColor: theme === 'light' ? '#fff' : '#000',
color: theme === 'light' ? '#000' : '#fff',
}}
onClick={exampleThemeStorage.toggle}>
Toggle theme
</button>
{/* added */}
<Button>Shadcn Button</Button>
</header>
</div>
);
};
export default withErrorBoundary(withSuspense(Newtab, <div> Loading ... </div>), <div> Error Occur </div>);
Same Problem , did you fix it ?
@caipa-mso not yet 😭. I'm just enlarging the popup to a maximum of 800 x 600 and proceeding.
@caipa-mso not yet 😭. I'm just enlarging the popup to a maximum of 800 x 600 and proceeding.
I don't know why the contentScript automatically fetch the css from website host instead of load from local assert , https://github.com/JohnBra/vite-web-extension this work great but it does not support hmr
Refused to apply style from 'https://github.com/assets/css/contentStyle1704736810181.chunk.css' because its MIME type ('text/plain') is not a supported stylesheet MIME type, and strict MIME checking is enabled.
index.js:42 Uncaught (in promise) Error: Unable to preload CSS for /assets/css/contentStyle1704736810181.chunk.css
at HTMLLinkElement.
@caipa-mso not yet 😭. I'm just enlarging the popup to a maximum of 800 x 600 and proceeding.
Hi , I fixed the bug , but now I run into another problem , If you still have time , we can talk about it
@caipa-mso Could you share how did you fix it and what another problem has occured? I can afford to this.
@caipa-mso Could you share how did you fix it and what another problem has occured? I can afford to this.
Basicly you can't import anything into content script directly , you can use the default scss file and add the tailwind property ,
@tailwind base;
@tailwind components;
@tailwind utilities;
if you try to import anything like global.css in to your content script , It will actually fetch the file through the website you visited . (e.g : github.com/tailwind.css) , this will cause fetching failed and the whole contescript broke down.
So they actually inject the scss in vite.config.js , and bundle them together , they don't import it
Anyway , here is the problem , Cause I'm not that familiar with vite though ,
first is
twind conflict with typescript 5.22 , If you try to downgrade to 4.89 , it will cause built error
after you use twind in contentScript , if some website is also written by tailwind , the css from that website will get polluted . it required a shadowDom ,it does not config in the repo
also if you are using shadcn , you can't import the global.css due to the contentscript does not support import css directly ,
Hmm I think this one can be solved by throwing out twind altogether. Just use plain tailwindcss with always uptodate version. I've mentioned it before on other issue. This is how i scoped css on tailwindcss
//buttonComponent.tsx
import React, { useRef, useEffect } from 'react';
import { render } from 'react-dom';
const ButtonRenderer = ({ onClickHandler, containerElement, buttonWrapperClasses, buttonName }) => {
const buttonRef = useRef(null);
useEffect(() => {
const shadowRoot = containerElement.attachShadow({ mode: 'open' });
// Can't directly return buttonElement because we still need to do dom manipulation on this button
// The only way is to useEffect hook to do dom manipulation before render
render(
<div className={buttonWrapperClasses}>
<button
onClick={() => onClickHandler()}
className="taoconv_button bg-green-500 hover:bg-green-300 text-black font-bold py-2 px-3 rounded items-center"
ref={buttonRef}
>
{buttonName}
</button>
</div>,
shadowRoot
);
// Inject the external stylesheet
const linkElement = document.createElement('link');
linkElement.rel = 'stylesheet';
linkElement.href = chrome.runtime.getURL('assets/css/tailwindStyle.chunk.css');
shadowRoot.appendChild(linkElement);
// Cleanup logic (optional): Remove the stylesheet and reset buttonRef on component unmount
return () => {
shadowRoot.removeChild(linkElement);
};
}, [onClickHandler, containerElement]);
// Return an empty div as a placeholder for the component
return <div ref={buttonRef}></div>;
};
export default ButtonRenderer;
const button_container = document.createElement('div');
button_container.classList.add('tao_convert_button');
bought_threadop_wrapper_el.insertAdjacentElement('afterbegin', button_container)
// Render the BuyerTradeButtonWrapper component and pass the button_wrapper as a prop
render(
<ButtonRenderer
onClickHandler={onClickHandler}
containerElement={button_container}
buttonWrapperClasses="float-left inline-flex mx-4"
buttonName="taoImport"
/>,
button_container
);
And voila you get all the scoped css and no pollute global css. here's the result
Hmm I think this one can be solved by throwing out twind altogether. Just use plain tailwindcss with always uptodate version. I've mentioned it before on other issue. This is how i scoped css on tailwindcss
I think this is the best way to use tailwindcss. I'll test it!
I managed to get tailwind and shadcn UI added. Adding tailwind without twind is the way to go.
Everything was working, but my problem was tailwind globals were wrecking the styling on the page I was loading my extension on. So it's a no-go for me.
@stevenirby I thought the code i provided won't break anything as you see from the image i attached. The main thing you have to do is include shadowdom which include separation from globalcss
I've applied tailwindcss with a new project renewal, enjoy!