Code Monkey home page Code Monkey logo

vue-web-component-wrapper's Introduction

@vue/web-component-wrapper CircleCI

Wrap and register a Vue component as a custom element.

Compatibility

Requires ES2015 classes. IE11 and below not supported.

  • If targeting browsers that natively support ES2015, but not native Web Components:

    You will also need the Shady DOM + Custom Elements polyfill.

    See caniuse.com for support on Custom Elements v1 and Shadow DOM v1.

  • Note on CSS Encapsulation When Using the Shady DOM polyfill

    It's recommended to use CSS Modules instead of <style scoped> in your *.vue files if you intend to use the Shady DOM polyfill, because it does not offer real style encapsulation like Shadow DOM does, so external stylesheets may affect your components if not using hashed class names.

  • If targeting browsers that does not support ES2015:

    You might want to reconsider since you'll be better off not using Web Components in this case.

Usage

  • dist/vue-wc-wrapper.js: This file is in ES modules format. It's the default export for bundlers, and can be used in browsers with <script type="module">.

  • dist/vue-wc-wrapper.global.js: This is for old school <script> includes in browsers that do not support <script type="module"> yet (exposes wrapVueWebComponent global).

import Vue from 'vue'
import wrap from '@vue/web-component-wrapper'

const Component = {
  // any component options
}

const CustomElement = wrap(Vue, Component)

window.customElements.define('my-element', CustomElement)

It works with async components as well - you can pass an async component factory function that returns a Promise, and the function will only be called when an instance of the custom element is created on the page:

const CustomElement = wrap(Vue, () => import(`MyComponent.vue`))

window.customElements.define('my-element', CustomElement)

Interface Proxying Details

Props

  • All props declared in the Vue component are exposed on the custom element as its properties. Kebab-case props are converted to camelCase properties, similar to how they are converted in Vue.

  • Setting properties on the custom element updates the props passed to the inner Vue component.

  • Setting attributes on the custom element updates corresponding declared props. Attributes are mapped to kebab-case. For example, a prop named someProp will have a corresponding attribute named some-prop.

  • Attributes that map to props declared with type: Boolean are auto-casted into boolean values in the following rules:

    • "" or same value as attribute name: -> true

    • "true" -> true

    • "false" -> false

  • Attributes that map to props declared with type: Number are auto-casted into numbers if the value is a parsable number.

Events

Custom events emitted on the inner Vue component are dispatched on the custom element as a CustomEvent. Additional arguments passed to $emit will be exposed as an Array as event.detail.

Slots

Slots work the same way as expected, including named slots. They also update when changed (using MutationObserver).

Scoped slots however, are not supported as they are a Vue specific concept.

Lifecycle

When the custom element is removed from the document, the Vue component behaves just as if it's inside a <keep-alive> and its deactivated hook will be called. When it's inserted again, the activated hook will be called.

If you wish to destroy the inner component, you'd have to do that explicitly:

myElement.vueComponent.$destroy()

Acknowledgments

Special thanks to the prior work by @karol-f in vue-custom-element.

License

MIT

vue-web-component-wrapper's People

Contributors

ankurk91 avatar potato4d avatar sodatea avatar sylvainpolletvillard avatar yyx990803 avatar zoeyfyi avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

vue-web-component-wrapper's Issues

组件名必须是中划线化的,比如 tag-show 而不能是 tagshow

import Vue from 'vue'
import wrap from '@vue/web-component-wrapper';
import myPicker from './picker.vue'
 
const CustomElement = wrap(Vue, myPicker)
 
window.customElements.define('mypicker', CustomElement)   //这个报错

window.customElements.define('my-picker', CustomElement)

Uncaught DOMException: Failed to execute 'define' on 'CustomElementRegistry': "mypicker" is not a valid custom element name

MDN

这是不是就意味着
1 组件名必须得是 中划线化的,不是中划线根本无法注册为web-component了。
2 如果通过customElements.define('my-picker', CustomElement)注册了组件,父组件就不能同时通过

components:{
    'my-picker':myPicker
  }

去注册了

完整代码如下(vue2 cli直接生成的项目)

<template>
  <div class="hello">
    <h1>{{ msg }}</h1>
    <div @click="handleClick(item.method)">handleCLick</div>
    <div v-for="(item, index) in array" :key="index">
      <div><span @click="handleClick(item.method,item.name)">array-click</span></div>
    </div>
    <my-picker ></my-picker>
  </div>
</template>

<script>
import Vue from 'vue'
import wrap from '@vue/web-component-wrapper';
import myPicker from './picker.vue'
 
const CustomElement = wrap(Vue, myPicker)
 
window.customElements.define('my-picker', CustomElement)


