vuejs / core

🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

Home Page:https://vuejs.org/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

When importing component inside custom element, style is discarded

gnuletik opened this issue · comments

Version

3.2.14

Reproduction link

github.com

Steps to reproduce

git clone git@github.com:gnuletik/vue-ce-import-comp-style.git
cd vue-ce-import-comp-style
yarn run dev

Open browser

What is expected?

CSS of OtherComponent.vue should be applied.
The text "It should be blue" should be blue.

What is actually happening?

Style is not applied.


I tried renaming OtherComponent to OtherComponent.ce.vue, but the result is the same.

This is useful when writing a set of custom elements to have shared components between custom elements.

Also encountered this issue. Here's a couple more repros:

I also encountered it and just worked on a fix, will create a PR soon (my first one here 😨 )

I just realized there is already an open PR about this : #4309

I see 👁️ . My changes are very similar, just its more recursive, meaning also components at any level are interested.

Ok, this should be safer now. This addition will add any child component style to the main parent, no matter how deeply nested it is

Anyone know if it will be merged? It blocks me from using Vue 3 :/ @raffobaffo What about third party nested components (e.g. imported from some package)? Will your solution also work for them?

We will solve this, it might take a few more days or weeks though.

We will solve this, it might take a few more days or weeks though.

@LinusBorg Ok, thanks for quick reply, it's good to know that it will be solved at some point in the future :)

@pawel-marciniak Yes, it is.
@LinusBorg Great to hear. Atm to overcome this problem, we are using an own extended version of the defineCustomElement that provides that hack I inserted in the pr.

@raffobaffo would you mind sharing how did you extend it? I tried it here by basically cloning /runtime-dom/src/apiCustomElement.ts with your changes but got a lot of syntax error from TS.

@lucasantunes-ciandt sorry but not TS for me on this project. This is pretty much the hack I built:

import { defineCustomElement as rootDefineCustomElement } from 'vue'
[...]
export const getChildrenComponentsStyles = (component) => {
  let componentStyles = [];
  if (component.components) {
    componentStyles = Object.values(component.components).reduce(
      (
        aggregatedStyles,
        nestedComponent,
      ) => {
        if (nestedComponent?.components) {
          aggregatedStyles = [
            ...aggregatedStyles,
            ...getChildrenComponentsStyles(nestedComponent),
          ];
        }
        return nestedComponent.styles
          ? [...aggregatedStyles, ...nestedComponent.styles]
          : aggregatedStyles;
      }, [],
    );
  }
if (component.styles) {
    componentStyles.push(...component.styles);
  }

  return [...new Set(componentStyles)];
};

export const defineCustomElement = (component) => {

  // Attach children styles to main element
  // Should be removed once https://github.com/vuejs/vue-next/pull/4695
  // gets merged
  component.styles = getChildrenComponentsStyles(component);
  const cElement = rootDefineCustomElement(component);

  // Programmatically generate name for component tag
  const componentName = kebabize(component.name.replace(/\.[^/.]+$/, "") );
  // eslint-disable-next-line tree-shaking/no-side-effects-in-initialization
  customElements.define(componentName, cElement);

  // Here we are attaching a <ling ref="style" href="/style.css" ...
  // This is to have the css outside of the shadow dom
  // also available inside the shadow root without inlining them
  // The browser will automatically use the available declaration and won't
  // make multiple calls
  const componentShadowDom = document.querySelector(componentName)?.shadowRoot;
  if(componentShadowDom){
    const styleLink = document.createElement('link');
    styleLink.setAttribute('rel', 'stylesheet');
    styleLink.setAttribute('href', 'style.css');
    componentShadowDom.appendChild(styleLink);
  }
}

@raffobaffo Thank you so much! I thought you were literally extending the whole file 😅

I tried this code here, but unfortunately it doesn't work. I suppose it expects the components are all custom elements, e.g. Component.ce.vue and ChildComponent.ce.vue.

When my components are named Component.vue they don't come with any styles prop, only when they're Component.ce.vue.

I reckon we'll need to wait for your PR to be merged.

Oopsie, actually this works! Thank you @raffobaffo!!

