Styling not applied to vue web component during development
Asked Answered
C

2

30

While developing a Vue web component, the style is not applied to the web component, but added to the head of the document. This means that the style is ignored in the shadow DOM. Here is how I wrap the web component in main.js:

import Vue from 'vue';
import wrap from '@vue/web-component-wrapper';
import MyWebComponent from './components/MyWebComponent';

const WrappedElement = wrap(Vue, MyWebComponent);

window.customElements.define('my-web-component', WrappedElement);

Again, any CSS rules inside the style tags do not take effect.

When I build for production, the styles are added to the web component. I use the following command to do the wrapping:

vue-cli-service build  --target wc --name my-web-component ./src/components/MyWebComponent.vue

Is there a way to achieve the same thing with vue-cli-service serve?

edit: example repo here: https://github.com/snirp/vue-web-component

edit2: I have the feeling my problem is closely related to this issue. I cannot make much sense of the workarounds, and I would value a more basic solution.

Cornemuse answered 22/11, 2018 at 13:9 Comment(0)
P
28

Based on the GitHub issue you linked, the solution is to set the shadowMode option in vue-loader and vue-style-loader. shadowMode is false by default in a Vue CLI project, but we can tweak that in vue.config.js.

First, we'd inspect the Webpack config to determine which loaders to change:

# run at project root
vue inspect

The command output reveals several loader configs with shadowMode: false:

