vuejs / vue-web-component-wrapper

(Vue 2 only) Wrap a Vue component as a web component / custom element.
1.05k stars 99 forks source link

How to define style within a web component? #12

Open zhaomenghuan opened 6 years ago

zhaomenghuan commented 6 years ago

I tried to use this plugin to write a UI library, but I found that the styles defined in the component did not take effect, and the template tag in the .vue file could not write style in it, and how to handle it more elegantly.

gnardecky commented 6 years ago

Playing around with this awesome wrapper, I'm finding it impossible to attach any style to my registered vue converted webcomponent.

  1. <style module> makes no difference to adding ANY style.
  2. ONLY inline style works

How can I define ANY style on my shadow DOM (from FILE 2) with this wrapper?

File 1:

<template>
  <div>
    <!-- test row-template outside of zing-grid tag -->
    <row-template branch="hello world" username="nardecky" commitId="023741as" runTime="0:27"></row-template>
  </div>
</template>

<script>
import RowTemplate from '@/components/row-template'
import wrap from '@vue/web-component-wrapper'
import Vue from 'vue'

// define custom row template immediately as a web component
const CustomElement = wrap(Vue, RowTemplate);
window.customElements.define('row-template', CustomElement);

// define templateing component
export default {
  data () {
    return {
    }
  },
}
</script>

<!-- style scoped globally or not, doesn't work for second example -->
<style>

/* works */
row-template {
  display: flex;
  justify-content: space-between;
  background: #eee;
  margin-bottom: 1rem;
  padding: 1rem;
  border-radius: .5rem;
  overflow: hidden;
}

/* Doesn't work*/
 row-template >>> .customSingleRow{
  display: flex;
  justify-content: space-between;
  background: #eee;
  margin-bottom: 1rem;
  padding: 1rem;
  border-radius: .5rem;
  overflow: hidden;
}
</style>

File 2:

<template>
  <section class="customSingleRow">
    <div class="firstCell">
      <div class="buildStatus"></div>
      <div>

        <h3>{{ branch }}</h3>
        <div class="build-contents">
            <span>{{ username }}</span>
            <span>{{ branch }}</span>
            <span>{{ commitId }}</span>
            <span>{{ runTime }}</span>
        </div>
      </div>
    </div>
    <div>
      <select>
        <option>Restart Build</option>
        <option>Reset Cache</option>
        <option>Drill Into Page</option>
      </select>
    </div>
  </section>
</template>

<script>
export default {
  name: 'row-template',
  props: ['branch', 'username', 'commitId', 'runTime'],
  data () {
    return {
    }
  },
  mounted: function() {
    console.log(this.$style)
  }
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style>
/* trying to get any selector to work for this component */
:host >>> .customSingleRow,
:host-context(section),
:host, 
row-template,
.customSingleRow {
  display: flex;
  justify-content: space-between;
  background: #eee;
  margin-bottom: 1rem;
  padding: 1rem;
  border-radius: .5rem;
  overflow: hidden;
}

/* ideally I want to just target .customSingleRow*/
</style>
gnardecky commented 6 years ago

I found a really hacky solution.

Reasoning: The reasoning for this solution is styles are handled by webpack. They are not accessible on the Vue element itself, thus it can't be solved with this wrapper correct?

Implementation

Create a custom component to render/import style tags since the compiler won't let us

File 1:

<template>
  <section>
    <custom-styles>
      .custom-single-row {
        display: flex;
        justify-content: space-between;
        background: #eee;
        margin-bottom: 1rem;
        padding: 1rem;
        border-radius: .5rem;
        overflow: hidden;
      }
    </custom-styles>

    <div class="custom-single-row">

      <div class="first-cell">
        <div v-bind:class="buildStatus"></div>
        <div>
          <h3>{{ branch }}</h3>
          <div class="build-contents">
              <span>{{ username }}</span>
              <span>{{ branch }}</span>
              <span>{{ commitId }}</span>
              <span>{{ runTime }}</span>
          </div>
        </div>
      </div>
      <div>
        <select>
          <option>Restart Build</option>
          <option>Reset Cache</option>
          <option>Drill Into Page</option>
        </select>
      </div>
    </div>

  </section>
</template>

<script>
import CustomStyles from '@/components/styles'
export default {
  name: 'row-template',
  props: ['status', 'branch', 'username', 'commitId', 'runTime'],
  data () {
    return {
    }
  },
  mounted: function() {
    console.log(this)
  },
  computed: {
    buildStatus: function() {
      return `build-status build-${this.status}`;
    }
  },
  components: {
    'custom-styles': CustomStyles
  }
}
</script>

File two is our <custom-styles> element:

<template>
  <section ref="styles" style="display:none;">
    <slot></slot>
  </section>
</template>

<script>
export default {
  name: 'custom-styles',
  props: [],
  data () {
    return {
    }
  },
  mounted: function() {
    console.log(this.$refs.styles)
    let styleTag = document.createElement('style');
    styleTag.textContent = this.$refs.styles.textContent;
    this.$refs.styles.textContent = null;
    this.$refs.styles.appendChild(styleTag);
  }
}
</script>

This will import our styles into our webcomponent, making them scoped.

screen shot 2018-04-19 at 5 13 36 pm screen shot 2018-04-19 at 5 13 24 pm

nate250 commented 6 years ago

I slightly simplified @gnardecky's custom style component like so:

const customStyle = {
  name: 'custom-style',
  render (createElement) {
    return createElement('style', this.$slots.default)
  }
}
ankurk91 commented 6 years ago

I was able to enable shadow mode with webpaack v4 and vue-loader 15 (without vue-cli v3), docs

// webpack.config.js
 module: {
    rules: [

{
        test: /\.vue$/,
        loader: 'vue-loader',
        exclude: path.resolve(__dirname, 'node_modules'),
        options: {
           shadowMode: true // vue-loader v15.0.9+
         }
      },
 {
        test: /\.css$/,
        use: [
          {
            loader: 'vue-style-loader',
            options: {
              shadowMode: true
            }
          },
          'css-loader',
          // don't use `style-loader`
        ],
      },
]
]

Now i can use vue SFC as normal

<template>
<div class="card">
<!-- add html here -->
</div>
</template>

<script>
// add script
</script>

<style>
/* no need to use CSS Modules or Scoped CSS */
@import "~bootstrap/dist/css/bootstrap.css";
.card {
   background: red
}
</style>

You also need to load import *.vue files like this, (only vue-loader >v15.0.9)

//app.js
import CustomElement from './CustomElement.vue?shadow'

Now vue-loader injects style tag within shadowRoot not in document head.

victorhramos commented 6 years ago

@ankurk91 i tried the solution you told but still goint to head? can you provide more details please?

victorhramos commented 6 years ago

@ankurk91 yay, i got it... now wondering how to make it with