I was a bit confused about the meaning of .ce.vue, now I realize the extension is only a Custom Element MODE and not actually declaring those components as custom elements. This means I really should have them as .ce.vue in order for this to work, even after this PR is merged.

So my two cents to the Issue Owner: OtherComponent.vue should be OtherComponent.ce.vue indeed.

@raffobaffo I had only to change the injection a little bit, since I may have multiple instances of the same Custom Element:

document.querySelectorAll(customElementName).forEach((element) => {
  if (!element.shadowRoot) return;

  const styleLink = document.createElement('link');
  styleLink.setAttribute('rel', 'stylesheet');
  styleLink.setAttribute('href', 'style.css');

  element.shadowRoot.appendChild(styleLink);
});

@lucasantunes-ciandt Nice find 👍 ! Still this block is not part of the PR, because this actually make sense to have it in a separate module that is loaded just when custom elements are needed. In my case I have a components library and I want to have 2 exports, one to be consumed by Vue applications and one for browsers/html.
This part would maybe make more sense in some Vite or VueCli plugin 🤔 .

@raffobaffo Exactly! In our case we're using Vue inside a CMS, so we'll be only exporting custom elements to use within it instead of creating a Vue app, so we'll definitely keep that part because we need some external styles and even scripts from the CMS to be injected into the elements.

@raffobaffo would your solution also inject styles into the shadow DOM from child components imported within the defineCustomElement component that originate from external packages?

As an example, if I define and export a component with defineCustomElement, but within that element is a button component that is being imported from an external package (and is not created with defineCustomElement).

Also @lucasantunes-ciandt do you have a working example using the reproduction repo?

@adamlewkowicz hard to answer. Depends from how the imported element (third party) is structured. Does it comes with is own inline styles? If yes, it should work, if not, it cant.

@raffobaffo would you be willing to update your reproduction repo linked above with a new branch that utilizes your fix? I tried what you have but wasn't able to get it working for some reason. Would be much appreciated!

As a workaround I've created component, which tracks Mutation in document.head and inline style tags (must includes comment /* VueCustomElementChildren */) into shadow dom in dev mode, for production it inlines link for stylesheets:

CustomElements.vue

<script setup lang="ts">
/***
 * @see https://github.com/vuejs/vue-next/issues/4662
 **/

import { computed } from "vue";
import { onMounted, onUnmounted, ref } from "@vue/runtime-core";

const props = defineProps<{
  cssHref?: string;
}>();

const inlineStyles = ref<HTMLElement[]>([]);
const html = computed(() =>
  // @ts-ignore
  import.meta.env.PROD && props.cssHref
    ? `<link href="${props.cssHref}" rel="stylesheet"/>`
    : `<style>${inlineStyles.value
        .map((style) => style.innerHTML)
        .join("")}</style>`
);

const findAndInsertStyle = () => {
  inlineStyles.value = Array.from(
    document.getElementsByTagName("style")
  ).filter((node) => {
    return node.innerHTML.includes("VueCustomElementChildren");
  });
};

// @ts-ignore
if (!import.meta.env.PROD) {
  const observer = new MutationObserver(findAndInsertStyle);
  observer.observe(document.head, {
    childList: true,
    subtree: true,
    characterData: true,
  });

  onMounted(findAndInsertStyle);
  onUnmounted(() => {
    observer.disconnect();
  });
}
</script>

<template>
  <div v-html="html"></div>
</template>

<style>
/* VueCustomElementChildren */
</style>

SomeOtherComponent.vue

<script setup lang="ts">
</script>

<template>
  <div class="some-other">test</div>
</template>

<style>
/* VueCustomElementChildren */
.some-other {
  color: red;
}
</style>

App.ce.vue

<script setup lang="ts">
import CustomElements from "@/components/CustomElements.vue";
import SomeOtherComponent from "@/components/SomeOtherComponent.vue";
</script>

<template>
  <CustomElements css-href="/style/stylesheet.css" />
  <SomeOtherComponent />
</template>

<style>
</style>

