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

Not able to pass object as attr with stencil.js generated web component #2343

Closed
robaxelsen opened this issue Oct 9, 2020 · 12 comments · Fixed by #5328
Closed

Not able to pass object as attr with stencil.js generated web component #2343

robaxelsen opened this issue Oct 9, 2020 · 12 comments · Fixed by #5328
Labels
✨ feature request New feature or request

Comments

@robaxelsen
Copy link

Version

3.0.0

Reproduction link

https://github.com/robaxelsen/stencil-vue-3-repro

Steps to reproduce

  1. Go to http://rob.ee/dev/stencil-vue-3-repro/ to see reproducer code live
  2. See that first line which should render the object stringified, returns "[object Object],[object Object]" instead of stringified object

What is expected?

Should return stringified object [{"id":"id1","label":"Option 1"},{"id":"id2","label":"Option 2"}] instead of "[object Object],[object Object]". The line below is a "hand rolled" web component that does the same successfully. Difference is that the first one is made with Stencil.js.

What is actually happening?

Web component renders "[object Object],[object Object]"instead of the stringified object[{"id":"id1","label":"Option 1"},{"id":"id2","label":"Option 2"}]`.


This example works if we instead use Vue 2 with object.prop that since have been deprecated. Does not work with Vue 3.

Source code for the live reproducer is here: https://github.com/robaxelsen/stencil-vue-3-repro

Most relevant are these files/folders:

  1. Index.html where web component is used with Vue 3: https://github.com/robaxelsen/stencil-vue-3-repro/blob/main/www/index.html
  2. Stencil.js compiled web component js files: https://github.com/robaxelsen/stencil-vue-3-repro/tree/main/www/build

We have not been able to isolate whether this is a Stencil.js or Vue 3 issue, but seems it works with Vue 2 I am posting here first. Will also post issue in Stencil.js repo next, and link to this.

@LinusBorg
Copy link
Member

LinusBorg commented Oct 9, 2020

the issue is likely rooted in the fact that, similar to React, in our new virtual DOM design, we don't differentiate between, (Vue-)props, attributes and domProps (or events) - all are just propertiess on a
flat vnode object.

We handle this for normal DOM Element props/attributes internally during the patch phase, but it seems to fail here for custom elements that rely on objects being passed via properties rather than as attributes (which are always string values).

Interested to understand how React et. al. void this.

Edit: re-reading your OP got me doubting my understanding, likely because of a lack of familiarity with stencil.

You say you expect a stringified object to be passed. Does stencil not use some props to pass objects without the need of being stringified?

@robaxelsen
Copy link
Author

robaxelsen commented Oct 9, 2020

@LinusBorg Thanks for having a look and providing insights!

Interested to understand how React et. al. void this.

They don't handle it, I am afraid. From https://custom-elements-everywhere.com/: "React passes all data to Custom Elements in the form of HTML attributes. For primitive data this is fine, but the system breaks down when passing rich data, like objects or arrays. In these instances you end up with stringified values like some-attr="[object Object]" which can't actually be used."

Edit: re-reading your OP got me doubting my understanding, likely because of a lack of familiarity with stencil. You say you expect a stringified object to be passed. Does stencil not use some props to pass objects without the need of being stringified?

No, that's my mistake. You understood me correctly initially, I think. In my limited understanding of Stencil.js internals, I believe they do not accept stringified objects, but require the framework or vanilla JS implementer to pass it as a prop instead for objects and arrays. From https://custom-elements-everywhere.com/: "By default, Vue passes all data to Custom Elements as attributes. However, Vue also provides syntax to instruct its bindings to use properties instead. To bind to a Custom Element property use :foo.prop="bar"."

In the past (Vue 2) we were able to rely on .prop as our savior for these cases. Would a reintroduction of .prop be feasible, or does it have performance or maintainability costs that want to be avoided?

EDIT: From Stencil.js' side, there exists output targets that wrap all web components with framework specific bindings. Angular and React exists, but Vue.js hasn't been implemented yet: https://github.com/ionic-team/stencil-ds-output-targets

This wasn't an issue for Stencil.js with Vue 2 though, as .prop works well without Vue specific output target and just plain Stencil.js web components.

@LinusBorg LinusBorg added the ✨ feature request New feature or request label Oct 9, 2020
@graynorton
Copy link

It seems significant that the "vanilla" custom element doesn't exhibit the same behavior–this would seem to point to a problem in Stencil.

I confirmed that a vanilla element seems to work fine here: https://stackblitz.com/edit/vue-rxb2du?file=src%2FApp.vue

Notably, it wasn't even necessary to use .prop in the binding:

<template>
  <div id="app">
    <img alt="Vue logo" src="https://vuejs.org/images/logo.png">
    <data-element :data="data"></data-element>
    <HelloWorld msg="Welcome to Your Vue.js App"/>
  </div>
</template>

@robaxelsen
Copy link
Author

Thanks @graynorton. Yeah, hoping for an answer from the Stencil.js team too.

But also curious as to if this could have to do with state management patterns inside more complex web components, or some similar best practices across web components frameworks, due to custom-elements-everywhere.com and their mention of Vue web component support depending on .prop for handling these type of cases (see quote and reference above).

@graynorton
Copy link

@robaxelsen It doesn't look like the tests for custom-elements-everywhere have been updated for Vue 3.0 yet, so I'd guess that the descriptive text hasn't been either.

I haven't looked at the Vue source and would be curious what @LinusBorg has to say, but it appears to me that Vue is no longer passing data to custom elements using attributes by default. What's unclear without digging in is whether it's always passing data using properties (which is probably not great), or whether it's doing something fancy based on either the type of data being passed or the existence of a like-named property on the target element.

@graynorton
Copy link

I was curious, so played around a bit more.

It appears that when binding into a CE, Vue 3.0 will bind to a property if the CE has a property descriptor or a setter for the corresponding name; otherwise, it will bind to an attribute. This seems like a good behavior; it essentially means that Vue will prefer binding to a CE property and fall back to an attribute as needed.

@robaxelsen, based on your broken example, it looks to me like Vue's logic to check for the existence of a property is failing for Stencil-created CEs (for whatever reason) and Vue is therefore binding to the attribute instead. Possibly this could be addressed by either Vue or Stencil, but someone will have to dig in and figure out specifically why Vue 3.0 isn't recognizing properties on Stencil CEs.

@KaelWD
Copy link
Contributor

KaelWD commented Oct 10, 2020

Vue just uses key in el to determine if it should bind to a property: https://github.com/vuejs/vue-next/blob/51c18ed193285b593adbf909bf323a645de246e7/packages/runtime-dom/src/patchProp.ts#L112

Stencil doesn't define the data property until after vue has already checked if it exists: https://github.com/ionic-team/stencil/blob/f6437b384f0c65c3bc9edaa06dfc36deb998aefb/src/runtime/proxy-component.ts#L20
If you place a breakpoint here you'll see that the stencil component is in the DOM but hasn't initialised itself yet so doesn't display any content.

@johnjenkins
Copy link

johnjenkins commented Oct 12, 2020

Stencil manages the loading of components and by default it does so lazily.
@KaelWD comment makes sense. I suspect if you were to change to a custom element with no lazy-loading it'd work fine (https://stenciljs.com/docs/custom-elements)

@bjankord
Copy link

bjankord commented Mar 2, 2021

Stencil has released a new output target dist-custom-elements in version 2.4.0 that allows loading of the custom element without stencil lazy loading them. I've found that by using these imports with the .prop modifier in Vue that it appears to work with Vue 3 in regards to passing objects or arrays.

@flozero
Copy link

flozero commented Jan 24, 2022

the .prop doesnt send the data on update...

EDIT: it work if I am spreading the object inside { ...myObj } that sounds like a non normal behavior in vue3 at least with reactive

@yyx990803
Copy link
Member

Closing as 3.2 has re-introduced the .prop modifier.

@github-actions github-actions bot locked and limited conversation to collaborators Sep 18, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
✨ feature request New feature or request
Projects
None yet
8 participants