Coder Social home page Coder Social logo

karol-f / vue-custom-element Goto Github PK

View Code? Open in Web Editor NEW

This project forked from vuejs/vue-element

2.0K 34.0 187.0 14.96 MB

Vue Custom Element - Web Components' Custom Elements for Vue.js

Home Page: https://karol-f.github.io/vue-custom-element/

License: MIT License

JavaScript 98.96% Shell 1.04%
custom-elements vuejs vuejs2 web-components vue-custom-element

vue-custom-element's Introduction

Vue-custom-element

Table of contents

Demo

You can check out Vue-custom-element demos at https://karol-f.github.io/vue-custom-element/

Installation

NPM

npm install vue-custom-element --save
import vueCustomElement from 'vue-custom-element'

Vue.use(vueCustomElement);

To build your web-component using vue-cli, you have to use the following command:

vue-cli-service build --target lib --name your-component-name src/main.js

Note: the command vue-cli-service build --target wc does not work, since it using vue's own vue-web-component-wrapper.

Direct include

If you are using Vue globally, just include vue-custom-element.js and it will automatically install the Vue.customElement method.

<script src="path/to/vue-custom-element.js"></script>

Optional polyfill

For cross-browser compatibility (IE9+) use Custom Elements polyfill.

<script src="https://cdnjs.cloudflare.com/ajax/libs/document-register-element/1.4.1/document-register-element.js"></script>

or

import 'document-register-element/build/document-register-element';

Description

Vue-custom-element is a tiny wrapper around Vue components. It provides a seamless way to use Vue components in HTML, plain JavaScript, Vue, React, Angular etc., without manually initialising Vue. It's using power of Web Components' Custom Elements.

  • Works with Vue 0.12.x, 1.x and 2.x
  • Small - 2.7 kb min+gzip, optional polyfill - 5 kb min+gzip

Why you might need Vue-custom-element?

Vue-custom-element

It might be confusing for users to understand the different use cases of Vue components vs Custom Elements .

Why might you need Vue-custom-element? Simple, for your Vue components user's convenience. All they will need to do is include your JavaScript file and then they can:

  • include components as HTML tags (e.g. <my-component></my-component>) at any time of the document lifecycle. You can use your elements in e.g. SPA application just by including HTML tag - no Vue initialization or JavaScript usage is needed. Custom Elements will auto initialize when mounted into document. You can include them in e.g. Vue, Angular or React projects and browser will take care of detecting it and initialization
  • use a simple API that allows for interacting with underlaying Vue instance by changing attributes, props or listening to events
  • take advantage of features like lazy-loading, that allows for loading components on demand, only when user add them to document

Features

  • Simplicity - only tag-name and Vue component object are needed for Vue.customElement() usage
  • Compatibility - using the optional polyfills a wide range of browsers is supported, including IE9+, Android and IOS
  • Full featured - you can use nesting, HMR, slots, lazy-loading, native Custom Elements callbacks.
    • reactive props and HTML attributes
    • automatic props casting (numbers, booleans) so they won't be available as strings but proper data types
    • listening to Vue component $emit'ed events
    • 'default' and 'named' slots are available for passing static content, check out the demo for an example
    • Hot Module Replacement for seamless developer experience (unminimized build, Vue 2.x+)
    • lazy-loading - you can download a component after it's attached to document. Useful for e.g. UI library authors. Check out the demo for an example
    • detect if detached callback is not invoked due to opening vue-custom-element in modal - element is then detached and attached to DOM again. It would be undesirable to destroy it immediately
  • Custom Elements v1 - compatible with latest specifications. Vue-custom-element will use native implementation if supported

Check out the demo site to see it in action.

Example

For additional examples and detailed description check the demos page.

Custom Element HTML
<widget-vue prop1="1" prop2="string" prop3="true"></widget-vue>
JavaScript - register with Vue-custom-element
Vue.customElement('widget-vue', {
  props: [
    'prop1',
    'prop2',
    'prop3'
  ],
  data: {
    message: 'Hello Vue!'
  },
  template: '<p>{{ message }}, {{ prop1  }}, {{prop2}}, {{prop3}}</p>'
});
JavaScript - element API usage
document.querySelector('widget-vue').prop2 // get prop value
document.querySelector('widget-vue').prop2 = 'another string' // set prop value

