Styling React components and ShadowDOM usage

Morning y'all, long time no see. Quite a while ago I removed the ShadowDOM implementation and injected the content script directly onto the root tree. I don't remember exactly why, I think it was an issue with font loading that I only got around when not using the shadow DOM. Anyways, that decision has ended up biting my ass back, and now I'm dealing with a lot of inconsistencies in the styling between sites due to the site styles bleeding into the extension. I've removed the getRootContainer export from my content script, and it's now in the Shadow DOM again... But it has absolutely no styling. Not from the site, not from the extension, nothing. It's just the HTML from the components. I believe this has something to do with how I'm importing the styles onto the React components used in the content script, but I'm not entirely sure why being in or out of the Shadow DOM affects this, so I want to double check. For react based content scripts, how do you style the components? I understand that there's the getStyle export, but that doesn't really work for more complex components that import other components with their own styles
61 Replies
Karstodes
KarstodesOP•2y ago
For reference, this is the previous getRootContainer method that I've removed
export const getRootContainer = async (): Promise<HTMLDivElement> => {
const body = document.querySelector("body")
const container = document.createElement("div")
container.style.cssText = `
z-index: 9999;
position: absolute;
`
body.appendChild(await getStyle())
body.appendChild(container)

return container
}
export const getRootContainer = async (): Promise<HTMLDivElement> => {
const body = document.querySelector("body")
const container = document.createElement("div")
container.style.cssText = `
z-index: 9999;
position: absolute;
`
body.appendChild(await getStyle())
body.appendChild(container)

return container
}
Also I'm importing the CSS (well, SASS) inside each component by just importing the file like in a regular React project. As an example:
import React from "react"

import "./this-component-style.scss"

const ExampleComponent = () => {
return (
<span className="example-class">This is a test!</span>
)
}
import React from "react"

import "./this-component-style.scss"

