chrome-extension-boilerplate-react-vite icon indicating copy to clipboard operation
chrome-extension-boilerplate-react-vite copied to clipboard

Error when importing tailwind css file into content script

Open wonkyDD opened this issue 1 year ago • 4 comments

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.

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

wonkyDD avatar Dec 31 '23 16:12 wonkyDD

Same Problem , did you fix it ?

caipa-mso avatar Jan 08 '24 15:01 caipa-mso

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

wonkyDD avatar Jan 08 '24 16:01 wonkyDD

@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 avatar Jan 08 '24 17:01 caipa-mso

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 avatar Jan 08 '24 18:01 caipa-mso

@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 avatar Jan 09 '24 16:01 caipa-mso

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

wonkyDD avatar Jan 10 '24 02:01 wonkyDD

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

caipa-mso avatar Jan 10 '24 21:01 caipa-mso

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

tngflx avatar Feb 04 '24 03:02 tngflx

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!

Jonghakseo avatar Feb 08 '24 08:02 Jonghakseo

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 avatar Feb 29 '24 14:02 stevenirby

@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

tngflx avatar Mar 01 '24 03:03 tngflx

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

Jonghakseo avatar May 12 '24 08:05 Jonghakseo