You can also change <widget-vue> HTML attributes and changes will be instantly reflected.

ShadowDOM Example

By default, vue-custom-element does not mount underneath a ShadowDOM. While this is easier to develop and debug, CSS stylings for the parent can contaminate the component, and stylings for the component can contaminate the parent. This is most prevalent when using prebuilt UI libraries which assume the component is the only content on the page (i.e. Vuetify). A ShadowDOM prevents this contamination by isolating the components and stylings within an HTML document fragment.

In these situations, components should be mounted underneath a shadowDOM using the option

Vue.customElement('widget-vue', CustomWidget, {
  shadow: true,
  beforeCreateVueInstance(root) {
    const rootNode = root.el.getRootNode();

    if (rootNode instanceof ShadowRoot) {
      root.shadowRoot = rootNode;
    } else {
      root.shadowRoot = document.head;
    }
    return root;
  },
});

The additional beforeCreateVueInstance is only required if your Vue component has bundled stylings and you are using css-modules with Webpack to bundle (which is most use cases). In addition, if you are using vue-loader and vue-style-loader plugins with Webpack, you will need to pass the shadowMode: true option to the plugins also. This is required so the plugins know they that CSS styles should be attached under the shadowDOM and not in the document.head (which is the default behavior).

webpack.config.js example for scss stylings

{
    test: /\.vue$/,
    use: [
        {
            loader: 'vue-loader',
            options: {
            	shadowMode: true
            }
        }
    ]
},
{
    test: /\.scss$/, //as example I used scss
    use: [
        {
            loader: 'vue-style-loader',
            options: {
                shadowMode: true
            }
        }
    ]
}

vue.config.js for Vue CLI 3

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 = {
  chainWebpack:
    (config) => {
      enableShadowCss(config);
    },
  css: {
    sourceMap: true,
    extract: false,
  },
};

Note: for stylings to work, CSS must be bundled in JS and injected at runtime. Otherwise you will need to manually link the CSS under the shadowDOM inside your component (which is obviously an anti-pattern).

For additional ShadowDom examples see: https://github.com/bryanvaz/vue-custom-element-shadow-examples

Browser support

Firefox
Firefox
Chrome
Chrome
Safari
Safari
Opera
Opera
iOS Safari
iOS
Chrome for Android
Android
63+ 54+ 10.1+ 42+ 10.3+ 55+

Custom Elements v1 support

With optional polyfills

IE / Edge
IE / Edge
Firefox
Firefox
Chrome
Chrome
Safari
Safari
Opera
Opera
iOS Safari
iOS
Chrome for Android
Android
IE9+, Edge

Options

Additional, optional, third parameter to Vue.customElement() is options object. You can pass following methods.

'This' in callbacks points to Custom Element's DOM Node.

{
  // 'constructorCallback' can be triggered multiple times when e.g. vue-router is used
  constructorCallback() {
      console.info('constructorCallback', this);
  },

  // element is mounted/inserted into document
  connectedCallback() {
    console.info('connectedCallback', this);
  },

  // element is removed from document
  disconnectedCallback() {
    console.warn('disconnectedCallback', this);
  },

  // one of element's attributes (Vue instance props) is changed 
  attributeChangedCallback(name, oldValue, value) {
    console.info('attributeChangedCallback', name, oldValue, value);
  },
  
  // Root component's definition is passed to this hook just before Vue instance creation - so you can modify it
  beforeCreateVueInstance(RootComponentDefinition) {
    console.info('beforeCreateVueInstance', RootComponentDefinition);
    return RootComponentDefinition;
  },
  
  // Vue instance is created
  vueInstanceCreatedCallback() {
    console.info('vueInstanceCreatedCallback');
  },
  
  // in case of using vue-custom-element with modals, we destroy  it after defined timeout. Use "null" value if you want to manually "$destroy" it.
  destroyTimeout: 3000,
  
  // only needed when using lazy-loading - 'props' are not accessible on Custom Element registration so we have to provide them
  props: [],

  // you can set shadow root for element. Only works if native implementation is available.
  shadow: false,
  
  // you can set CSS that will be available in Shadow DOM.
  shadowCss: ''
}

Example options usage:

import MyElement from './MyElement.vue';

Vue.customElement('my-element', MyElement, {
  shadow: true,
  shadowCss: `
  .card {
     background-color: blue;
  }`
});

