Inject CSS styles inside of the shadow-root instead of the head tag | Vue.js & Webpack
Asked Answered
F

2

8

I'm making an embeddable widget for websites using Vue.js and vue-custom-element. Everything was going smoothly until I ran into a problem.

When I'm trying to use a component (with css) from a package. Like vue-number-input for example. The css gets injected in the head of the webpage even though it should be added inside the shadow root.

As you can see here you see that the css from the number input package is injected inside the head while the other stylings are in the shadow root like they should.

As far as I know I changed all of the settings needed to make the application work inside a shadow root.

This is my vue.config.js, my main.js (where I register the custom element) and my component (where I import the component from the package).

Does anyone know how I can do this or is this even possible?

Flinch answered 26/1, 2021 at 15:13 Comment(3)
did you ever figure this out? I'm trying to accomplish the EXACT same thing.Stereotropism
@Stereotropism A little late on the reply. I posted our way of approaching this problem. https://mcmap.net/q/1382588/-inject-css-styles-inside-of-the-shadow-root-instead-of-the-head-tag-vue-js-amp-webpackFlinch
I actually got it working using a mutation observerStereotropism
D
0

If the library is itself embedding the styles to the head of the page, then there is nothing you can do other than to write some script to manually copy it into the shadow dom after initialization. I ran into the same issue for Font Awesome icons. Luckily, they provided a fix for this. (https://github.com/FortAwesome/vue-fontawesome#web-components-with-vue-web-component-wrapper)

Discoverer answered 8/10, 2021 at 7:28 Comment(0)
F
0

After a lot of research for a solution which actually lazy-loaded our styles into the shadow-root, we didn't find one. We went with suboptimal solution which is loading all the styles on first load.

Note: This makes it work for QA and production environments. When developing, the styles will still be loaded inside of the head of the page. So you have to make sure no other styles conflict with that.

This was our way of accomplishing this:

// vue.config.js

const webpack = require('webpack');

module.exports = {
  configureWebpack: {
    // ...
    plugins: [
      new webpack.optimize.LimitChunkCountPlugin({
        maxChunks: 1,
      }),
    ],
  },
  chainWebpack: (config) => {
    config.optimization.delete('splitChunks');
  },
};
// main.js

import createStyleLink from '@/helpers/createStyleLink';

// ...

const options = {};

// Enable shadow root for production build
if (process.env.NODE_ENV === 'production') {
  options.shadow = true;
  options.beforeCreateVueInstance = (root) => {
    const rootNode = root.el.getRootNode();
    if (rootNode instanceof ShadowRoot) {
      root.shadowRoot = rootNode;
    } else {
      root.shadowRoot = document.head;
    }
    // After deployment, this will create a style link and put it inside the shadow root. 
    // Preferably, you would use a CDN for this.
    createStyleLink(rootNode, `${process.env.VUE_APP_URL}/css/app.css`);
    return root;
  };
}

// ...

Vue.customElement('widget', App, options);
// createStyleLink.js

export default function (node, url) {
  const link = document.createElement('link');
  link.href = url;
  link.type = 'text/css';
  link.rel = 'stylesheet';
  node.appendChild(link);
}

I'll not mark this as a solution as this is more like a suboptimal workaround.

Flinch answered 9/6, 2022 at 21:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.