I've found a blog by @ElMassimo describing a workaround which works for me. To summarize:

  1. Name all SFC file names *.ce.vue
  2. In your main.ts use defineCustomElement.
  3. Define your custom elements.
  4. Use tag names instead of importing components. E.G <HelloWorld msg="hi"/> becomes \<hello-world msg="hi" />.

Don't know the limitations yet e.G vuex etc.

As a workaround I've created component, which tracks Mutation in document.head and inline style tags (must includes comment /* VueCustomElementChildren */) into shadow dom in dev mode, for production it inlines link for stylesheets:

@dezmound, wont that make other Vue custom elements' styles get injected into the first one?

For example, if you have 3 custom elements in the same page:

  1. element-a is inserted in page adds style to document.head
  2. observer from element-a copies the style from element-a to its shadow DOM
  3. element-b is inserted in page and adds style to document.head
  4. observer from element-a copies the style from element-b to element-a shadow DOM
  5. observer from element-b copies the style from element-a and element-b to element-b shadow DOM
  6. element-c is inserted in page and adds style to document.head
  7. observer from element-a copies the style from element-c to element-a shadow DOM
  8. observer from element-b copies the style from element-c to element-b shadow DOM
  9. observer from element-b copies the style from element-a, element-b and element-c to element-c shadow DOM

As so you end up with unwanted styles from other components into the components. It only works well if there is only one Vue based custom element/application in the page.

As a workaround I've created component, which tracks Mutation in document.head and inline style tags (must includes comment /* VueCustomElementChildren */) into shadow dom in dev mode, for production it inlines link for stylesheets:

@dezmound, wont that make other Vue custom elements' styles get injected into the first one?

For example, if you have 3 custom elements in the same page:

  1. element-a is inserted in page adds style to document.head
  2. observer from element-a copies the style from element-a to its shadow DOM
  3. element-b is inserted in page and adds style to document.head
  4. observer from element-a copies the style from element-b to element-a shadow DOM
  5. observer from element-b copies the style from element-a and element-b to element-b shadow DOM
  6. element-c is inserted in page and adds style to document.head
  7. observer from element-a copies the style from element-c to element-a shadow DOM
  8. observer from element-b copies the style from element-c to element-b shadow DOM
  9. observer from element-b copies the style from element-a, element-b and element-c to element-c shadow DOM

As so you end up with unwanted styles from other components into the components. It only works well if there is only one Vue based custom element/application in the page.

@claudiomedina Thank you for the comment 🙏 That's right, but it can be solved for example with adding scope prop for CustomElements.vue:

CustomElements.vue

<script setup lang="ts">
/***
 * @see https://github.com/vuejs/vue-next/issues/4662
 **/

import { computed } from "vue";
import { onMounted, onUnmounted, ref } from "@vue/runtime-core";

const props = defineProps<{
  cssHref?: string;
  scope?: string;
}>();

const inlineStyles = ref<HTMLElement[]>([]);
const html = computed(() =>
  // @ts-ignore
  import.meta.env.PROD && props.cssHref
    ? `<link href="${props.cssHref}" rel="stylesheet"/>`
    : `<style>${inlineStyles.value
        .map((style) => style.innerHTML)
        .join("")}</style>`
);

const findAndInsertStyle = () => {
  inlineStyles.value = Array.from(
    document.getElementsByTagName("style")
  ).filter((node) => {
    return node.innerHTML.includes(`${props.scope}: VueCustomElementChildren`);
  });
};

// @ts-ignore
if (!import.meta.env.PROD) {
  const observer = new MutationObserver(findAndInsertStyle);
  observer.observe(document.head, {
    childList: true,
    subtree: true,
    characterData: true,
  });

  onMounted(findAndInsertStyle);
  onUnmounted(() => {
    observer.disconnect();
  });
}
</script>

<template>
  <div v-html="html"></div>
</template>

<style>
/* VueCustomElementChildren */
</style>

Element-a.ce.vue

<script setup lang="ts">
import CustomElements from "@/components/CustomElements.vue";
import SomeOtherComponent from "@/components/SomeOtherComponent.vue";
</script>

<template>
  <CustomElements css-href="/style/stylesheet.css" scope="Element A" />
  <SomeOtherComponentForA />
</template>

<style>
</style>

SomeOtherComponentForA.vue

<script setup lang="ts">
</script>

<template>
  <div class="some-other">test</div>
</template>

<style>
/* Element A: VueCustomElementChildren */
.some-other {
  color: red;
}
</style>

Element-b.ce.vue

<script setup lang="ts">
import CustomElements from "@/components/CustomElements.vue";
import SomeOtherComponent from "@/components/SomeOtherComponent.vue";
</script>

<template>
  <CustomElements css-href="/style/stylesheet.css" scope="Element B" />
  <SomeOtherComponentForB />
</template>

<style>
</style>

SomeOtherComponentForB.vue

<script setup lang="ts">
</script>

<template>
  <div class="some-other">test</div>
</template>

<style>
/* Element B: VueCustomElementChildren */
.some-other {
  color: red;
}
</style>

My example's just a solution that allows to write and use components for CustomElement without casting it into native custom elements, so it's still compactable with TS. The child components may be shared with any entry points such as Vue App or different Custom Element. It doesn't contain complex logic because it does not need to. In some other cases as in your example, this workaround may be easily improved.

Is there any solution for this in sight ? This is a big show stopper in vitejs/vite#5731

Is there any solution for this in sight ? This is a big show stopper in vitejs/vite#5731

If you don't need to use shadow DOM, you can use your own defineCustomElement function from this PR as described here: #4314 (comment). That fixes the issue.

Thank's, but I need to use shadow DOM. I guess another workaround would be to use global styles from the top component - instead of scoped styles in each sub component ?

@lroal

I use global styles with importing stylesheets in root Component (root.ce.vue) to avoid this problem.

<style>
// v-bind things... like
--configurable-size: v-bind("~~~")

</style>
<style src="./styles/sheet1"></style> // maybe, use css variables declared in root component style
<style src="./styles/sheet2"></style>
<style src="./styles/sheet3"></style>
<style src="./styles/sheet4"></style>
import { createApp, defineCustomElement } from 'vue'
import App from './App.vue'

const styles = ['button { background: red; }']
customElements.define('yuty-lens', defineCustomElement({ ...App, styles }))

const app = createApp(App)
app.mount('#app')

I added inline CSS for shadow root but styles aren't loading at all, Does anyone has with the same issue?

Hi, is there a solution coming for this?

I currently use a workaround where I let vue-cli's webpack simply generate external link-tags inside a template tag. In the mounted hook I clone those tags into the shadow root.
This solution also works with @-paths for static assets in the css/scss.
I've added a minimal demo here, maybe it's helpful for someone:

https://gitlab.com/cgz/vue-web-comp-css-injection-demo.git

But I would really prefer a out of the box solution. Hopefully it will be fixed in one of the next versions.

There's another workaround:

  1. rename all components to *.ce.vue or enable { customElement: true } in your config so all components styles are exported like described
  2. in your main.ts import all components you are using in your CE and concatenate their styles with the main components styles e.g.:
import HelloWorld from './components/HelloWorld.vue';
import App from './App.vue';

App.styles = [...App.styles, ...HelloWorld.styles];

customElements.define('my-component', defineCustomElement(App));

I haven't exhaustively tested this yet but it works for me with the starter app.

+1 Urgently need this too!

If there aren't any plans to fix this soon, it really needs to be mentioned in the docs.

commented

+1

One workaround I'm using it to generate the style.css by building my app in lib mode. I then move that file into the public directory and then link to it from inside the template in my main app.ce.js file:

<template>
  <link href="/style.css" rel="stylesheet" />
  ...

I need to rebuild that stylesheet if I update any inline component styles but not a show stopper for now.

I've since found another workaround that doesn't require to import each component specifically.
Assuming customElement: true in vite.config.ts is set:

const styles: string[] = []
const modules = import.meta.glob('./**/*.vue');

for (const path in modules) {
  const mod = await modules[path]();
  styles.push(mod.default.styles);
}

App.styles = [styles.flat().join('')];

This is using vite glob import, not sure if a similar solution exists for webpack.