Callbacks are executed before the lifecycle hooks from Vue component passed to Vue-custom-element. It's a better idea to just use Vue component lifecycle hooks (e.g. created, mounted, beforeDestroy).

How does it work?

Vue-custom-element

Inside HTML tags of the defined custom element, Vue-custom-element will create:

  • Proxy component for seamless Hot Module Replacement, using render function for performance (Vue 2.x+)
  • Vue component is passed to Vue-custom-element

Custom Element HTML tag will expose API to interact with underlying Vue component - you can change HTML attributes or props, using JavaScript.

Testing

For advanced access, when exposed API is not enough, defined custom element can expose Vue instance via getVueInstance() method.

console.info(document.querySelector('widget-vue').getVueInstance())

Caveats

  • custom elements must contain a hyphen in its tag name. For example, my-element is valid, but myelement is not
  • in dev mode Vue will display console warning about element not being registered. It's desirable behaviour as we want to use browser's Custom Elements registration. You can use https://vuejs.org/v2/api/#ignoredElements to get rid of this warnings.

Contribute

Development

npm install
npm run dev:demo

Release

npm run build

This command will compile vue-custom-element.js and docs files.

Please take a note that npm run build will use config.build.assetsPublicPath, which is set to Github Pages path in config/index.js.

License

MIT

vue-custom-element's People

Contributors

aki77 avatar akki-jat avatar alpacabi avatar amisora avatar au-z avatar bezany avatar calvernaz avatar dinony avatar isaaclyman avatar karol-f avatar kazupon avatar mitchellbouwman avatar pimlie avatar piotros avatar qpitlove avatar sergejkasper avatar timctp avatar yyx990803 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  avatar  avatar  avatar  avatar

vue-custom-element's Issues

Not working on passing an object as prop

I have tried to pass an object as a prop but is not working. Trying with Vue.component it works perfectly but when proxied by this library the following won't work: (component.vue)

<template>
  <div>Then: {{ data }}</div>
</template>

<script>
  export default {
    props: {
      data: {
        type: Object
      }
    },
    mounted: function () {
      console.log(this.data)
    }
  }
</script>

Then applying vue-custom-element:

import 'document-register-element/build/document-register-element';
import Component from './component.vue';
import vueCustomElement from 'vue-custom-element';

// Enable the plugin
Vue.use(vueCustomElement);

// Register your component
Vue.customElement('component-elt', Component);

And calling this element from html:

<component-elt :data="{x:1}"/>

The output logged to the console is only {__ob__:Observer} while using the Vue.component it logs the {x:1}

Binding object from parent to child component

Hi man,
Good?
First, congrats for your work, it's a very cool project.
I just have a small question. Here a jsfiddle to illustrate my concern :

I try to bind an object "todo" from my v-for directive inside my nested component "s-todos-item" using the :todo="todo" notation.
The issue is that it set me the value as "[object Object]". Is there something I missed in the documentation or is that really an issue?
How can I pass my "todo" object down the my child component?

Thanks in advance for your answer and your work 👍

Cheers man!

Generated CSS outside of web component

Hi,

first of all thank you for your plugin, this is awesome.

So, I am using the Vue CLI to generate my project with the Webpack template. Then I use your plugin to render my project in a web component instead of into an element. The problem with this is, the generated CSS now is a file linked outside of the web component and not part of it. Do you have any solution for this?

Thank you
Lukas

How to build multi elements in one project?

How can I build something like 3 components using only one project with vue-custom-element and webpack?

scr:

\webcomponets:

  • widget1.vue
  • widget2.vue
  • widget3.vue

output bundles:

  • bundle-widget1.js
  • bundle-widget2.js
  • bundle-widget3.js

Need this help to reuse some code between components.

[Vue warn]: Method "$emit"

To begin, thank you for this component, I think it's really good work :)

For the warn problem, I try something like this for vue2 in createVueInstance and i think it work fine.

