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
For reference, this is the previous getRootContainer method that I've removed
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:
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
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?
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
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
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
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
yup
but it will also force you to find css-in-js component lib xD
@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?
Ugh, nevermind, we're using Parcel and not webpack
Wait a second, wait a sec, hold the horses
Navigating the parcel docs I came across this: https://parceljs.org/features/bundle-inlining/#inlining-a-bundle-as-text
Bundle inlining
Parcel includes several ways to inline the compiled contents of one bundle inside another bundle.
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?
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
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
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
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
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: <div...
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
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)
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?
yup, to which you will need to use the transformer for that yeah (to inject the consolidated style into the style-tag)
Oh, I assumed the injection happened somewhere else
@Karstodes has reached level 9. GG!
I'll take a look at it
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 )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
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..Well, this diagram comes handy
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?
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
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.
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?...
Seems like you hit the jackpot
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.
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
Oh, I just realized you're the same from the parcel discord
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.
I can handle that, I think
lol yeah I just bumped that so the parcel god came to down to us lol
Turns out, rubber duck debugging is a hell of a beast sometimes
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
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
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
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?
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
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
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.
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?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
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)
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?
font link tags are marked as stylesheet, right?
Yeah, it seems like so
yeah
so it should be supported
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
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•2y ago
Message Not Public
Sign In & Join Server To View
yeah, or you can use multiple tailwindcss config to ensure that each style entry only account for the style of certain component
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