export default {
  name: 'HelloWorld',
  data () {
    return {
      msg: 'Welcome to Your Vue.js App',
      item:{
        method:'this is method',

      },
       array:[
      {
        method:'this is array method',
        name:'array item'
      }
    ]
    }
  },
  methods:{
    handleClick(args){
      console.log('args',args)
    }
  },
  components:{
    'my-picker':myPicker
  }
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.parentPicker{
  width:100px;
  background-color:green;
  height:100px;
  display:block;
}
h1, h2 {
  font-weight: normal;
}
ul {
  list-style-type: none;
  padding: 0;
}
li {
  display: inline-block;
  margin: 0 10px;
}
a {
  color: #42b983;
}
</style>

Event fired from mounted hook; how to listen?

Hi,
I have a little problem; I'am trying to listen an event fired from mouted hook, but seem not to be possible, because mounted function are always fired before the event callbacks definition

Router not working with Vue web-component

I have my vue project seted up and working fine, along with vue-router. But when build it to web-component, my <router-link> stop working..

"build-webcomp": "vue-cli-service build --mode staging --dest webcomp/static --target wc --name my-comp ./src/MyEntryComp.vue"

[Vue warn]: Unknown custom element: <router-view> - did you register the component correctly? For recursive components, make sure to provide the "name" option.

Web Components encorperating Vuetify a la carte ignore style, even when bundled together via rollup

Cool stuff you're doing here.

How I got here:

  1. Created empty project repo: mkdir test && cd test
  2. Created fresh project via vue-cli: vue create dev
  3. Added Vuetify to project: cd dev && vue add veutify
  4. Pulled config files from test/dev up to test
  5. Updated vue.config.js to point to new location:
//vue.config.js
{
//...
pages: {
    index: {
      entry: 'dev/src/main.js',
      template: 'dev/public/index.html',
      filename: 'index.html',
    }
  },
}
  1. Made a very simple component CountButton.vue which is just a wrapper of the Vuetify component <v-btn> imported a la carte (see test/dev/components/CountButton.vue)
  2. tried mounting multiple instances of the component CountButton in dev/src/main.js e.g.
// dev/src/main.js
// ...
import CountButton from './components/CountButton.vue'
new Vue({
  store,
  vuetify,
  render: h => h(CountButton)
}).$mount('#app')

new Vue({
  store,
  vuetify,
  render: h => h(CountButton)
}).$mount('#app2') // <--- copy pasted <div id="app"> and gave it new id "app2" in `public/index.html`
  1. Noticed that direct mounting this way does not work as expected (CountButton.vue was mounted and data was reactive in Vue inspector, but not the template)
  2. Updated dev/src/App.vue to contain just CountButton.vue
  3. Tried the multi-mount, and that works.
  4. Copy pasted the file dev/src/App.vue to dev/src/App0.vue and tried mounting the App0 component. This failed in the same manner as (8) as the vue-cli targets dev/src/App.vue as the entry point.
  5. Tried to then convert component to a WebComponent by adding the following package.json
"build:wc": "npx vue-cli-service build --target wc --name pub 'dev/src/components/*.vue'",
  1. inspected the resulting dist/pub.min.js and dist/demo.html files and noticed the component regained its dynamic template - error from (8) - but lost all styling

  2. Thought that this may be due to Vuetify styles not being bundled with the the Vuetify component

  3. Setup rollup to try and force the CSS to be bundled with the component (see rollup.config.js, dev/src/rollup.entry.js and package.json)

  4. then I rolled up npm run build:r

  5. then I tried to use the component: (dist/demo.html)

  6. Am back to where I was at step (13); namely, I have WebComponents, but despite bundling vuetify.min.css with the CountButton.vue component, but no styling.

This belabored process raises two questions:

  1. How to mount multiple components (not App.vue) in main.js from vue-cli3
  2. How to rollup Vue Components that are built on top of Vuetify.js to keep their styling and functionality outside of

The former is related more to vue-cli (but if you know the answer I'd appreciate it)
The latter is where my issue comes from.

How can I have @vue/web-component-wrapper convert my component and either include the requisite styles or use the stylesheets from a cdn?

a MWE repo to help figure this out can be found here

Externalize other libs than only Vue

This is more of a question.

I'm creating a POC that we want to create separated micro apps and then load them into the main app.
For example:

Main
  -- Flow-1
  -- Flow-2

For this my idea was wrap Flow-1 and Flow-2 as webcomponents and then consume them in my Main Vue App.

The problems I encontered was:

  • A Vue App don't define the Vue in window, so I had to define Vue in the window for the webcomponents to work.
in the main.js:
...
window.Vue = Vue;
...
  • All of them, the Main Vue App and both webcomponents uses VueRouter, so I face an error when importing the webcomponents.
Uncaught TypeError: Cannot redefine property: $router

Is there a way to exclude other libs from the webcomponents final bundle?

Slot content is cloned and doesn't use the existing element

The slotted content appears to just be a clone of whatever is inside the web-components body. For example if I had:

<my-first-web-component>
      <button>Something</button>
</my-first-web-component>

If I created that button programatically and inserted it inside the wrapped web component then this wrapper will simply pick that up and create VNode out of it for Vue to render. This is bad because if I attach an event listener to this element then it simply wont ever be called as the element rendered is not the same as the one I'm attaching a listener to.

I'm not certain on a good solution though, I think this needs to be handled outside of Vue. Possibly instead of creating a VNode from the element itself we could render a native slot element here and leave the slot handling up to the browser/polyfill where this may not be a problem?

Modified toVNode method to understand the 'potential' solution above:

function toVNode (h, node) {
  if (node.nodeType === 3) {
    return node.data.trim() ? node.data : null
  } else if (node.nodeType === 1) {
    const slotName = node.getAttribute('slot')
    return h('slot', slotName ? {
      slot: slotName,
      attrs: { name: slotName }
    } : null)
  } else {
    return null
  }
}

Can i prop Object and Array to my custom element ?

I use this lib and I want to prop Object and Array to my-custom-element but it past String to my custom element

<template>
  <chat-vue-web-component-wrapper :id="id" :name="name" :prefix="prefix" :thread="thread" :msg="msg"/>
</template>

<script>
export default {
  name: 'VueWebComponentWrapper',
  data () {
    return {
      thread: {
        img: 'https://vignette.wikia.nocookie.net/line/images/b/b7/1511.png/revision/latest?cb=20150809133444'
      },
      id: 99271,
      prefix: 'Mr.',
      name: 'Brown',
      msg: [
        { sender: 'user', message: 'Hello :)' },
        { sender: 'user', message: 'How are you ?' }
      ]
    }
  }
}
</script>

I built it with vue-cli-service.
Thanks.

Async component doesn't get initial props which are passed as element properties

With async components, it's impossible to pass props through element properties (useful for objects) before the component is loaded and initialized.

Example:

import Vue from 'vue'
import wrap from '@vue/web-component-wrapper'

const Component = () => new Promise(resolve => {
    setTimeout(() => {
      resolve({
        template: `<div>The value is - {{ foo.value }}</div>`,
        props: {
          foo: {
            type: Object,
            default: () => ({ value: 'banana' })
          }
        }
      })
    }, 100)
})

customElements.define('my-element', wrap(Vue, Component))

const myElement = document.createElement('my-element')

document.body.appendChild(myElement)

myElement.foo = { value: 'apple' }

Expected result:

The value is - apple

Actual result:

The value is - banana

It happens because the wrapper doesn't sync already defined element's properties with a component, but expects them to be passed after. It's the only possible flow for synchronous components but with async ones, it should be expected to have properties defined before a component is resolved.

PR with the possible fix will follow this issue

Single file build .js

Hi!

I was wondering if exists the chance to make a build without put the unpkg/vue in the html head. For example, all js and css in app.js.

Right now i need to import two files, but i prefer to import just one (app.js). This is possible?

Thanks!

Default casting of Boolean prop

Using the version of vue-web-component-wrapper included with Vue cli version 3.0.0-beta.1

I have noticed a strange / not documented behaviour of the auto casting of Boolean Props:

a prop defined as:

props: {
  aBooleanProp: {
    type: Boolean,
    default: true
  }
}

will be set to false if the prop is omitted in the web component tag:

<my-web-component />

I understand that this may be the expected behaviour in the case of web-components but since it differ from what happens in normal Vue development I think it should be documented.

Props are not properly inherited from mixins / extended components

I'm having a bunch of components that all share some properties, so I've moved them into a mixin (also tried with extending from a parent component).

The prop binding works perfectly when using the component normally, in an SPA for example. When building it as a webcomponent though, the prop binding will no longer be recognized: The component is still able to access the prop attribute correctly, but will always chose the default value - or undefined if no default is provided.

Example:

Mixin:

export default {
props: {
    theme: {
      type: String,
      default: 'defaultTheme',
    }
  },
}

MyComponent:

<template>
  <div>{{ theme }}</div>
</template

<script>
export default {
  name: 'Test',
  mixins: [theme],
}
</script>

Using the Webcompenent:

<webcomponent-test theme="custom"></webcomponent-test>
This will display 'defaultTheme' instead of 'custom'.
When adding the prop directly to the component it works fine though.

How to define style within a web component?

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.

Make global vue smaller?

I really like the simplicity of this approach to wrapping vue-components as custom-elements, but I cant figure out how to make the vuejs bundle (not my code or the wrapper - but the global vue) smaller. I'm building a very simple components and doesnt really use many features, so I would think, I could shrink the bundle considerably.
I cant find any "official" way of doing this, but am I missing something, is there a workaround/hack for it, or is it jut not possible?

web-components定义的组件对象和数组传递不进去

父组件

<template>
  <div class="hello">
    <h1>{{ msg }}</h1>
    <div @click="handleClick(item.method)">handleCLick</div>
    <div v-for="(item, index) in array" :key="index">
      <div><span @click="handleClick(item.method,item.name)">array-click</span></div>
    </div>
    <my-picker id="pickerID" :pickerInfo="pickerInfo"></my-picker>
  </div>
</template>

<script>
import Vue from 'vue'
import wrap from '@vue/web-component-wrapper';
import myPicker from './picker.vue'
window.customElements.define('my-picker', wrap(Vue, myPicker))

export default {
  name: 'HelloWorld',
  data () {
    return {
      modelValue:111,
      msg: 'Welcome to Your Vue.js App',
      item:{
        method:'this is method',

      },
      pickerInfo:{
        name:'picker-name'
      },
       array:[
      {
        method:'this is array method',
        name:'array item'
      }
    ]
    }
  },
  methods:{
    handleClick(args){
      console.log('args',args)
      console.log('this',this)
    }
  },
  components:{
    // 'my-picker':myPicker
  }
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.parentPicker{
  width:100px;
  background-color:green;
  height:100px;
  display:block;
}
h1, h2 {
  font-weight: normal;
}
ul {
  list-style-type: none;
  padding: 0;
}
li {
  display: inline-block;
  margin: 0 10px;
}
a {
  color: #42b983;
}
</style>

子组件

<template>
  <div class="hello">
    <h1>{{ id }}</h1>
    <h1>{{pickerInfo.name}}</h1>
  </div>
</template>

<script>
export default {
  name: 'picker',
  props:{
    id:{
      type:String,
      default:'defaultID'
    },
    pickerInfo:{
      type:Object,
    }
  },
  data () {
    return {
      msg: 'pickers',
      item:{
        method:'this is method',

      },
       array:[
      {
        method:'this is array method',
        name:'array item'
      }
    ]
    }
  },
  methods:{
    handleClick(args){
      console.log('args',args)
    }
  },
 
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h1, h2 {
  font-weight: normal;
}
ul {
  list-style-type: none;
  padding: 0;
}
li {
  display: inline-block;
  margin: 0 10px;
}
a {
  color: #42b983;
}
</style>

报错: [Vue warn]: Error in render: "TypeError: Cannot read property 'name' of undefined" vue.esm.js?efeb:610 [Vue warn]: Error in render: "TypeError: Cannot read property 'name' of undefined"

How to load a font with Vue Web Component Wrapper

I'm working in a new project using Vue CLI 3 and I'm having an issue when I try to load a font in a web component.

I have prepared this basic example.

<template>
  <div>
    <p>
      For a guide and recipes on how to configure / customize this project
    </p>
  </div>
</template>

<script>
export default {
  name: 'Test',
};
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style>
p {
  @import url('https://fonts.googleapis.com/css?family=Montserrat&display=swap');

  font-family: 'Montserrat';
  color: #42b983;
  font-size: 28px;
}
</style>

And this is the code generated running npm serve:

image

As you can see in the image above, it generates an <style> tag with this content:

<style type="text/css">@import url(https://fonts.googleapis.com/css?family=Montserrat&display=swap);</style>

How can I solve this?

[Request] Please improve the Documentation

At this point the documentation on this version is extremely poor compared to the original custom-elements package, so i see no reason to switch to this version just yet.
It's important to note exactly which Browsers you support and which you tested this on, and we need more code examples and tutorials on how to set this all up properly, how to access our data if we need it, things like that.

Would also be nice to see how this implementation performs when compared to React Fiber & StencilJS using an implementation of their common performance demo :)

https://stencil-fiber-demo.firebaseapp.com/perf.html

`$slots` not accessible in `mounted` when the build target is web component

I have created a github repo to demonstrate the issue I am facing: https://github.com/MrCoder/vue-wc-slot-issue. And also a SO question: https://stackoverflow.com/questions/58353854.

Basically, I found the when I set the target as web component with vue-cli-service, I cannot access this.$slots.default (undefined) in the mounted hook method. The vue file is as below:

<template>
  <div id="app">
    <slot></slot>
  </div>
</template>

<script>

export default {
  name: 'app',
  mounted() {
    window.App = this;
    // undefined
    console.log(window.App.$slots.default && window.App.$slots.default[0].text);
    this.$nextTick(() => {
      // undefined
      console.log(window.App.$slots.default && window.App.$slots.default[0].text)
    });
    setTimeout(() => {
      // correct value
      console.log(window.App.$slots.default && window.App.$slots.default[0].text)      
    });
  }
}
</script>

How to attach Vue plugin config to Wrapper Vue instance?

Hi,
I am trying to use this library for my Vue components. All of my exportable components use "vue-i18n" adding i18n property to item:

...
i18n: {
		messages: Langs
	}

In web-component-wrapper, when instance Vue object:

const wrapper = this._wrapper = new Vue({....})

I can't understand how to pass to vue instance the vueI18n instance.

Only for test, if I add:

	    i18n: {locale: 'IT'}

as property to Vue instance, all works as expected.

Converting component App with Vuex store, Nuxt or Vue router to web components

I cannot find the above stated in the readme anywhere

All the articles are focused on converting Vue only components to Web components.

However, we are gearing towards micro-frontend approach and very proactively considering to convert Vue (main front-end stack) to Web components. But since it will use Store and Nuxt.js in future for that. How can I go about the process of converting components using store and server side rendering into web components?

Few cases -

  1. For now let's say I have a very simple main.js which has code like below

`import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import store from "./store";
import "./registerServiceWorker";

Vue.config.productionTip = false;

new Vue({
router,
store,
render: h => h(App)
}).$mount("#app");
`
This file contains a Vue root instance.

Case 2 - App grows in size

How would one can go about the conversion process then.
For example, there will be base/ui/dumb components like button, input and so forth that will be without the dependency of store and other SSR stuff. That's simple to convert!
However, when dealing with components that are connected to global store and/or are pages( i.e. server side or Nuxt.js rendered), how will the conversion go like.

Case 3 - App instance file using router or SSR and store.

If someone can come up with approach towards any of the listed case, please do. We are evaluating the approach and will be great to know the possibilities too for near future if possible to help us move forward with it

Best way to load polyfills automatically

I'm currently working on a project that creates a web component using the Vue Web Component Wrapper library.

I execute this command to generate the web component library:

vue-cli-service build --target wc --name widget ./src/components/widget.vue

At this point, I have compiled the web component and the next step is to load the polyfills to add compatiblity with the old browsers versions.

To avoid load manually the polyfills in each website where the component is used, I have been thinking about to create a 'loader.js' script inside this project to load automatically the polyfills.

So with this aproach, the final user only has to reference the 'loader.js' script and it will check the browser compatibility and load the required files.

An example of loader will be something like this: (I haven't tested it)

function loadScript(src) {
  const script = document.createElement('script');
  script.async = true;
  script.rel = 'preload';
  script.src = src;
  document.head.appendChild(script);
}

if (!('customElements' in window)) {
  loadScript('dist/webcomponents-bundle.js');
}

if (!HTMLElement.prototype.attachShadow) {
  loadScript('dist/shadydom.min.js');
}

loadScript('dist/widget.min.js');

With this code, I need to have inside the dist folder the 'loader.js' script and the polyfills, to be able to serve that files when user loads the script.

How should I create this loader inside the vue cli project?

To generate it when the componet is compiled, I've tried to use the copy-webpack-plugin to copy all the files inside dist. But with this solution I'm not able to use the environment variables inside the script for example.

configureWebpack: {
    plugins: [
      new CopyWebpackPlugin(
        [
          {
            from: 'src/loader.js',
            to: '.',
          },
          {
            from: './node_modules/@webcomponents/webcomponentsjs/webcomponents-bundle.js',
            to: './loader',
          },
          {
            from: './node_modules/@webcomponents/shadydom/shadydom.min.js',
            to: './loader',
          },
        ],
      ),
    ],
  },

What is the best way to achieve this?

Regards

browser error on props not found

After creating a web component and deployed it into an html i'm getting this error :

Uncaught TypeError: Cannot read property 'props' of undefined
    at initialize (vue-wc-wrapper.js:113)
    at wrap (vue-wc-wrapper.js:261)
    at Module.WnQP (entry-wc.js:11)
    at __webpack_require__ (bootstrap:19)
    at bootstrap:83
    at bootstrap:83

But the component is working.

My component is developped with vue-cli 3.0.0-rc3

Typescript support

I have used vue-cli to create a Vue.js project with typescript support. I want to use this wrapper to generate the Web Components but I can not build with the vue-cli-service tool.

2:18 Could not find a declaration file for module '@vue/web-component-wrapper'. './node_modules/@vue/web-component-wrapper/dist/vue-wc-wrapper.js' implicitly has an 'any' type.
Try `npm install @types/vue__web-component-wrapper` if it exists or add a new declaration (.d.ts) file containing `declare module 'vue__web-component-wrapper';`

If I add a dummy typescript definition, I can build it but then it does not work as expected when loading the generated javascript library. I have loaded the polyfill and the vue lib in a HTML page like this:

    <script src="https://unpkg.com/vue"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/webcomponentsjs/1.2.0/webcomponents-loader.js"></script>
    <script src="../dist/be-company-elements.js"></script>
    <be-dog/>
`
But it does not work, in the console log I can see the following:

`
Uncaught TypeError: Cannot read property 'props' of undefined
    at initialize (be-company-elements.js:1093)
    at wrap (be-company-elements.js:1241)
    at Module.5a74 (be-company-elements.js:1837)
    at __webpack_require__ (be-company-elements.js:20)
    at 044b.module.exports (be-company-elements.js:84)
    at be-company-elements.js:87

TypeError: Cannot read property '_injectedStyles' of undefined
    at addStyles (be-company-elements.js:645)
    at addStylesToShadowDOM (be-company-elements.js:627)
    at Module.8404.module.exports.__inject__ (be-company-elements.js:2182)
    at VueComponent.injectStyles (be-company-elements.js:1677)
    at VueComponent.hook (be-company-elements.js:1320)
    at callHook (vue.js:2921)
    at VueComponent.Vue._init (vue.js:4613)
    at new BeDog (vue.js:4785)

...

Uncaught TypeError: Failed to construct 'CustomElement': The result must implement HTMLElement interface

Can Typescript Vue components be generated as Web Components?

Webcomponents (vue build –target wc) used in another Vue project -> problems with events (copy from vuejs forum)

Hi there!

So I’m struggling for this for a while. I can’t seem to get a simple click event working inside a nested webcomponent. The webcomponent ListItem & List is exported via

vue-cli-service build --target wc --name wk --dest ./build/ 'src/components/*.vue

ListItem.vue code:

<template>
  <li
    class="list-group-item"
    :class="classes"
    @click="itemClick">
    <slot></slot>
  </li>
</template>
<script>
export default {
  name: 'ListItem',
  computed: {
    attributes() {
      return {
        'aria-disabled': this.disabled,
      };
    },
    classes() {
      return {
        active: this.active,
        disabled: this.disabled,
        'list-group-item-action': this.action,
      };
    },
  },
  props: {
    active: {
      type: Boolean,
      default: false,
    },
    action: {
      type: Boolean,
      default: false,
    },
    disabled: {
      type: Boolean,
      default: false,
    },
  },
  methods: {
    itemClick(e) {
      this.$emit('click', { e, vm: this });
      this.$emit('tick', { e, vm: this });
    },
  },
};
</script>

Those webcomponents (ListItem/List) are then used in another Vue project. As such:

    <wk-list>
      <wk-list-item
        active
        @click="clicked"
      >
        <div class="d-flex w-100 justify-content-between">
          <h5 class="mb-1">
            List group item heading
          </h5>
          <small>3 days ago</small>
        </div>
        <p class="mb-1">
          Donec id elit non mi porta gravida at eget metus. Maecenas sed diam eget
          risus varius blandit.
        </p>
        <small>Donec id elit non mi porta.</small>
      </wk-list-item>
      <wk-list-item
        action
        @click="clicked"
      >
        TEST
      </wk-list-item>
    </wk-list>

And the layout shows, etc.
But the click never triggers… (I tried working with event modifiers.native / .native.stop/ .native.prevent/ .native.prevent.stop, to no avail)

Can someone point me in the right direction please? Thanks in advance!

**PS: it does trigger in the normal vue project, project where they are build to webcomponents **

Scoped Slot Alternatives?

Hi Evan,

Are scoped slots completely unsupported? It states that they can't be used at the moment in the docs.
Are you able to elaborate on the main roadblock to this?

I've got a perfect use-case for embedding a vue.js component as a snippet for people to paste into their website but it relies on scoped slots. I basically want them to provide a template and I provide all of the hooks for integrating with my API.

If this is actually impossible, is there any alternative? I'd like to use the reactivity of Vue without mounting to someones full page (since they might already be using it!)

Thanks in advance :)

Disable Shadow DOM

Is there a way to disable Shadow DOM like in karol-f/vue-custom-element?

Native Slot - please explain how to use

I am trying to pass in a slot via webcomponents but it appears as if the wbecomponent slot is ignored and Vue interprets it to be a Vue slot. I'd love to see an example of how this is supposed to work, given that right now it seems as though my slot is being picked up by vue and not the component-wrapper.

Something like
((in the dom)

<component-name>
Content
</component-name>

then

down the line:

image

i can see this is fully in shadowRoot and not even picked up as a 'slot' (which are rendered in litedom)

this is not even the content i am trying to pass from outside.

here's a bit of my chain:

<template>
  <div class="main">
    <content-header />
    <router-view>
      <slot>Default content 1</slot>
    </router-view>
  </div>
</template>

and the component from the router:

        <slot>
            <div>default content</div>
        </slot>

So passing in slot from router-view worked. but not in litedom. Also its ignoring content from the outer component.

Where should scoped css live?

Shadow DOM supports scoped CSS (inside of the shadow tree), but it's not possible to use scoped css inside of templates with Vue, when I try it, vue throws:

- Templates should only be responsible for mapping the state to the UI. Avoid placing tags with side-effects in your templates, such as <style>, as they will not be parsed.

My template has the style tag:

<template id="twenty-post">
    <div>
        <div class="loader" v-if="loading">Loading post...</div>
        <article>
            <slot v-if="!loading"></slot>
        </article>
        
        <style>
            .loader {
                color: red;
            }
        </style>
    </div>
</template>

Properties from parent are not acessible

Usage:

<my-element data-id="1">test</my-element>

Component code:

MyElement = {
  template: `<div v-bind="$attrs">Content</div>`
}

Expected result

  • <div data-id="1">Content</div>

Actual result

  • <div>Content</div>
  • js error: [Vue warn]: Property or method "$attrs" is not defined on the instance but referenced during render. Make sure to declare reactive data properties in the data option. (found in <Anonymous> - use the "name" option for better debugging messages.)
  • v-bind="$attrs" doesn't bind attributes to component

Demo

Related documentation

double rendering briefly in non-chrome browsers

My vue web component renders great in chrome but there are some brief rendering issues in firefox (flashes both web components at first). I'm not sure how to go about debugging this... whether this can be fixed in the loading of scripts or in the vue component itself (https://github.com/crdschurch/feature-flag-web-component/blob/master/src/components/feature-flag-web-component.vue).

The web component should load a single "Give" button. See codepen below in chrome vs. firefox.
https://codepen.io/timmyg/pen/vaKWKG

Self contained web components

Thanks a lot for this great way to wrap Vue components as web components!

I have a question, is there a way to include all external libraries, like Vue itself (and for example Vuetify and even Stylesheets) in the resulting javascript that you get when calling something like yarn build --target wc --name my-element?
By default Vue has to be loaded in the html, for example by <script src="https://unpkg.com/vue"></script>.
I'm in need of a really self-contained web-component.

In `CustomElements`, DOM `#shadow-root` `slot` semantics are not being honored, Vue is stepping on them

TL;DR
Issue:
vue-web-component-wrapper prevents a WebComponent #shadow-root from containing DOM rendered slot elements.

In its implementation, the wrap function it provides is focused on creating CustomElements tags that behave as VueComponents; rather than focusing on behaving as WebComponents defined from a VueComponent that integrates, with shadow-DOM semantics, as a direct child-element in their #shadow-root definition.

I've written a Vue vue-web-component-plugin (<=) , using a different design, that supports DOM WebComponent slot behavior. It is approximately ~100 lines of self-contained ready to use Javascript .msj module code.

Some of the other differences from wrap are:

  • seamless Vue.component registration via Vue.component(..)
  • any ancestral $root vue is inherited/shared, or if none, then a $root vue is created as needed
  • lazy Vue integration of DOM upgraded custom HTMLElements; reducing temporal side-effects.
  • vueDefinition key shadowCss support for #shadow-root encapsulated css definitions

For slots to work, I had to enhance Vue with a way to honor DOM semantics for #shadow-root slot elements.

Note:
The following discussion assumes some familiarity with the DOM concepts slot, light DOM and shadow DOM.

For more detail on what wrap does, and why it is different from the plugin I mentioned above, I will briefly present a minimal description of how Vue VueComponents work and describe a few browser terms for clarity.

The Vue system has VueComponents (similar to classes) that are based on vueDefinitions associated with a tagName using Vue.component(tagName, vueDefinition). I will be referring to these VueComponents as HTML macros, from here on.

When you create a new $root vue using new Vue(vueDefinition) with a el: member in the vueDefinition (or manually invoke its' $mount), Vue will walk the childElements and recursively perform macro substitution of all the elements whose tagName matches the VueComponents you registered using Vue.component(tagName, vueDefinition). It will continue to perform this behavior based on reactive change tracking from that point on.

It does this using the render function defined on the vueDefinition.

If no render function is defined, it will look for a template to compile into a render function. If no template is defined, it will extract the innerHTML of the corresponding tagName element, and use that as the template.

This makes Vue a nicely designed and powerful reactive HTML macro (aka VueComponent) system, where arguments to the tagName element macros are provided in their attributes, and all rendered macro results are reactively tracked for efficient management of change propagation (observe and react patterns from Smalltalk). In that same system templates and (innerHTML) support {{..}} substitution rules.

The browser DOM allows you to define CustomElements and WebComponents.

To enable that, it provides the customElements API to allow registering a Javascript class as the behavior for an element with a hyphenated tagName.

A custom element once registered is a new type of browser DOM element; depending on your browser, custom elements technology is the same technique by which it internally implements a number of the intrinsic browser elements with closed #shadow-roots.

A custom element is not a macro and thus can't do some of the things Vue VueComponents can, but a WebComponent using a shadow DOM #shadow-root can provide some capabilities that Vue VueComponents cannot. This can be specifically seen in how it uses DOM slot elements, and how it lazily upgrades tagName elements when a corresponding custom element is registered, and in how it encapsulates the #shadow-root elements including css.

When you issue the call:

customElements.define(tagName, behavior_class, options)

you are defining a CustomElement. If that CustomElement will utilize a #shadow-root then you are defining a WebComponent. A WebComponent uses slot elements within the #shadow-root for slotting (transposing) the innerHTML (DOM Nodes) contained within any HTML that has that tagName element.

What's the problem, why should I think about these things?

Given that background we now have the foundation to understand why wrap does not provide expected WebComponent #shadow-root semantics and behavior, but does allow self-contained CustomElement isolated $root wrappering of Vue VueComponent vueDefinitions.

Phew, sorry you had to read all that. It is a lot of pedantically crafted explanatory discussion for saying DOM slot elements in a WebComponent's shadowRoot don't work in Vue, and that you can't use WebComponents (defined by a vueDefinition) properly in Vue without some modifications to Vue. Specifically, we need a way to tell Vue to treat appropriately marked slot elements, as DOM slot elements and not as Vue slot macros.

Basically, Vue thinks that any slot elements it sees within a vue subtree of VNodes are slot elements that it should always use as its (Vue's) own macro element directives for performing transpositional replacement of innerHTML content into a render/template definition.

That behavior leads to a direct conflict with the DOM semantics of slot elements in #shadow-roots.

In other words Vue grabs all slot elements wherever it sees them, which with wrap defined CustomElements includes their #shadow-root. Therefore the DOM never gets to see them in a wrapped WebComponents #shadow-root and can't perform the expected DOM slotting semantics.

Can't apply styles with Shadow Dom

Hello,

I am building a Web Component Todo app comparison between several alternatives such as Polymer, Stencil, Angular Elements and now with Vue :)

Code: https://github.com/shprink/web-components-todo/tree/master/vue
Demo: https://wc-todo.firebaseapp.com/vue/

I cannot apply any style to Vue Web component:

image

I tried withscoped and module but without success. I use Chrome and the other implementations with Shadow DOM work.

Let me know what to do to apply styles.

thanks

Can't get slots to work

I am trying to get pass a title to my wrapped web-component but it does not seem to work. Am I missing something?

index.html:

<body>
    <my-component>
        <h1 slot="main-title">This should be the title</h1>
    </my-component>
</body>

my-component.vue:

<template>
    <article>
        <div>
            <slot name="main-title">Place title here</slot>
        </div>
    </article>
</template>

The result is that I only see the default title and not my actual title.
Within vue, this kind of slots (not scoped) work just find, so I suspect that the wrapper does some things a little bit different.

Please help.

wrap from vue instance

Hi,

My vue aplication using $root in some parts, how could i wrap web component from new Vue(..) ?

Setting props before mounting the web component causes them to be overwritten by default values

Given a web component which accepts an input property eg.

import Vue from 'vue';
import Component from 'vue-class-component';
import { Prop } from 'vue-property-decorator';

@Component({
    name: 'my-custom-element',
})
export default class ExampleComponent extends Vue {
    @Prop({
        default: () => ({ anObject: 'Testing!' })
    }) someProp: { anObject: string };
}

And then creating it via JS:

const custom = document.createElement('my-custom-element');
custom.someProp = { anObject: 'Changed!' };

document.body.appendChild(custom);

The value of someProp is immediately overwritten once the component has been attached to the DOM. If we move the custom.someProp assignment after the call to appendChild it works but that shouldn't be necessary.

Have some problems with displaying WebComponent content...

I use your lib in pair with WebExtension, wrap vue-component in "vue-web-component-wrapper" and try to inject it into page... gets empty document...

2018-03-21 18-41-48

As You can see on screenshot - element actually in DOM, but is not accessible...
seems like some problem with shadow-root, can You help me?

Is this project dead?

Currently we have a lot of components written in VueJS and want to provide them to other teams as web components. But as it seems there is no ongoing development in this project.

Features needed:

  • Expose inherited properties from extends and mixins (this was already implemented in VueCustomElement)
  • Allow to hook into the web components lifecycle events

We could improve the web component wrapper, but as it seems the open PR are neither declined nor merged. Please give us feedback, otherwise we have to maintain our own fork and try to use the extended wrapper in our webpack config.

Missing Css!!

hi!, I constructed a component using Vuetify (https://vuetifyjs.com/), then I wanted to make a Webconponent and the result was not good, Missing Css and icons.

Component:
wrap1

WebComponent
wrap2

Road to Constructable Stylesheet Objects

Hice guys!

There's a plan to apply Constructable Stylesheet Objects (and polyfill) on future?

Maybe here:

class CustomElement extends HTMLElement {

Recently I've created a Vue component with this wrapper, but I implement the Stylesheet adoption on mounted()

this.$el.parentNode.shadowRoot.adoptedStyleSheets = [styleObject]

Thanks!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

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

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.