const ExampleComponent = () => {
return (
<span className="example-class">This is a test!</span>
)
}
.example-class {
color: red;
}
.example-class {
color: red;
}
lab
lab•2y ago
Fascinating. CSUI style are injected into the shadow DOM at point of entry. The way you have it works on extension pages but won't work on csui bc it's like SSR - you can't hydrate child style unless consolidated
Karstodes
KarstodesOP•2y ago
Alright, so using a Shadow DOM for isolation is not an option in this use case? I can't really change the way components are styled, not all of them at least. Some come from imported libraries, so I'd assume those wouldn't work What are some possible alternatives, either for style loading or for styling isolation?
lab
lab•2y ago
you will need to consolidate the style fragment somehow to inject them in the right place. One way to do so, is hack the plasmo's resolver and js transformer, see if it's possible to isolate those style and inject them into the designated style tag. basically, we need to study how the default css transformer is injecting styles, mark all styles being injected into csui as they are inside csui, and instead of being injected as top-level page style tag, get injected into plasmo's CSUI
Karstodes
KarstodesOP•2y ago
Interesting, I might take a look at it. Otherwise I hope there are libraries for CSS isolation that I can use if I'm forced to mount the script into the root body tag
lab
lab•2y ago
the easiest is to use css-in-js that's what I use for my extension project that also share component with webapp and native apps
Karstodes
KarstodesOP•2y ago
Yeah, but that gives me control only for the components I'm making if I'm using a component defined by a library and said component doesn't use css-in-js, I'm out of luck I assume
lab
lab•2y ago
yup but it will also force you to find css-in-js component lib xD
Karstodes
KarstodesOP•2y ago
@louis I've been digging about the topic and it seems that there are ways to make it work using the webpack style loader plugin, do you know if the webpack configuration is accessible in any convenient way to toy with it?
Karstodes
KarstodesOP•2y ago
Ugh, nevermind, we're using Parcel and not webpack Wait a second, wait a sec, hold the horses
Karstodes
KarstodesOP•2y ago
Bundle inlining
Parcel includes several ways to inline the compiled contents of one bundle inside another bundle.
Karstodes
KarstodesOP•2y ago
I assumed this kind of approach wouldn't work because I'd have to do it manually for each react component, so the components imported from libraries wouldn't work, but the docs say this:
Parcel will compile the resolved file as normal, including bundling all dependencies, and then inline the result as a string into the parent bundle.
I assume this means the SCSS dependencies (@import, @use...), but could there be a way to make it also fetch the CSS modules from dependant components?
lab
lab•2y ago
This is what the getStyle feature is yeah So one thing you can try to find is a postcss plugin That alter the style injection location behavior for parcel. And you can add a postcss.json to configure that feature
Karstodes
KarstodesOP•2y ago
There's postcss-import, but it doesn't seem to help in my particular scenario it resolves imports inside the CSS files, but the files themselves are independent from each other, they're linked by the component dependencies inside react
lab
lab•2y ago
Yeah I went on a search spree and came to a similar conclusion - seems like there's no public postcss plugin to do that lol
Karstodes
KarstodesOP•2y ago
This is a brick wall I wish I wasn't hitting right now, specially because there are pretty much no alternatives for my use case I can't use iframes, I've looked into CSS reset alternatives like cleanslate but they end up with the same problem as before, not being able to change the CSS of imported components ShadowDOM seems to be also a dead end I remember this being addressed in a RFC some time ago
Karstodes
KarstodesOP•2y ago
GitHub
[RFC] Style injection for Content Script UI · Issue #9 · PlasmoHQ/p...
How do you envision this feature/change to look/work like? This RFC is meant to discuss how styles are injected into the content script. By default, the framework supports inline style: &lt;div...
Karstodes
KarstodesOP•2y ago
Any chance to revisit option A? I'll take a look and see if I can figure a way to at least extract the style
lab
lab•2y ago
I won't have time for it atm, but if you're down to make a framework PR I can give you pointers The baseline idea is that, right now we're using this parcel transformer plugin to transform and create the css bundle: https://github.com/parcel-bundler/parcel/blob/v2/packages/configs/default/index.json#L27 Which in turns, invoke this transformer: https://github.com/parcel-bundler/parcel/blob/v2/packages/transformers/css/src/CSSTransformer.js What you can try is replace that transformer within plasmo, similar to the inline-css transformer: https://github.com/PlasmoHQ/plasmo/blob/a00f567160042945917c0a51e99e08e559f314ac/packages/parcel-transformer-inline-css/src/index.ts#L6 and mount it into the plasmo shadowDOM (or any custom selector)
Karstodes
KarstodesOP•2y ago
I mean Looking at the build files, the css is already being unified at some point, there's a single css file generated with all of the project definitions, so maybe there's no need to toy with the transformers All that would be needed is to get that built file into a <style> tag inside the Shadow DOM, right?
lab
lab•2y ago
yup, to which you will need to use the transformer for that yeah (to inject the consolidated style into the style-tag)
Karstodes
KarstodesOP•2y ago
Oh, I assumed the injection happened somewhere else
Arcane
Arcane•2y ago
@Karstodes has reached level 9. GG!
Karstodes
KarstodesOP•2y ago
I'll take a look at it
lab
lab•2y ago
the current getStyle API is the surface level for CS entry to do so within your project's code but when it comes to redirect the rest of the CSS bundle, rn it all happens within the transformer (which produce the bundle code )
Karstodes
KarstodesOP•2y ago
Hey @louis I'm coming around a different implementation Rather than toying with the transformer, I thought it would be a better idea to wrap the parcel css packager with our own implementation, and take the output of the packager to inject that into the Shadow DOM seems cleaner than stitching it ourselves Well, that idea died out quickly It's not easily wrappable
lab
lab•2y ago
yeah, after spending a week figuring out where the heck did parcel consodliate the css into the style tag bundle, I kinda gave up and just go with the inline bundling for now lol basically, we need to figure out how parcel transform that import "style.css" statement in a dependency into a style in the style bundle, and where does it generate that style tag code and I think it's likely within lightning-css - that's the only place I haven't look deeply into yet parcel reads the import "style.css" within the resolver, then it pass them down to transformer, then packager etc..
Karstodes
KarstodesOP•2y ago
Well, this diagram comes handy
Karstodes
KarstodesOP•2y ago
I'm going to look in the js transformer, I assume that's where the magic happens However, I don't really understand the end goal here Let's say we find exactly where things are converted into the final bundle I don't see any convenient way to pass that information from Parcel to Plasmo, is there one I'm missing?
lab
lab•2y ago
parcel must have created the style tag entry point for the css import. If this entry point import can be changed, we can align it with plasmo's entry. This style tag entry is likely a js module injected into the bundle to create the style tag for the css style, which will be evaluaetd at runtime
Karstodes
KarstodesOP•2y ago
But as far as I understand plasmo doesn't convert it into any style tags, it creates a css file that stitches all of the css bundles Sorry, not plasmo. Parcel.
lab
lab•2y ago
That css file must still be referenced within the js runtime somehow, otherwise it will not be loaded which gave me a thought, perhaps it's done within their runtime and not at the packager level :d.... I,e within the jsruntime, they might be reading the parcel_require then spawn the style tag?...
Karstodes
KarstodesOP•2y ago
Seems like you hit the jackpot
Karstodes
KarstodesOP•2y ago
GitHub
parcel/css-loader.js at v2 · parcel-bundler/parcel
The zero configuration build tool for the web. 📦🚀. Contribute to parcel-bundler/parcel development by creating an account on GitHub.
lab
lab•2y ago
fascinating we can add a middleware runtime before this happens imo this aligns with some recent work to provide a prod-runtime for plasmo as well
Karstodes
KarstodesOP•2y ago
Oh, I just realized you're the same from the parcel discord
Karstodes
KarstodesOP•2y ago
So the basic idea would be to add a middleware that does the same, but modifying this so it injects it inside the ShadowDOM rather than the head? https://github.com/parcel-bundler/parcel/blob/f8d3fc30ca5b33d8f8674525f2a741d662c5986a/packages/runtimes/js/src/helpers/browser/css-loader.js#L30
GitHub
parcel/css-loader.js at f8d3fc30ca5b33d8f8674525f2a741d662c5986a · ...
The zero configuration build tool for the web. 📦🚀. Contribute to parcel-bundler/parcel development by creating an account on GitHub.
Karstodes
KarstodesOP•2y ago
I can handle that, I think
lab
lab•2y ago
lol yeah I just bumped that so the parcel god came to down to us lol
Karstodes
KarstodesOP•2y ago
Turns out, rubber duck debugging is a hell of a beast sometimes
lab
lab•2y ago
It would need to check for some metadata, then we can safely align that tag with plasmo yeah. Potentially, we can place our runtime before this JS runtime. I wonder if they can work together (?)... Or we will need to figure out a way to disable parcel's css loader
Karstodes
KarstodesOP•2y ago
I think having both is fine, but perhaps do it the opposite way? Have the default runtime load first and generate the style tag in the head, then run a custom plasmo runtime and if there is an existing Shadow DOM move the style tag node from the head to the Shadow DOM That way you avoid having duplicate definitions
lab
lab•2y ago
interesting, sounds like a good idea' one thing to note tho is we need to account for when plasmo creates the shadowDOM vs when this cssloader is run :d... and whether we can get a hold of it in our runtime
Karstodes
KarstodesOP•2y ago
Oh I'm not too familiar with it unfortunately, but I'm pretty sure the cssloader transformer will run before the bundler What is exactly the problem with cssloader? As long as it has done all of its transformations before injecting it into the shadowDOM it should be fine, right?
lab
lab•2y ago
reading the css loader code, it seems to not be inlining the style but rather reference it as a link pointing to head I'm not sure if that would work xd
Karstodes
KarstodesOP•2y ago
I'll do a quick proof of concept Alright, so digging a bit more it seems like the helper code that handles the injection into the DOM is attached as part of the asset generated by the runtime, so rather than a custom runtime we need a custom CSS packager to swap the code attached to the assets for our own helper function that decides if the code is attached to the Shadow DOM or the document's head The docs on the runtime API are quite poor so I'm not sure if we can access the assets from a runtime output in a different runtime So that's the next best option, I believe
Karstodes
KarstodesOP•2y ago
GitHub
parcel/JSRuntime.js at cafaeaf62a887fb7a388a4ef1895716ee09a68a2 · p...
The zero configuration build tool for the web. 📦🚀. Contribute to parcel-bundler/parcel development by creating an account on GitHub.
lab
lab•2y ago
we might need to take a step back the current css-loader is meant for isolated css bundle loading (via a link reference) can that work in ShadowDOM? i.e, can you use link inside shadowDOM and ensure just that DOM has style applied?
Karstodes
KarstodesOP•2y ago
I don't see why you wouldn't be able to use link inside the shadowDOM it's not an iframe, it doesn't isolate everything
lab
lab•2y ago
so when I was trying to add a font into shadowDOM, it doesn't work. I had to add it as a css in the content script config which is why I'm not sure if it would work. You would also need to configure the host webpage's CORS to load the css bundle from the extension right? (just thinking outloud about any potential CSP issue when loading resource into a webpage from a CS)
Karstodes
KarstodesOP•2y ago
You are right https://wicg.github.io/webcomponents/spec/shadow/#inertness-of-html-elements-in-a-shadow-tree Or maybe not it seems like stylesheet links work inside a shadow tree?
Karstodes
KarstodesOP•2y ago
font link tags are marked as stylesheet, right? Yeah, it seems like so
lab
lab•2y ago
yeah
Karstodes
KarstodesOP•2y ago
so it should be supported
lab
lab•2y ago
seems like link stylesheet should work in this case then the last issue would be CSP, I suppose that can be mitigated with either CSP or web-accessible-resource declaration
Karstodes
KarstodesOP•2y ago
That's a bit out of my league I believe but tomorrow morning I'll work in a proof of concept, at least to see how it would work if I just replace the injection point in the helper might even go through the sledgehammer route and just hard code the mount point in the parcel node module so I can fiddle with it (Just for testing purposes, of course)
Unknown User
Unknown User•2y ago
Message Not Public
Sign In & Join Server To View
lab
lab•2y ago
yeah, or you can use multiple tailwindcss config to ensure that each style entry only account for the style of certain component
lab
lab•2y ago
GitHub
examples/with-multiple-tailwindcss at main · PlasmoHQ/examples
🔰 Example projects that demonstrate how to use the Plasmo Framework and integrate with popular tools - examples/with-multiple-tailwindcss at main · PlasmoHQ/examples
Want results from more Discord servers?
Add your server