/* config.module.rule('css') */
{
  test: /\.css$/,
  oneOf: [
    /* config.module.rule('css').oneOf('vue-modules') */
    {
      resourceQuery: /module/,
      use: [
        /* config.module.rule('css').oneOf('vue-modules').use('vue-style-loader') */
        {
          loader: 'vue-style-loader',
          options: {
            sourceMap: false,
            shadowMode: false  // <---
          }
        },
        /* ... */
      ]
    },
    /* ... */

full list of Webpack loader configs with shadowMode: false:

config.module.rule('vue').use('vue-loader')
config.module.rule('css').oneOf('vue-modules').use('vue-style-loader')
config.module.rule('css').oneOf('vue').use('vue-style-loader')
config.module.rule('css').oneOf('normal-modules').use('vue-style-loader')
config.module.rule('css').oneOf('normal').use('vue-style-loader')
config.module.rule('postcss').oneOf('vue-modules').use('vue-style-loader')
config.module.rule('postcss').oneOf('vue').use('vue-style-loader')
config.module.rule('postcss').oneOf('normal-modules').use('vue-style-loader')
config.module.rule('postcss').oneOf('normal').use('vue-style-loader')
config.module.rule('scss').oneOf('vue-modules').use('vue-style-loader')
config.module.rule('scss').oneOf('vue').use('vue-style-loader')
config.module.rule('scss').oneOf('normal-modules').use('vue-style-loader')
config.module.rule('scss').oneOf('normal').use('vue-style-loader')
config.module.rule('sass').oneOf('vue-modules').use('vue-style-loader')
config.module.rule('sass').oneOf('vue').use('vue-style-loader')
config.module.rule('sass').oneOf('normal-modules').use('vue-style-loader')
config.module.rule('sass').oneOf('normal').use('vue-style-loader')
config.module.rule('less').oneOf('vue-modules').use('vue-style-loader')
config.module.rule('less').oneOf('vue').use('vue-style-loader')
config.module.rule('less').oneOf('normal-modules').use('vue-style-loader')
config.module.rule('less').oneOf('normal').use('vue-style-loader')
config.module.rule('stylus').oneOf('vue-modules').use('vue-style-loader')
config.module.rule('stylus').oneOf('vue').use('vue-style-loader')
config.module.rule('stylus').oneOf('normal-modules').use('vue-style-loader')
config.module.rule('stylus').oneOf('normal').use('vue-style-loader')

So, we can set shadowMode: true for those configs in vue.config.js with this snippet:

function enableShadowCss(config) {
  const configs = [
    config.module.rule('vue').use('vue-loader'),
    config.module.rule('css').oneOf('vue-modules').use('vue-style-loader'),
    config.module.rule('css').oneOf('vue').use('vue-style-loader'),
    config.module.rule('css').oneOf('normal-modules').use('vue-style-loader'),
    config.module.rule('css').oneOf('normal').use('vue-style-loader'),
    config.module.rule('postcss').oneOf('vue-modules').use('vue-style-loader'),
    config.module.rule('postcss').oneOf('vue').use('vue-style-loader'),
    config.module.rule('postcss').oneOf('normal-modules').use('vue-style-loader'),
    config.module.rule('postcss').oneOf('normal').use('vue-style-loader'),
    config.module.rule('scss').oneOf('vue-modules').use('vue-style-loader'),
    config.module.rule('scss').oneOf('vue').use('vue-style-loader'),
    config.module.rule('scss').oneOf('normal-modules').use('vue-style-loader'),
    config.module.rule('scss').oneOf('normal').use('vue-style-loader'),
    config.module.rule('sass').oneOf('vue-modules').use('vue-style-loader'),
    config.module.rule('sass').oneOf('vue').use('vue-style-loader'),
    config.module.rule('sass').oneOf('normal-modules').use('vue-style-loader'),
    config.module.rule('sass').oneOf('normal').use('vue-style-loader'),
    config.module.rule('less').oneOf('vue-modules').use('vue-style-loader'),
    config.module.rule('less').oneOf('vue').use('vue-style-loader'),
    config.module.rule('less').oneOf('normal-modules').use('vue-style-loader'),
    config.module.rule('less').oneOf('normal').use('vue-style-loader'),
    config.module.rule('stylus').oneOf('vue-modules').use('vue-style-loader'),
    config.module.rule('stylus').oneOf('vue').use('vue-style-loader'),
    config.module.rule('stylus').oneOf('normal-modules').use('vue-style-loader'),
    config.module.rule('stylus').oneOf('normal').use('vue-style-loader'),
  ];
  configs.forEach(c => c.tap(options => {
    options.shadowMode = true;
    return options;
  }));
}

module.exports = {
  // https://cli.vuejs.org/guide/webpack.html#chaining-advanced
  chainWebpack: config => {
    enableShadowCss(config);
  }
}

Creating <projectroot>/vue.config.js with the snippet above enables Shadow CSS in development mode in your project. See https://github.com/snirp/vue-web-component/pull/1.

Presence answered 2/12, 2018 at 6:42 Comment(4)
This needs to be in Vue Documentation! I have looked far and wide for the past 2 days for this answer.Centurion
In my case, in a Web Component + Typescript setup, styles imported as JS would not inject even with the above in the vue.config.js. The only thing that finally worked was to import scss styles in component style tag as: <style lang="scss" src="./x.scss" module></style>Reginareginald
Run these two commands to get a list like the one above: 1: npx vue-cli-service inspect > config.js — 2: cat config.js | grep ".use('vue-style-loader')". Don't forget vue-loader.Pyongyang
I got error when I build the component. What node version should I use? I tried with 17,16 and 14 and I get error. It will be nice to add the cdn for the exact vue version on code/stackoverflow answer.Atheist
B
0

An improved version of @tony19 answer with the script that does update shadowDom to true for all components, but you don't have to list them statically.

function enableShadowCss(config) {
  const configs = [
    config.module.rule('vue').use('vue-loader'),
  ];

  // based on common rules returned by `vue inspect`
  const ruleSets = ['css', 'postcss', 'scss', 'sass', 'less', 'stylus'];
  const ruleNames = ['vue-modules', 'vue', 'normal-modules', 'normal'];

  ruleSets.forEach((ruleSet) => {
    if (config.module.rules.store.has(ruleSet)) {
      ruleNames.forEach((rName) => {
        if (config.module.rule(ruleSet).oneOfs.store.has(rName)) {
          if (config.module.rule(ruleSet).oneOf(rName).uses.store.has('vue-style-loader')) {
            configs.push(config.module.rule(ruleSet).oneOf(rName).use('vue-style-loader'));
          }
        }
      });
    }
  });

  if (!process.env.BUILD_MODE) {
    process.env.BUILD_MODE = config.store.get('mode');
  }

  configs.forEach((c) => c.tap((options) => {
    options.shadowMode = true;
    return options;
  }));
}

// ...
module.exports = {
  css: {
    extract: false
  },
  chainWebpack: (config) => {
    enableShadowCss(config);
  },
}
Basketwork answered 3/7, 2023 at 16:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.