export default function createVueInstance(element, Vue, componentDefinition, props, options) {
...
...

// Duplicated the  original vue bind function
function bind (fn, ctx) {
  function boundFn (a) {
    var l = arguments.length;
    return l
      ? l > 1
      ? fn.apply(ctx, arguments)
      : fn.call(ctx, a)
      : fn.call(ctx)
  }
  // record original fn length
  boundFn._length = fn.length;
  return boundFn
}

// Comment your $emit definition (only for vue2)
/* 
ComponentDefinition.methods.$emit = ctorOptions.methods.$emit = function emit(...args) { // eslint-disable-line no-multi-assign
  customEmit(element, ...args);
  this.__proto__ && this.__proto__.$emit.call(this, ...args); // eslint-disable-line no-proto
};
*/

// move your $emit code in beforeCreate hook
ComponentDefinition.beforeCreate = ctorOptions.beforeCreate = function beforeCreate() {
  this.$emit = bind(function emit(...args) {
    customEmit(element, ...args);
    this.__proto__ && this.__proto__.$emit.call(this, ...args); // eslint-disable-line no-proto
  }, this);
};

I know this is only a warning and although the component works fine but have you tried this solution?

Bye

Example of using CSS

Do you have any examples of getting CSS inside the custom element's shadom root?

I am unable to create vue-custom-element using vue-component MyElement although I can do it using MyElement.options

Issue:
I am unable to create vue-custom-element by passing Vue component object as second parameter in Vue.customElement('my-element', MyElement).
although I can create vue-custom-element by passing Vue component object.options as second parameter in Vue.customElement('my-element', MyElement.options).

Scenario:
(1) I am not using any build system.
(2) I am using precomplied minified vue.js and vue-custom-element.js into index.html as an external script.

Files:

  • app.js file
    1. created global vue-component using:
    var MyGlobalComponent = Vue.extend({...});
    2. registered vue-component:
    Vue.component('my-global-component', MyGlobalComponent);
    3. accessed vue-component into a variable MyElement:
    var MyElement = Vue.component('my-global-component');
    4. created vue-custom-element:
    Vue.customElement('my-element', MyElement, {...}); // this is not working
    Vue.customElement('my-element', MyElement.options, {...}); //this is working
  • index.html file
    <my-element myvar="abc" id = "myelmt"/>< my-element>

mutating the [[Prototype]]

I have a problem getting a simple example to work.
Vue.js v2.4.4
vue-custom-element v1.3.0

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Vue Example</title>     
  </head>
  <body>
	<p>Testing</p>
    <widget-vue></widget-vue>
  </body>
  <script src="vue.min.js" type="text/javascript"></script>
  <script src="vue-custom-element.js" type="text/javascript"></script>
  <script>
		Vue.customElement('widget-vue', {
			template: '<p>Testing widget-vue</p>'
		});
  </script>
</html>

Tested with following
Chrome 61.0 (Works)
Firefox 47.0.1 (not working)
Firefox 56.0 (not working)
IE 11.6 (not working)

With Firefox 47 I got a following warning:
"mutating the [[Prototype]] of an object will cause your code to run very slowly; instead create the object with the correct initial [[Prototype]] value using Object.create"
vue-custom-element.js:47:1

Although I'm not sure if this warning is the root problem or is there something wrong with my example.

Not working on Firefox even with polyfill.

I have everything working flawlessly on Chrome, Edge, IE9+, Android, and iOS. However, doesn't work on Firefox, even with polyfill. No errors. Even the expected, [Vue warn]: Method "$emit" conflicts with an existing Vue instance method. Avoid defining component methods that start with _ or $. error is missing. Just a silent failure and the component's DOM isn't getting inserted into the custom element body.

Chrome/Edge/IE9+/Android/iOS:

notbroken

Firefox:

broken

The demos on the main site are broken as well, for some easy test cases.

Custom component warning

I am getting the fallowing warning:

[Vue warn]: Method "$emit" conflicts with an existing Vue instance method. Avoid defining component methods that start with _ or $.

I have created a vue project using "vue init webpack" and installed the custom component.

main.js:

`import Vue from 'vue'
import vueCustomElement from 'vue-custom-element';
Vue.use(vueCustomElement);

Vue.customElement('widget-vue', {
props: [
'prop1',
],
template: '

{{ prop1 }}

'
});`

It seems that this happens with the newest version of vue 2.4.4

Binding objects to props in the early stage

Vue component a-custom-element

...
name: 'a-custom-element',
props: {
        earlyProp: {
            type: Array
        }
    },
created () {
   // need data in early prop for loading data.
    this.loadData(this.earlyProp);
}
...

The way the component is used:

<div id="container"></div>

<script>
            const aCustomElement = document.createElement("a-custom-element");
            aCustomElement.earlyProp= { token: 'a', infoNeededForApi: 'b' };
            document.getElementById('container').appendChild(aCustomElement);
</script>

The challenge here is that the earlyProp is not available in the created hook, if we assign object to earlyProp before attaching it into DOM.

How I can make this possible?

ShadowCss does not work on Edge

I've tried to use shadowDOM with shadowCss, worked fine on Chrome and Safari but not on Edge.

A work arround for this issue is do not use shadowDOM and define the style on .vue file with "scoped" flag.

This enforces the vue to mantain the style inside the component in all browsers. But this is absolutely not the same thing as shadowDOM

Async component fails in conjunction with [email protected]

    "vue": "^2.4.3",
    "vue-custom-element": "^1.3.0",
    "vue-loader": "^13.0.4",

Sorry in advance if this is an issue with vue-loader and not vue-custom-element but just to let you know: Using Webpack code-splitting with custom-elements works like a charm with [email protected] but ceased to work when upgrading vue-loader > 13.x.x

Example

import Vue from 'vue'
import VueCustomElement from 'vue-custom-element'
Vue.use(VueCustomElement)

Vue.customElement('some-widget', () => import('./components/SomeWidget.vue'))

Causing the following traceback:

[Vue warn]: Failed to mount component: template or render function not defined.

vueloader_vce

Yet it still works with [email protected]

Thanks a lot in advance & all the best,
Ralf

How to access vue-component methods from vue-custom-element

Issue:
I am unable to access vue-component methods from vue-custom-element although I can access and set props of vue-component using vue-custom-element from my app.js file.

Scenario:
(1) I am not using any build system.
(2) I am using precomplied minified vue.js and vue-custom-element.js into index.html as an external script.

Files:

  • app.js file
    1. created global vue-component using:
    var MyGlobalComponent = Vue.extend({...});
    2. registered vue-component:
    Vue.component('my-global-component', MyGlobalComponent);
    3. accessed vue-component into a variable MyElement:
    var MyElement = Vue.component('my-global-component');
    4. created vue-custom-element:
    Vue.customElement('my-element', MyElement.options, {...});
    5. var myelmt = document.querySelector("#myelmt");
    6. // using myelmt I would like to access methods of my Vue component.
  • index.html file
    <my-element myvar="abc" id = "myelmt"/>< my-element>

Error in IE11

seleccion_032

There is an error in the latest release when is executed on IE11 and earlier. Its working as expected in firefox, chrome and Opera( tested browsers in their latest release). I did use the optional polyfil on my project.

Error translation: "Object doesnt accept method or property 'call' "

Handling events in angular

First of all, great work here, Karol. I think this will help me in the migration of angular app into Vue!
I try to handle element events inside angular 1.5 component and I have some doubts about my way of doing that.
I'm referring here to event examples https://karol-f.github.io/vue-custom-element/#/demos/events

I cannot use @change="$ctrl.changeCallback", angular just doesn't see it that way.
The second way of handling it, by adding eventListener, is working but has two cons:

  • it has to be called in timeout in angular component so for some small amount of time, when element is visible, there is no handler for event
  • when the element is hidden, caused by the ng-if action, and showed up then obviously eventListener stops working too.

Do you have an idea how to do it properly?

How to use vue-router with vue-custom-element

Hello: First of all, you project is awesome! That being said, how do I use vue-router with vue-custom-element

Using regular vue I would do

new Vue({
  el: '#app',
  router,
  template: '<App/>',
  components: { App },
});

How does it work with

Vue.customElement('my-app', App);

Parent to nested elements has empty $children

I have a tabs component with two different components: app-tabs and app-tab. Tabs is parent and tab is children. In the mounted-method of app-tabs I want fetch the app-tab children so I can generate the tab links. The problem is that this.$children is an empty array. Even though the app-tab items are picked up by the parent and rendered.

See plunkr:
http://embed.plnkr.co/0Kx6eMYSzgcVgeUZfnI6/

Any idea what I'm doing wrong? I've also tried to have app-tab not as a custom element but as a regular Vue component but it makes no difference.

Add documentation how to use the custom element in other projects

@karol-f Hello,
maybe I am just dumb, but I didnt get the cutom element working in another project. There are questions wich are still unanswered:

  1. whats the best way to setup a custom element in a singe project?

vue init webpack

and then put the component inside the src folder or how?

  1. how to build the custom element that it is ready to use in other projects?

  2. after 2, if I installed the element with npm, how to import it or is the <my-element> - tag enough?

sorry if my question is at the wrong pace but I read the whole readme and locked at the demo page...

Slot updating issue

Hi again.
Sorry to disturb again but theirs another small issue that I'm currently working on and not able to correct myself by this time. Maybe you will be much more quicker to do so.

https://jsfiddle.net/pbfzw9fb/3/

I have a slot here in the child component that print the title of the parent one. When the parent title attribute is updated, the slots are not updated accordingly... Any way around that problem?

Thanks in advance again. Let me know if I can help with something. I will try again myself to figure out what happens here..

Cheers!

Properties added via Mixin are not updated

Hallo Karol,
i've come across the following issue when using vue-mixins with custom elmenents:

Steps to Reproduce:

  1. Create a component, register it as a custom element and add a prop via vue-mixin
    -> The default of the prop is displayed properly
  2. Change the prop by assigning a different value (as attribute or via js)
    -> The prop is not being updated

When taking a look in the debugger i discovered that the
__vue_custom_element__.$options.props
are only showing the component's own props

Bin

https://www.webpackbin.com/bins/-KovSNBlmGRuMtIy7zop

How to get single js file after build

In the documentation it says install vue-custom-element and then import the vue component like this:

import vueCustomElement from 'vue-custom-element'
Vue.use(vueCustomElement);

import Hello from './components/Hello.vue'
Vue.customElement('hello-world', Hello);

Then I've to use in index.html file right? But what js file should I include? From where can I get it? (building the project gives app.js, vendor.js, manifest.js etc)

Alternative compilation mode for vue-loader

@yyx990803

We will implement an alternative compilation mode for vue-loader that instead of relying on vue-style-loader, uses some runtime API provided by vue-element to insert a CSS string as a <style> tag into the custom element's Shadow DOM. This requires vue-element to provide a css option:

Vue.customElement('my-foo', ComponentOptions, {
  css: '...' // CSS string inserted as <style> into the shadow root
})

How to get started?

Thanks for the amazing plugin :) Looks very promising. I'm going to build a chat widget with this one. However, the documentation doesn't look good. I can't start