I've since found another solution that doesn't require to import each component specifically. Assuming customElement: true in vite.config.ts is set:

const styles: string[] = []
const modules = import.meta.glob('./**/*.vue');

for (const path in modules) {
  const mod = await modules[path]();
  styles.push(mod.default.styles);
}

App.styles = [styles.flat().join('')];

This is using vite glob import, not sure if a similar solution exists for webpack.

That is indeed a workaround, but not a solution. This would imply that every component knows every other component styles, which is not the case in 90% of the situations. A web component itself should only load its styles, while child components load theirs. Only this way it's a true web component, being reusable, maintainable and clean.

I've since found another solution that doesn't require to import each component specifically. Assuming customElement: true in vite.config.ts is set:

const styles: string[] = []
const modules = import.meta.glob('./**/*.vue');

for (const path in modules) {
  const mod = await modules[path]();
  styles.push(mod.default.styles);
}

App.styles = [styles.flat().join('')];

This is using vite glob import, not sure if a similar solution exists for webpack.

That is indeed a workaround, but not a solution.

A solution is a way of solving a problem or dealing with a difficult situation. Although I disagree with you I've edited my wording.

This would imply that every component knows every other component styles, which is not the case in 90% of the situations. A web component itself should only load its styles, while child components load theirs. Only this way it's a true web component, being reusable, maintainable and clean.

It would be helpful if you'd provide cases where this workaround isn't helping. What are the 90% of the situations?. So far your contributions to this thread have been +1 Urgently need this too! and a patronizing response where you're pulling numbers out of thin air.

@soultice's most recent solution workaround is probably the best yet. Given that 1.453% of GH's users post 84.52195% of the patronizing comments, don't let it get you down! I'm glad to see your post.

Also callout to @lroal who suggested using a global stylesheet in the top-level component, as that is the particular workaround I'm using until this issue if fixed.

I would absolutely reiterate that this should be mentioned in the docs. I was having a merry old time working on a new custom element until I hit this roadblock.

Unless I'm misunderstanding this issue, this essentially means that any Vue based custom element can only usefully exist as a lone component with no other Vue components in its subtree? It cannot be the root of a more complicated app which uses a variety of different components for example.

My use case is using custom elements as the root of a whole app, not a tiny component of many within the page. This might not be the purest use of custom elements but it's certainly a useful one and I can't imagine I'm the only one doing so. The benefits of the encapsulation provided by the shadow dom in this case are very desirable for my use case.

I'm following this "thread" for a while, thought it would be valid to join the group. This is a relevant problem.

I've since found another workaround that doesn't require to import each component specifically. Assuming customElement: true in vite.config.ts is set:

const styles: string[] = []
const modules = import.meta.glob('./**/*.vue');

for (const path in modules) {
  const mod = await modules[path]();
  styles.push(mod.default.styles);
}

App.styles = [styles.flat().join('')];

This is using vite glob import, not sure if a similar solution exists for webpack.

Hi @soultice. Thanks for sharing your solution. Would you mind elaborating on it a bit? Which file is your code snippet for? Would you mind providing a full code snippet if you have time, and if possible, include a code snippet for your vite.config.js? Sorry if it is obvious, I am having trouble implementing your suggestion and would appreciate some help. Thank you in advance :).

+1 Urgently need this too!

I've since found another workaround that doesn't require to import each component specifically. Assuming customElement: true in vite.config.ts is set:

const styles: string[] = []
const modules = import.meta.glob('./**/*.vue');

for (const path in modules) {
  const mod = await modules[path]();
  styles.push(mod.default.styles);
}

App.styles = [styles.flat().join('')];

This is using vite glob import, not sure if a similar solution exists for webpack.

Hi @soultice. Thanks for sharing your solution. Would you mind elaborating on it a bit? Which file is your code snippet for? Would you mind providing a full code snippet if you have time, and if possible, include a code snippet for your vite.config.js? Sorry if it is obvious, I am having trouble implementing your suggestion and would appreciate some help. Thank you in advance :).

Hi @shareefhadid, the code from my snippet goes into main.ts. This is my exact file:

import { defineCustomElement } from 'vue';
import App from './App.vue';

