Jonghakseo / chrome-extension-boilerplate-react-vite

Chrome Extension Boilerplate with React + Vite + Typescript
MIT License
2.22k stars 322 forks source link

Error when importing tailwind css file into content script #347

Closed wonkyDD closed 4 months ago

wonkyDD commented 8 months ago

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

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

options.

image

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.

image

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>);
image
caipa-mso commented 8 months ago

Same Problem , did you fix it ?

wonkyDD commented 8 months ago

@caipa-mso not yet 😭. I'm just enlarging the popup to a maximum of 800 x 600 and proceeding.

caipa-mso commented 8 months ago

@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

caipa-mso commented 8 months ago

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. (index.js:42:52) here is the issue I encounter

caipa-mso commented 8 months ago

@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

wonkyDD commented 8 months ago

@caipa-mso Could you share how did you fix it and what another problem has occured? I can afford to this.

caipa-mso commented 8 months ago

@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 ,

tngflx commented 7 months ago

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 image

Jonghakseo commented 7 months ago

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!

stevenirby commented 6 months ago

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.

tngflx commented 6 months ago

@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

Jonghakseo commented 4 months ago

I've applied tailwindcss with a new project renewal, enjoy!