I started a new vuejs project using:
vue init webpack testing

then installed vue-custom-element using:
npm install vue-custom-element --save

imported the module in main.js using:

import vueCustomElement from 'vue-custom-element'
Vue.use(vueCustomElement);

Whats next? When I run "npm run build" I get the same files as before like, app.js, manifest.js, vendor.js etc..

  1. How I declare which components to use?
  2. If I used scoped styles in a component, will that work? Where will be the css files placed? Inside the js file?
  3. Can one component contain another component inside?
  4. What if I want to external libraries like Socket.IO, axios etc??

Vue versions interfering

It's my understanding that I should be able to run Vue v1.x globally on the page and use a custom element using Vue v2.x, since the custom element is sandboxed inside itself.

My main.js looks as such:

import Vue from 'vue'
import vueCustomElement from 'vue-custom-element/src/vue-custom-element'

Vue.use(vueCustomElement)

import App from './App'

Vue.customElement('test-app', App)

My index.html looks as such:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>vue-custom-element</title>
    <script src="https://unpkg.com/[email protected]/dist/vue.min.js"></script>
  </head>
  <body>
    <test-app></test-app>
    
    <!-- built files will be auto injected -->
  </body>
</html>

I'm simply loading in Vue v1.0.28 as a proof of concept, however I get a webpack error in the browser, after a successful compile, "customElement is not a function". After some digging and debugging, the instance of Vue that's available to the vue-custom-element install method is actually the Vue v1 instance.