const styles: string[] = [];
const modules = import.meta.glob('./**/*.vue');

for (const path in modules) {
  const mod = await modules[path]();
  styles.push(mod.default.styles);
}

App.styles = [styles.flat().join('')];

customElements.define('vite-test', defineCustomElement(App));

The exact vite.config.ts

import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    vue({
      customElement: true
    }),
  ],
  build: {
    target: 'esnext',
    rollupOptions: {
      output: {
        format: 'esm',
        entryFileNames: `[name].js`,
        chunkFileNames: `[name].js`,
        assetFileNames: `assets/[name].[ext]`,
      },
    },
  },
});

Essentially I'm importing the styles from all sub-components and re-register them under the main component.
This line is causing the bug as styles from sub-components are not registered. If they're all squashed together under the main component that's registered as the customElement it should work

I've since found another workaround that doesn't require to import each component specifically. Assuming customElement: true in vite.config.ts is set:

const styles: string[] = []
const modules = import.meta.glob('./**/*.vue');

for (const path in modules) {
  const mod = await modules[path]();
  styles.push(mod.default.styles);
}

App.styles = [styles.flat().join('')];

This is using vite glob import, not sure if a similar solution exists for webpack.

Hi @soultice. Thanks for sharing your solution. Would you mind elaborating on it a bit? Which file is your code snippet for? Would you mind providing a full code snippet if you have time, and if possible, include a code snippet for your vite.config.js? Sorry if it is obvious, I am having trouble implementing your suggestion and would appreciate some help. Thank you in advance :).

Hi @shareefhadid, the code from my snippet goes into main.ts. This is my exact file:

import { defineCustomElement } from 'vue';
import App from './App.vue';

const styles: string[] = [];
const modules = import.meta.glob('./**/*.vue');

for (const path in modules) {
  const mod = await modules[path]();
  styles.push(mod.default.styles);
}

App.styles = [styles.flat().join('')];

customElements.define('vite-test', defineCustomElement(App));

The exact vite.config.ts

import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    vue({
      customElement: true
    }),
  ],
  build: {
    target: 'esnext',
    rollupOptions: {
      output: {
        format: 'esm',
        entryFileNames: `[name].js`,
        chunkFileNames: `[name].js`,
        assetFileNames: `assets/[name].[ext]`,
      },
    },
  },
});

Essentially I'm importing the styles from all sub-components and re-register them under the main component. This line is causing the bug as styles from sub-components are not registered. If they're all squashed together under the main component that's registered as the customElement it should work

@soultice thank you for sharing :)

Thanks @soultice I tried implementing your suggestion and it does indeed work 🙏 , however in doing so the HMR stopped working for styles in sub-components which is certainly frustrating! The content of subcomponents is correctly hot reloaded, but the styles require a page refresh to update. HMR works only on the root component for styles.

Oh well, I will keep following this thread and look forward to a first class resolution!

@murphybob I've just released a vite plugin that works around this bug, HMR included. vite-plugin-vue-hoist-ce-styles
It's in an early stage so I'd really appreciate any raised issue / support with testing

Hey @soultice that's great news but unfortunately I won't be able to try it out for a bit anyway, I just left my job and am awaiting starting the next one so I'm between dev laptops atm! (Yeah my personal one sucks I know I should do something about that lol but company ones are always better because I'm a total cheapskate!)

Hello there, I have created an alternate PR to try and fix this issue. I believe it is a bit more robust than the other attempt (#4309). It additionally supports async components and the use of <script setup>. The only missing feature I can think of is the support of globally registered components.

For some background, at our company we're currently building a micro front-end architecture that wraps each micro front-end in a custom element. Of course we want to use sub components in our micro front-ends as they are more complex than your regular component library. Because we really have need of this feature.

We will solve this, it might take a few more days or weeks though.

Hi, could you please provide an estimated time on resolving this issue? We are loving developing custom elements using Vue, but are blocked by this issue.

Thank you so much,
Taras Savka

I'm not sure if this is a same issue, when exporting a project from the playground, the css is placed in /src/ and there is no reference to it in index.html neither in the App.vue component, I believe a better place would be at /assets/ directory.

+1 Urgently need this too!

+10 Urgently need this too!

The question now is, does the next person say +100 or +19 🤔

As another workaround, here is how I got an entire application to be shipped as a custom element.

First, you need to disable your Vue components to be treated as custom elements (either by not turning on the customElement option of your bundler, or by not ending the name of your components by .ce.vue). This is important, as we want the bundler to produce a CSS file when creating the final bundle.

Then, the following file will be used as the entry point of the bundler:

import { defineCustomElement } from 'vue'
import App from './App.vue'

export function createApp (styles) {
  return defineCustomElement({ ...App, styles: [styles] })
}

On the consumer side (the application that consumes your custom element), here is how to get the custom element registered:

import { createApp } from 'your-library-name'
import styles from 'your-library-name/dist/style.css?inline'

customElements.define('your-custom-element', createApp(styles))

Note the ?inline option at the end of the CSS import. It tells Vite to not include the CSS in the <head> of your web page, instead returning it as a string.

@yyx990803 Can't this issue be fixed? Is pending for more than one year and is marked as a "minor bug". We have to work with all kinds of workaround so the <style> from SFC to be imported when we make a custom element of a Vue component.

As another workaround, here is how I got an entire application to be shipped as a custom element.

First, you need to disable your Vue components to be treated as custom elements (either by not turning on the customElement option of your bundler, or by not ending the name of your components by .ce.vue). This is important, as we want the bundler to produce a CSS file when creating the final bundle.

Then, the following file will be used as the entry point of the bundler:

import { defineCustomElement } from 'vue'
import App from './App.vue'

export function createApp (styles) {
  return defineCustomElement({ ...App, styles: [styles] })
}

On the consumer side (the application that consumes your custom element), here is how to get the custom element registered:

import { createApp } from 'your-library-name'
import styles from 'your-library-name/dist/style.css?inline'

customElements.define('your-custom-element', createApp(styles))

Note the ?inline option at the end of the CSS import. It tells Vite to not include the CSS in the <head> of your web page, instead returning it as a string.

Nice workaround. How would you use this in a monorepo without App.vue in library?

Hey @yyx990803 ,
I hope you are well.

Could you please provide some estimates on this issue (either this will be solved or not)? Seems like a lot of people are waiting on this. We use NX and have no work around this issue.

Thank you,
Taras

commented

As a simple workaround exclude the styles into a css-file and import it inside the <style> tag of the main.ce.vue file.

As a simple workaround exclude the styles into a css-file and import it inside the <style> tag of the main.ce.vue file.

Hi,

This would get very messy if you have a design system in place that re-uses components.

commented

This would get very messy if you have a design system in place that re-uses components.

That's true. But it's working for small components included once or twice on your page.
Apart from this I don't see a way to deal with this problem, tho.

I'm following up on this issue. Do we have an estimated time of completion for the fix?

Hello @yyx990803 @LinusBorg ,
I hope you are doing well.

Do you by any chance have any update on this?

Thank you so much,
Taras

Hi @yyx990803 @LinusBorg @sodatea. Should we by any chance expect a fix anytime soon? Some info would be much appreciated.

I've upgraded from vue2 to vue3 and now I'm waiting for this feature to work desperately.

Not sure why after this many comments, and all this time, this still gets no attention from the Vue team 😞. A simple update or reason as to why this has not been implemented would be sufficient.

Any update, or is it time to ditch Vue and switch to Svelte?

Any update, or is it time to ditch Vue and switch to Svelte?

Oddly enough, Svelte has the exact same issue: sveltejs/svelte#4274 where there is talk about switching to Vue.

Should we by any chance expect a fix anytime soon

This issue is labeled as "p4-important".

I can't promise, but in an ideal world, the priority queue could be like this:

  1. Fixing v3.3 regressions
  2. p4 issues in core / important issues in other core repos / other planned 3.4 features
  3. p3 issues

Since this problem has caused me some troubles, and I don’t know when the official vue will fix it, as a temporary solution, I made my PR into a vite plugin and injected the code through compilation to temporarily solve this problem problem, but for more complex scenarios still need testing, if you happen to need it too, you can use this plugin
https://github.com/baiwusanyu-c/unplugin-vue-ce/blob/master/packages/sub-style/README.md

Since this problem has caused me some troubles, and I don’t know when the official vue will fix it, as a temporary solution, I made my PR into a vite plugin and injected the code through compilation to temporarily solve this problem problem, but for more complex scenarios still need testing, if you happen to need it too, you can use this plugin
https://github.com/baiwusanyu-c/unplugin-vue-ce/blob/master/packages/sub-style/README.md

Could I using regular Vue components with scoped styles tag inside custom elements?

Since this problem has caused me some troubles, and I don’t know when the official vue will fix it, as a temporary solution, I made my PR into a vite plugin and injected the code through compilation to temporarily solve this problem problem, but for more complex scenarios still need testing, if you happen to need it too, you can use this plugin
https://github.com/baiwusanyu-c/unplugin-vue-ce/blob/master/packages/sub-style/README.md

Could I using regular Vue components with scoped styles tag inside custom elements?

cc 👀: https://stackblitz.com/edit/vitejs-vite-4wwdax?file=package.json

commented

Since this problem has caused me some troubles, and I don’t know when the official vue will fix it, as a temporary solution, I made my PR into a vite plugin and injected the code through compilation to temporarily solve this problem problem, but for more complex scenarios still need testing, if you happen to need it too, you can use this plugin
https://github.com/baiwusanyu-c/unplugin-vue-ce/blob/master/packages/sub-style/README.md

Could I using regular Vue components with scoped styles tag inside custom elements?

cc 👀: https://stackblitz.com/edit/vitejs-vite-4wwdax?file=package.json

Duplicate css

image

Thanks, I'll check it out later, maybe there's something wrong with the plug-in injection code @yoyo837

Thanks, I'll check it out later, maybe there's something wrong with the plug-in injection code @yoyo837

Sorry, this is not a code problem. Before this test project, I manually pasted and copied 1000 style tags for testing.

@raffobaffo would your solution also inject styles into the shadow DOM from child components imported within the defineCustomElement component that originate from external packages?

As an example, if I define and export a component with defineCustomElement, but within that element is a button component that is being imported from an external package (and is not created with defineCustomElement).

Also @lucasantunes-ciandt do you have a working example using the reproduction repo?

Sorry, I haven't worked in this project in over a year now so I no longer have access to it. Hope you have fixed your issue or been able to use baiwusanyu-c's plugin!

Since this problem has caused me some troubles, and I don’t know when the official vue will fix it, as a temporary solution, I made my PR into a vite plugin and injected the code through compilation to temporarily solve this problem problem, but for more complex scenarios still need testing, if you happen to need it too, you can use this plugin
baiwusanyu-c/unplugin-vue-ce@master/packages/sub-style/README.md

Could I using regular Vue components with scoped styles tag inside custom elements?

cc 👀: stackblitz.com/edit/vitejs-vite-4wwdax?file=package.json

Duplicate css

image

I remember having this issue at the time, but the culprit was another one. In my case, I vaguely remember I was trying to @import .less (could be SCSS or other preprocessor in your case) mixins and variables in our main.ts file for them to be available in all components, but it would duplicate the CSS for every component we had, so instead I had to inject it by creating a global "theme" for our app. IIRC we used style-resources-loader for this, so Webpack would handle it during build time. Hope it helps 😅

Hey all,

I wanted to give a BIG THANK YOU to @baiwusanyu-c for creating unplugin-vue-ce and resolving #4662 issue on a plugin level.

Thank you for all your hard work @baiwusanyu-c !

https://github.com/baiwusanyu-c/unplugin-vue-ce/tree/master

Best regards,
Taras

We will solve this, it might take a few more days or weeks though.

@LinusBorg looks like it takes quite a bit more than a few days or weeks. Any idea when this will be fixed?

Bump, we really need this...

Bump, we really need this...

it will reduce the amount of hacks we have to implement, for sure. now everybody is solving this problem in different ways.

anyone managed to solve this cleanly yet?