vitejs / vite

Next generation frontend tooling. It's fast!
http://vite.dev
MIT License
68.17k stars 6.15k forks source link

Ability to append `style`/`script` elements inside of a `shadow-root` #11855

Open seahindeniz opened 1 year ago

seahindeniz commented 1 year ago

Description

Vue started to support mounting app inside of a shadow-root element within this PR. With the following code, I can mount my Vue app under a shadow-root node

import App from './App.vue';

const shadowHost = document.querySelector('#my-host');
const shadowRoot = shadowHost.attachShadow({ mode: 'open' });

createApp(App).mount(shadowRoot);

However, Vite loads style/script elements under the document head element because of the following function and since style elements are getting appended outside of the shadow-root scope, style definitions are not getting applied to HTML elements under the shadow-root. https://github.com/vitejs/vite/blob/356ddfe2e534800c1fd59fa502a2c4c8945f4f92/packages/vite/src/node/plugins/importAnalysisBuild.ts#L63-L119

As a use case, I need to mount a multiple apps to DOM under a shadow-root to avoid scope related collisions.

Suggested solution

Mentioned function can support different parent element to append style/script elements

Alternative

No response

Additional context

No response

Validations

IamFive commented 1 year ago

is this feature in plan?

patak-dev commented 1 year ago

There is an open PR that should address this issue https://github.com/vitejs/vite/pull/12206. I added it with higher priority to discuss the addition of a new option. Let's use this issue to track the feature.

RogierdeRuijter commented 1 year ago

This is the workaround I used.

Run this code in the connectedCallback:

const styleTag = document.createElement("style");
styleTag.innerHTML = <link-to-css>;
this.shadowRoot.appendChild(styleTag);
Oumar96 commented 1 year ago

This is the workaround I used.

@RogierdeRuijter Can you expand on what you mean by the connectedCallback? Is the connectedCallback part of Vue? In my use case I'm not using web components but rather, I am mounting my app inside a shadow DOM to use that as a micro front end.

Oumar96 commented 1 year ago

@seahindeniz Do you have any updates on this issue? I feel that this is a crucial part of building Microfrontends with Vue. ShadowDOM is probably the best and least hacky way to encapsulate styles within micro-frontends. Perhaps you know a workaround that is not too hacky? I feel that manually moving styles from the document head into the shadow DOM is brittle.

seahindeniz commented 1 year ago

Hi @Oumar96, I totally agree with you and unfortunately no solution so far

hood commented 1 year ago

+1, same request here.

gs-rpal commented 1 year ago

+1. do we have any solution for this?

bradlocking commented 1 year ago

+1 - looking for a way to apply Vite generated CSS into a shadow root

gs-rpal commented 1 year ago
Screenshot 2023-10-11 at 10 55 27 PM

try this

Oumar96 commented 1 year ago

@gs-rpal Could you share the vite config file instead of the screenshot?

arqex commented 1 year ago

+1, it would be great to have this option as some kind of vite's configuration or plugin.

@gs-rpal please share that config 🙏 ❤️

trusktr commented 1 year ago

The only workaround for this for now is to query the document for <link> or <style> elements, then add them to your ShadowRoot.

StylesSheets can be added to this.shadowRoot.adoptedStyleSheets in all modern browsers.

const styleElements = document.documentElement.querySelectorAll('..... select all the <link>s and <style>s you want ...')
const sheets = Array.from(styleElements).map(el => el.sheet)

// Apply the desired style sheets to your custom element (via the shadow root):
this.shadowRoot.adoptedStyleSheets = [...sheets]

If you cannot support adoptedStyleSheets because you support older browsers, then you can clone elements and place them in the root:

const styleElements = document.documentElement.querySelectorAll('..... select all the links and styles you want ...')
const styleClones = Array.from(styleElements).map(el => el.cloneNode(true))

// Append clones of the desired style elements to your custom element (via the shadow root):
this.shadowRoot.prepend(...styleClones)

[!Note] prepend() instead of append() so that they will be above your shadow roots existing styles, if any, so that your own root's styles override the shared ones that you bring in.

pascalvos commented 1 year ago

i would also like the element that are created to be not document.createElement but shadowroot.createElement... current way is copy whole internals to add a custom renderer in place... this would help with custom element registery where there is already a proposal for and a polyfill

vue is just not made for webcomponents.. could be oke for mfe stuff if it fixes lot of these issues there many more on like not syncing the attributes if you use a custom element as a root its not ready for most of the newer browser api's and its not made for smaller webcomponents/custom elements.. there only thing is lit or fast something like this... this hacking with out real good support just makes is very brittle because you have to copy and rely on non exposed internals... that dont follow semver :) cause there internals... and hard to follow...

sorry for the rant .. vue just needs to give webcomponents some serious love to be atlease useable for mfe stuff there currently only properly suited for spa things