Binding property to custom element attr

Here's my Vue component which I register as <custom-volume>.

{props: ['volume']}

Updating the custom element's volume attr manually syncs the property down. If I change the property inside the component, it does not reflect on the custom element like I see here https://karol-f.github.io/vue-custom-element/#/demos/binding

<custom-volume volume="50">

I'm not sure what I am missing. Here's a testcase: https://codepen.io/donleche/pen/qjKJdQ?editors=1010

And thank you very much for this plugin. It's really helpful!

Events

Is it possible to trigger an event on the Web Component, such that its listened to inside the Vue component?

//index.html
<my-component class="hello-world"></my-component>
<script>
var event = new CustomEvent('foo', { detail: 'bar' });
document.querySelector('.hello-world').dispatchEvent(event);
</script>
// my-component
<template>
     <div @v-on:foo="onFoo"><button>Click</button></div>
</template>

<script>
export default {
      methods: {
           onFoo (e) {
                  console.log(e)
           }
     }
}
</script>

Event-binding does not work, if custom elements are also used as vue-components

When using custom element both in html and inside vue-templates as vue-components, the event-binding does not work.

Steps to reproduce:

  1. Register a custom element firing events (emitter-el)
  2. Register a custom element (wrapper-el) using emitter-el as a vue-component and reacting to it's events
    -> wrapper-el reacts to events properly (with v-on)
  3. Declare the emitter-element outside the wrapper html
    -> wrapper-el does not react to events any more

