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

Viewed 1364

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?

2 Answers

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.

Related