for singlar webcomponents since vue creates a vue app even if you make a button its about 800% slower then something like lit and 10 times more heavy on memory usage.

hood commented 1 year ago

vue just needs to give webcomponents some serious love

This is the vite repo tho.

pascalvos commented 1 year ago

yea true

hood commented 11 months ago

FYI I’ve created a plugin that does just what I was looking for. Please try it out and let’s see whether it fits your use cases too. I’m pretty sure it’s very specific to my specific usage scenarios, so it’ll not fit anyone’s needs, but nothing stops us from using it as a starting point for a more adaptable plugin.

Link here: https://www.npmjs.com/package/vite-plugin-shadow-style

Oumar96 commented 11 months ago

Screenshot 2023-10-11 at 10 55 27 PM try this

@gs-rpal Any updates? Could you share the config file?

shtief commented 6 months ago

Hello, this feature would be really great to support injection into Shadow roots! Is there already a nice workaround for this?

hood commented 6 months ago

Hello, this feature would be really great to support injection into Shadow roots! Is there already a nice workaround for this?

Hi, probably my solution I linked above can help you with style injection into shadow roots: https://www.npmjs.com/package/vite-plugin-shadow-style

shtief commented 6 months ago

Hello @hood thanks for your comment, but somehow for the dev mode it deactivates the shadow root and for build mode it gives me the warning:

rendering chunks (141)...[vite-plugin-shadow-style] Multiple CSS files found in the output bundle. This plugin currently only supports handling a single css output.

A question to the "vite" team: is it possible to add a feature where it is possible to configure where to inject the styles? Right now only "head" is possible... The code for it is here:

https://github.com/vitejs/vite/blob/bb79c9b653eeab366dccc855713369aea9f90d8f/packages/vite/src/node/plugins/importAnalysisBuild.ts#L125-L137

shtief commented 6 months ago

I also tried the solution from @gs-rpal but it only seems to work for dev mode.

YKalashnikov commented 5 months ago

Hello guys, is there any progress for this feature? Thank you 🙏 cc: @bluwy

66696e656c696665 commented 5 months ago

Screenshot 2023-10-11 at 10 55 27 PM try this

worked for me! But need create Shadow Dom in Vue and after this code put in ShadowRoot your css code create shadowdom main.js:

import vue from 'vue';
import MyComponent from 'src/MyComponent';

let treeHead = document.querySelector(".container");
let holder   = document.createElement("div");
let shadow   = treeHead.attachShadow({mode: 'open'});
shadow.appendChild(holder);

let app = new Vue({
  el: holder,
  render: h => h(MyComponent, {})
});
jayswo commented 3 months ago

I would need this feature so bad for my current project! A custom target, where the Styles should be inserted would be so great - with it we would be totally flexible! Is there any progress on this feature? 🙏🙏🙏🙏

scottsmith-1 commented 1 month ago

Something else to consider is SSR Declarative Shadow DOM. Link tags will be hoisted to the head ignoring the template and waiting for client code to append the tags results in FOUC.

Ahn1 commented 2 weeks ago

Is there any updates on this? I ended up importing the styles using 'style.css?inline' and adding them manually inside the shadow dom. But it feels quite hacked.

dbalabka commented 2 weeks ago

FYI it is impossible to add fonts and icons to shadow dom via <style> tag because @font-face isn't supported there yet: https://issues.chromium.org/issues/41085401

The workaround is to use <link> instead: https://github.com/mdn/interactive-examples/issues/887#issuecomment-470703209

kuus commented 1 week ago

my currently working solution (both with serve and build commands) is:

in vite.config.ts

import { defineConfig } from "vite";
import cssInjectedByJsPlugin from "vite-plugin-css-injected-by-js";

export function defineConfig(() => {
  const globalStylesIdentifier = "__myStylesIdentifier";

  return {
    plugins: [
      cssInjectedByJsPlugin({
        injectCode: (cssCode, _options) => {
          return (
            `try{` +
            `var s = document.createElement('style');` +
            `s.appendChild(document.createTextNode(${cssCode}));` +
            `window.${globalStylesIdentifier} = window.${globalStylesIdentifier} || []; window.${globalStylesIdentifier}.push(s);` +
            `}catch(e){` +
            `console.error('${globalStylesIdentifier}', e);` +
            `}`
          );
        },
        dev: {
          enableDev: true,
          removeStyleCodeFunction: function removeStyleCode(_id) {
            // The 'id' corresponds to the value of the 'data-vite-dev-id' attribute found on the style element. This attribute is visible even when the development mode of this plugin is not activated.
          },
        },
      })
    ]
  };
};

in app.ts or where in my application code I want to inject the styles

const myShadowDomRoot = myDiv.shadowRoot;

if (myShadowDomRoot) {
  const styles = window.__myStylesIdentifier || [];
  while (styles.length) {
    myShadowDomRoot.prepend(styles.pop() || "");
  }
}