Use Case:
It is absolutely necessary to be able to use custom-elements as vue-components in order to be able to work with props (pass functions into the component by declaration).

Bug Demonstration:
https://www.webpackbin.com/bins/-KmaNH_uvygSlOIY1p4e

EDIT: The demo should be accessible now

AngularJS 1.6.x integration

Hey, first of all congrats for your project, it's amazing.

I'd generated somes custom elements components and I'm trying to use it in my AngularJS application. The problem is that I don't know how to send my scope set vars to my custom-element.

Did someone had this kind of trouble?

Thanks.

Not able to listen for the events from the component inside "slot".

I am trying to reuse vue components in different frameworks through vue-custom-element. During the course I am trying to have a mark-up like below:

<v-actionbar>
     <v-actionlink v-bind:link-name='link1' v-on:link-click-action='captureLinkAction' is-link-visible='true'></v-actionlink>
 </v-actionbar>

v-actionbar component has a slot, which is expected to take 'v-actionlink'. A click action on v-actionlink emits a link-click-action.

The wired function 'captureLinkAction' on the event is not triggered when using with vue-custom-element. All is well when used with Vue only.

Plunker Link

Directives are visible vs. a div with a vue id

Greetings,

I noticed that a directive is stamped out with the output rendered within the directive.
Is this the expected behavior or is this a bug.

For example if I add which is supposed to return an tag.

I believe in vue it would be a

with a reference id for vue and the image would be contained inside the div. However when rendering as a custom element it simply adds with the div contained inside. I just want to know if this is the expected behavior since it is a custom element ?

Thank you,

Ryan.

CSS from Style tags

Hoping for the component CSS to be injected into the shadow DOM automatically. I speculate this is an issue with the vue-loader not attaching the style to the component object but there might be a better solution than this. Is this something in the works?

Disappearing slot content in nested elements

Here's an unusual bug that only seems to be happening in Firefox. Example here:

https://codepen.io/benjarwar/pen/xrrOzw?editors=1010

I've got a vce-collapsible component, which is a simple content toggle. These components can be nested in a vce-accordion component, which manages the state by ensuring only one of the nested collapsibles can be toggled at a given time. It does so by updating a curAccordionItem prop on each child vce-collapsible. If the curAccordionItem doesn't match its own id data, the vce-collapsible untoggles itself.

To see the issue: toggle one item, then wait a while (about 10-20 seconds seems to work consistently), then toggle another item. All of the content injected via the toggle and content slots disappears from all of the nested vce-collapsible instances.

Any ideas? Also happy to hear any refactoring suggestions if this architecture doesn't follow best practice.

PS: Thanks for this lib, it's excellent :)

Use with Vuex, mapGetters

I'm having issues getting custom element to all communicate to a central vuex store, and not seeing any documenting reflecting that this shouldn't work.

Using mapGetters on custom elements results in devtools showing "(error during evaluation)" for the

I have tested this in instances where only one custom element is displayed, or many of the same type are displayed and get the same issue.

Manually subscribing to the store on a created property for the vue instance works fine.

Component registration order matters with nested elements

This issue was discovered while developing a similar nested component architecture to the collapsible/accordion child/parent system I laid out in Issue #28. If I register the child custom element component before the parent, the slots do not render.

See an example in this fork of my collapsible/accordion example. The only difference is I've registered vce-collapsible before vce-accordion:

https://codepen.io/benjarwar/pen/NggLvm?editors=1010

Is this behavior expected?

Any workaround for `slot` attribute name?

I'm trying to migrate a native web component over to vue-custom-element. It's in use in production already, with components using the slot attribute in order to organize the layout.

Is there any workaround at the moment to allow vue-custom-element to replace these components without updating the slot attribute? Possibly rewrite the attributes using JS before the component loads?

Anyone have any experience with this? I can hack at it and try to get something working, but any pointers would be greatly appreciated.

Thanks!

Lazy loaded component -> shadow options not working

Vue.customElement('test-cart', () => new Promise((resolve) => { require(['./components/TestCart.vue'], resolve); }), { props: ['token', 'locale'], shadow: true, shadowCss:
.card {
background-color: blue;
}});

with the shadow option the component is not rendered.

work with Typescript/ angular 2?

I am trying to build a Vue component that can be used inside Angular 2.

Apparently, it cannot find module 'vue-custom-element' when I just simply import this.

Is there any way I can apply it with Typescript or Angular 2 framework?

Add module build

Mirrors what vue has already done with their 2.x builds:

This would be extremely useful for bundlers such as Rollup

Improve usage with vue-class-component

Thanks @karol-f for your work on this! I've done some exploratory work integrating Vue with an Angular 1 app and the initial results are very promising.

I've run into one snag while using TypeScript with vue-class-component which would be nice to avoid...

@Component({
  props: {
    prop1: String,
    prop2: String,
    prop3: Boolean,
  },
  template: `
    <p>{{ message }}, {{ prop1  }}, {{prop2}}, {{prop3}}</p>
  `
})
class WidgetVue extends Vue {
  protected message = 'Hello Vue!';
}

The syntax is great, but the resulting WidgetVue constructor unfortunately doesn't work with Vue.customElement. I've been able to get it working as shown below:

// Preferred, but does NOT work
Vue.customElement('widget-vue', WidgetVue);

// Ugly, but DOES work
Vue.customElement('widget-vue', new WidgetVue().$options);

I may try and put a PR together, feedback welcome! 🤔

Any examples of how Vue's slotting works inside other frameworks?

I'm just starting to play with this, it looks great! I'm running into a few issues trying to integrate it into my setup, and am looking for some feedback. I don't know if the issue is specifically to vue-custom-element or in Vue itself, but I'll start here; feel free to tell me to go elsewhere :)

My use case: I'd like to build components in Vue, wrap them in vue-custom-element, and use them inside my AngularJS 1.x app (and elsewhere). In particular, I'd like to build Vue components with slotted areas that I can project content that's "owned" by Angular.

So the setup is:

<angular-app>
  <vue-component>
    <another-angular-component></another-angular-component>
  </vue-component>
</angular-app>

Here's an example: http://plnkr.co/edit/FzvS3WQoZ3xg6IGmUpKZ?p=preview

In this case, <a-app> is an Angular component, whose template contains <v-groupbox>, a Vue component wrapped as a Custom Element. <v-groupbox> in turn has a slot, into which I'm projecting <a-nested>, another Angular component.

In Chrome this appears to work fine, but in Firefox, Edge, and IE11 the Angular bindings don't work when slotted inside a Vue component. For example, the buttons have an ng-click event that should call an Angular controller, but they never fire.

I thought it might be a polyfill issue, and have tried tried both document-register-element and @webcomponents/custom-elements; neither appears to work.

So! This is a roundabout way of requesting your help debugging what might be going on. We're researching ways of creating framework-independent components, but if we can't get this interop to function we'll have to drop Vue from our list. Happy to answer any questions regarding the AngularJS code.

Thanks!

Nested Custom Elements

Hi again,

Can we use vue custom elements nested? something like

  <custom-element>
    <another-custom-element></another-custom-element>
  </custom-element>

I've done something like the above example and inner element is initialized twice (by logging in it's mounted method).

Thanks.

Allow for nested dynamic elements

Hi,
thank you for creating this awesome plugin!
I have an urgent feature request and a suggestion for implementation.
In the docs regarding the usage of slots it says:

Dynamic content woun't work due to fact that when Custom Element register we replace it's HTML with Vue component template.

Would it be possible to detach the component's innerHTML from the DOM upon registering the component and then reattach it as a child?

As the nesting of dynamic components is crucial for complex apps, i think this would boost the use of vue-custom-element.

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.