Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

slots not correctly injected in Chrome and Opera #166

Open
santo74 opened this issue May 20, 2019 · 20 comments
Open

slots not correctly injected in Chrome and Opera #166

santo74 opened this issue May 20, 2019 · 20 comments
Assignees

Comments

@santo74
Copy link

santo74 commented May 20, 2019

I created a Vue component "simple-form" for building dynamic forms. It's using vue-custom-element in order to be able to inject it in my websites.
To be able to specify a custom header, footer and other stuff for each form, I'm defining several slots in the components template.

Basically, the template looks something like this:

<div>
	<slot name="header"></slot>
	<h1>title</h1>
	<slot name="intro"></slot>
	<form>
		<div id="startmsg">
		<slot name="startmsg"></slot>
		</div>

		<!-- Dynamic form content goes here -->

		<slot name="endmsg"></slot>
	</form>
	<slot name="footer"></slot>
</div>

Everything works fine, except that in Chrome and Opera the slots are not correctly injected.

This is how I use the component in my website:

<simple-form config="my-config">
    <template slot id="header"><div>form header</div></template>
    <template slot id="intro"><div>form intro</div></template>
    <template slot id="startmsg"><div>This is a start message</div></template>
    <span vue-slot="startmsg"><div>This is a start message</div></span>
    <template slot id="endmsg"><div>This is an end message</div></template>
    <span vue-slot="endmsg"><div>This is an end message</div></span>
    <template slot id="footer"><div>form footer</div></template>
</simple-form>

For debugging purposes I'm using the startmsg and endmsg slots in 2 ways: as a template tag (1) and as a named slot (2)

(1) <template slot id="startmsg"><div>This is a start message</div></template>
(2) <span vue-slot="startmsg"><div>This is a start message</div></span>

In the attached screenshot you can see that the startmsg is not injected inside the div tag with id="startmsg".
Instead, all slots are injected at the end of the component, right before the closing tag

Moreover, when using a template tag (1) the slot content is inserted inside a "#document-fragment", which is not visible in the browser.

Any ideas what's happening here?

opera_slots_console

@karol-f
Copy link
Owner

karol-f commented May 20, 2019

Hi, can You prepare example on e.g. https://codesandbox.io/s/jv4nvmp74v or GitHub repo?

Also are you sure that you are using vue-slot and not slot anywhere? Please do not use slot.

@santo74
Copy link
Author

santo74 commented May 20, 2019

Hi karol-f,
I created the smallest possible example and modified the code in https://codesandbox.io/s/vue-template-lni9w accordingly.
However, as you will see the example will run fine in codesandbox.

The issues with Chrome and Opera that I'm referring to only occur when I build the component (npm run build) and include the resulting js file (dist/HelloWorld.umd.min.js) in another website.
(Obviously I'm also including vue.js and document-register-element in that website).
Just like this:

<!DOCTYPE html>
<html>
  <head>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/document-register-element/1.13.2/document-register-element.js"></script>
    <script src="/assets/scripts/hello-world.js"></script>
  </head>
  <body>
    <hello-world msg="hello there"}}">
        <template slot id="header"><h3>Header</h3></template>
        <template slot id="footer"><h3>Footer</h3></template>
    </hello-world>
  </body>
</html>

=> This works fine in Firefox and Edge, but not in Chrome and Opera

@santo74
Copy link
Author

santo74 commented May 20, 2019

Just FYI: it did work in Chrome and Opera until recently.

@karol-f
Copy link
Owner

karol-f commented May 20, 2019

Can You confirm that You are not double loading Vue library?

@santo74
Copy link
Author

santo74 commented May 20, 2019

Yes, I can confirm that.
I'm only loading the Vue library in the main html file, nowhere else.
If I comment that line, I get the error "cannot read property 'use' of undefined, indicating that Vue is not loaded

@karol-f
Copy link
Owner

karol-f commented May 20, 2019

From a quick look there is invalid HTML in example above: <hello-world msg="hello there"}}">

If it's not causing it, can You please prepare GitHub repo with NOT working example and I will take a look at it?

@santo74
Copy link
Author

santo74 commented May 20, 2019

I'm sorry, the "}} shouldn't be there, that's probably a copy/paste issue.
I can confirm it's not in my code, so that's not the issue.

I'm not sure how I can prepare a GitHub repo. All there is to reproduce the issue is building the hello-world component as it is currently defined in the codesandbox link https://codesandbox.io/s/vue-template-lni9w and then include it in the following html page:

<!DOCTYPE html>
<html>
  <head>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/document-register-element/1.13.2/document-register-element.js"></script>
    <script src="/assets/scripts/hello-world.js"></script>
  </head>
  <body>
    <hello-world msg="hello there">
        <template slot id="header"><h3>Header</h3></template>
        <template slot id="footer"><h3>Footer</h3></template>
    </hello-world>
  </body>
</html>

@santo74
Copy link
Author

santo74 commented May 22, 2019

@karol-f were you able to reproduce it with the above instructions?

@karol-f
Copy link
Owner

karol-f commented May 23, 2019

Sorry, not yet, I still have it on my list.

@santo74
Copy link
Author

santo74 commented May 23, 2019

Ok, no worries. Let me know if you need more information.

@santo74
Copy link
Author

santo74 commented Jun 18, 2019

@karol-f I just noticed the status of this issue is still "waiting for feedback". Maybe you can change this to "investigating" or something like that?

@jstiers
Copy link

jstiers commented Mar 26, 2020

@karol-f Hey Karol, thank you so much for your work! Any update on the slot issue?

@aki77
Copy link

aki77 commented Mar 28, 2020

I have taken the following workarounds.

// before
Vue.customElement('slot-component', slotComponent);

// after
import { getProps } from 'vue-custom-element/src/utils/props'

const asyncComponentDefinition = (component) => () => new Promise((resolve) => {
  setTimeout(() => {
    resolve(component)
  }, 0)
})

Vue.customElement('slot-component', asyncComponentDefinition(slotComponent), {
  props: getProps(slotComponent).camelCase
});

@jstiers
Copy link

jstiers commented Mar 28, 2020

I have taken the following workarounds.

// before
Vue.customElement('slot-component', slotComponent);

// after
import { getProps } from 'vue-custom-element/src/utils/props'

const asyncComponentDefinition = (component) => () => new Promise((resolve) => {
  setTimeout(() => {
    resolve(component)
  }, 0)
})

Vue.customElement('slot-component', asyncComponentDefinition(slotComponent), {
  props: getProps(slotComponent).camelCase
});

Wow, awesome to see this community active!
Thank you @aki77 , I'll try that coming week.

@allenhwkim
Copy link

allenhwkim commented Sep 9, 2020

@aki77 you are awesome!!
@karol-f FYI, I have tried this approach and it's working for me. The following is what I have done successfully

HelloWorld.vue this will go into <slot></slot>

<template>
  <div>
    <h1>{{ msg }}</h1>
  </div>
</template>

<script>
export default {
  name: "HelloWorld",
  props: { 
    msg: String 
  }
};
</script>

XButton.vue. this has <slot></slot>

<template>
  <div class="host">
    <slot></slot>
    <span> {{title}} {{ count }} {{times}}. </span>
  </div>
</template>

<script>
export default {
  name: 'XButton',
  props: ['title'], // passing data through props
  data: function() {
    return { // data must be a function
      count: 0
    }
  },
  computed: { // getters
    times: function() { 
      return 'times'
    }
  }
};
</script>

<style scoped>
.host {
  display: inline-block;
  border: 1px solid #ccc;
  box-shadow: 4px 4px 8px #ccc;
}
</style>

custom-element-build.js. this uses @Kai77 method

import Vue from "vue";
import vueCustomElement from 'vue-custom-element';
import { getProps } from 'vue-custom-element/src/utils/props'
import HelloWorld from '../components/HelloWorld';
import XButton from '../components/XButton';

Vue.use(vueCustomElement);

const asyncComponentDefinition = (component) => () => new Promise((resolve) => {
  setTimeout(() => {
    resolve(component)
  }, 0)
})


Vue.customElement('hello-world', HelloWorld);
// Vue.customElement('x-button', XButton);

Vue.customElement('x-button', asyncComponentDefinition(XButton), {
  props: getProps(XButton).camelCase
});

command

$ vue-cli-service build --target lib --inline-vue --name elementsX custom-element-build.js

html

<body>
  <x-button>
    <hello-world msg="Hello Custom Element"></hello-world>
  </x-button>
</body>

Successful Result withasyncComponentDefinition
image

Erroneous Result without asyncComponentDefinition
image

@allenhwkim
Copy link

@aki77 can you explain how your code work?

@aki77
Copy link

aki77 commented Sep 9, 2020

In Chrome, the child elements does not seem to be present yet at the time of the connectedCallback event.
This is why we're working around it by executing a delayed execution.
One thing to keep in mind is that the original element will be drawn for a moment before the Vue component is drawn, so you need to be careful.

@allenhwkim
Copy link

Thanks @aki77
@karol-f Just as a wish, it would be wonderful there is async feature like this, https://cli.vuejs.org/guide/build-targets.html#async-web-component

@karol-f
Copy link
Owner

karol-f commented Sep 9, 2020

Thanks @aki77
@karol-f Just as a wish, it would be wonderful there is async feature like this, https://cli.vuejs.org/guide/build-targets.html#async-web-component

Like this? https://karol-f.github.io/vue-custom-element/#/demos/lazy-loading

@petomi
Copy link

petomi commented Jan 14, 2021

Is there a solution for the slots issue on Chrome that doesn't involve delaying component styles loading?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants