-
Notifications
You must be signed in to change notification settings - Fork 31
Support for attributes, type-extension CEs, and those loaded by HTML import #60
Conversation
… constructor or a string/tag-name)
This is awesome @rnicholus, thank you! I've had limited availability, so hopefully I'll get to reviewing this soon. Sorry for the delay :( |
No rush. I know there's a lot to digest here. Let me know if I can assist in any other way. Aside: While this library with my changes + the Webpack loader addresses most of the issues I had while trying to integrate web components into a React app, one problem I haven't yet solved is how to get HTML imports and ES6 imports to play nicely. How can we de-dupe across both of these mechanisms? I've been giving that some thought, but have not come up with a good answer yet. In the meantime, I believe this is a step in the right direction. |
Re the aside, you might be able to solve it with a Webpack plugin that checks hashed content. Not sure, though, I haven't given it much thought either. |
My fear is that the two just won't be able to work together. It really has to be one or the other in terms of de-duping. I could update my Webpack plug-in to rip out all the At this point, I'm not worrying too much about loading duplicate resources. In many cases, this probably won't be a big deal, until a project mixes a great number of HTML-imported WCs and ES6 imported code/WCs. |
README.md
Outdated
If the underlying web component you intend to Reactify requires some properties to be set directly on the element as attributes, include an `attr-` prefix on the property name. For example: | ||
|
||
```jsx | ||
<MyComponent attr-data-fo='bar' /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if it used something like attributes={{ foo: 'bar' }}
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like a reasonable alternative.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've also been playing around with a similar pattern in a separate lib but added support for more special props https://github.com/skatejs/dom-diff#attributes.
README.md
Outdated
|
||
```jsx | ||
render() { | ||
const Form = reactify('form') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My first thought would be to do reactify('ajax-form')
here instead, and not have to specify is
on the component.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will that work? That will result in the library rendering an element named ajax-form, but this isn't a valid tag name as far as I can tell. It's a form element.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I commented in the particular parts of the code that could handle this. Essentially, you'd try and look up the component with customElements.get(name)
and then construct it using the constructor. Underneath the hood you could tell React to use the real tagName
and set the is
attribute.
I'm unsure how this will work, though. The v1 CE polyfill doesn't support customised built-ins, I don't think. I also don't think React will call createElement()
with the correct arguments in order for them to work, even if it did (or the browser natively supported them).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I commented in the particular parts of the code that could handle this.
I'll take some time to look over those comments ASAP.
Essentially, you'd try and look up the component with customElements.get(name) and then construct it using the constructor
Most of this PR is driven to better support CEs loaded using HTML imports. During development, I spent quite a bit of time integrating my own CEs as well as a few other CEs that are loaded via HTML import (some of these are mentioned the docs I contributed). When HTML imports are involved, there is no guarantee that the CE constructor will be available by the time reactify
is called. In that case, it seemed safer to rely on the tag name and allow the rendered element to be upgraded at some time after the element specification was loaded by the HTML import. This timing issue was what drove most of the changes in this PR.
I also don't think React will call createElement() with the correct arguments in order for them to work
My testing seemed to suggest that, as long as the is
property is included on the native element as part of its first render, the customized built-in works as expected. But if is
is added later, the element is never upgraded. That is why I included the change at https://github.com/webcomponents/react-integration/pull/60/files#diff-1fdf421c05c1140f6d71444ea2b27638R81, to ensure a customized built-in element is rendered correctly.
The v1 CE polyfill doesn't support customised built-ins, I don't think
That very well may be true. The v1 CE polyfill is fairly new to me. I have only recently started using it in new WC projects. But if this is missing, it seems this support will need to be added, since the is
property used to mark an extended native element is still part of the spec as far as I know. Granted, all of my testing was with v0 polyfills.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 for automatically adding the is=""
attribute without each instance having to put it on there. The v1 CE polyfill may soon have support for customized built-in, pending webcomponents/custom-elements#42.
Once skatejs-react-integration has support for customized builtins (when this pr is merged), myself and the company I work for are interested in using this. Love the work you're doing here!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't see a test for testing customised built-ins, but I'm not sure we could do that since I don't think the polyfill supports it yet.
src/index.js
Outdated
|
||
let CustomElementConstructor = CustomElementOrTagName | ||
if (typeof CustomElementOrTagName === 'string') { | ||
CustomElementConstructor = document.createElement(CustomElementOrTagName).constructor |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could use customElements.get(CustomElementOrTagName)
. This would have the added benefit of solving https://github.com/webcomponents/react-integration/pull/60/files#r95096012, I think.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will return undefined
if the element hasn't been imported yet.
src/index.js
Outdated
} | ||
|
||
const tagName = typeof CustomElementOrTagName === 'string' | ||
? CustomElementOrTagName |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You wouldn't need this part if looking up via customElemenst.get()
but you would need to use the is
attribute if it exists and fallback to tagName
if not.
src/index.js
Outdated
} | ||
} | ||
|
||
const proto = CustomElement.prototype; | ||
const proto = typeof CustomElementOrTagName === 'string' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since there's already CustomElementConstructor
above, could you do CustomElementConstructor.prototype
?
Hey @rnicholus sorry it took so long for me to get around to reviewing this. |
No worries, nothing pressing here. It may be a day or so before I can get around to responding to your comments anyway, as I'm working through some other side project commitments over the next few weeks. |
PR to implement customised built-ins in the CE pfill: webcomponents/custom-elements#42. |
Speak of the devil. Looks like that was created w/in the last hour. |
Someone else was working on converting this over to v1. Maybe we should help finish that off then polish this up. Depends on #59 which is being done as part of $58 in https://github.com/kentendo/react-integration. |
Would love to help out. Is there a timeline for that to be completed? off-topic: Congrats on Trello. We use JIRA + Confluence at my company. Will be interesting to see how putting all of those under one roof benefits users. |
@rnicholus re timeline, not sure if open source has one :). Availability + ASAP haha. It'd be great if you wanted to help out. Cheers! |
Hey @rnicholus I just wanted to give this a bump to see where it's at. Totes understand that you've probably been really busy. Very excited for this to go in! |
Thanks for the reminder. It fell off my radar, but I'll add it to my task list so that does not happen again. I'll start by rereading your comments in the next day or so and take it from there |
@treshugart I +1'd a few of your suggestions. Please let me know if there are others that you feel strongly about. Otherwise, I'll begin updating my PR with those changes post-haste. One thing: my thoughts on custom elements have evolved a bit since I created this PR. I see the v1 spec as the future (and of course it is), and am more willing to leave behind html imports as a result. It seems possible that the HTML imports spec will be deprecated at some point, unless Firefox, Safari, and Edge choose to implement this part of the WC spec. It looks like Safari and Firefox will never implement though. So, I'm not sure if this PR will do anything more than clutter up your library. ...but maybe the |
@rnicholus cool, it's kinda hard for me to tell which ones weren't thumbs up'd be fore, but looks good. I can't tell from the thumbs up if you're on board with auto-applying the |
Re: using |
There's an API in the polyfill to detect when all imports have loaded and the consumer could reactify after that. That said, I don't think it's a good idea to support very contentious standards that aren't yet finalised. As long as the consumer has the control over when they call |
Makes sense. Do you think it is still important to allow users to pass tag names, instead of CE constructor functions. |
Maybe not? I don't have strong feelings about that other than it's easier to add than it is to subtract. Meaning, since the spec hasn't stabilised around HTML Imports, it might be better to wait for a bit before adding that functionality. If this is going to block you from using it, then I think it's valuable to have that API if it's necessary. I'll let you make the judgement call here :) |
It may, without HTML import support, but I've realized that I need to embrace v1 CEs, and have already started re-writing one of my CEs to account for this. One thing I don't know the answer to yet: What tag name will be used to render the CE by react-integration if something like |
If we left it only as a constructor, you can use the |
Ok, so I plan to do the following (let me know if I've missed anything, or included something redundant):
|
I do think it's important to solve your use-cases, so if we find that we do eventually require using strings, then I'm happy to discuss it at that point. I created #65 to add an One last bikeshed, what do you think about using Happy with what you've proposed in your last comment. Thank you!! :) |
fine by me - just updated my checklist |
At the moment, I'm blocked on the "automatically add the First, constructing a CE that extends another element results in a class XFoo extends HTMLSpanElement {
constructor() {
super()
}
}
customElements.define('x-foo', XFoo, {extends: 'span'})
// Uncaught TypeError: Illegal constructor
new XFoo() Also, I'm not certain how to determine both the tagName ('SPAN') and the |
After further research, even though |
Which part exactly? It seems that both have to do with customised built-ins. If the advice is to hold off, then wouldn't it be worth holding off on that one, too? Maybe I misunderstood. |
I plan to hold off on the auto-"is" feature until the polyfill matches the spec. But even after the polyfill is updated, a question regarding how this can be completed remains. That question/issue, as posed earlier, is: I'm not certain how to determine both the tagName ('SPAN') and the is value to apply to the element ('x-foo') given only the passed constructor. Maybe I'm missing something, but it doesn't seem like we can determine the proper value to set for is with nothing more than a constructor function. |
Well, we currently construct the element to get information about it. Polyfill / native will automatically apply the Alternatively, a better solution might be to override That should work, no? |
That said, we probably don't need to do that until the polyfill supports |
Ping :). If this is gtg now, happy to merge. |
Sorry for the delay. I don't think this is ready to merge. I was unable to complete/verify support for built-in support due to the lack of native support for built-ins in any browser and the lack of support in the polyfill. Perhaps that has changed recently or there is a workaround, but I'm unable to look into this further now. I'm hoping to get some time this month to take another look. |
@rnicholus okay, no worries. Just following up. Let me know when you need me to take another look :) |
Closing because stale. LMK if there's ever any intention to update, and thank you so much for the original PR. |
...I also added an
"engines"
property topackage.json
to reflect the earliest version of Node.js required to run the unit test suite, which appears to be 6+. No changes I made here influenced the required version of Node to run the tests, this was a pre-existing requirement. The"engines"
property provides documentation of this requirement if nothing else.Note: There are no breaking changes in this PR, only additions. The updated version number reflects this.
Background
I am personally a fan of HTML imports. While I understand this portion of the Web Components specification is controversial in some circles, I love the way this portion of the spec provides for standardized self-contained modularization of HTML, JS, and CSS for web components. I can easily define the HTML, CSS, and JS for my Web Component in appropriate files and tie them all together in a way that is transparent to the user, which simply imports the root HTML file using a single HTML import. While this certainly will increase the number of HTTP requests, HTTP/2 (supported in all modern browsers) mitigates this with connection multiplexing.
I tend to use HTML imports often when I write my own web components. A couple OSS WCs I wrote that utilize HTML imports include ajax-form and file-input. But this is not an uncommon pattern, so I quickly ran into trouble when attempting to easily integrate WCs into my React-based projects. For the most part, webcomponents/react-integration makes this possible for WCs where the constructor function is easily accessible, but more work was required to properly Reactify WCs that relied on HTML imports. My work to support these types of WCs includes the commits in this pull-request as well as a Webpack loader I wrote that provides easy access to the "root" URL of an HTML-importable WC.
Changes in this PR
My pull request provides support for the following:
The remainder of this PR description goes into more detail regarding the above three items.
Associating attributes with custom elements
In a perfect world, all element properties would be automatically reflected as attributes where appropriate. But there are a number of custom elements in existence that do not properly update attributes when property changes occur. I have come across many of these myself, which is what prompted some of the changes in this pull request. Admittedly, I'm also the author of a few such CEs. While the long-term goal is to "fix" these CEs such that they sync property changes to attributes whenever prudent, it is desirable to allow direct access to attributes at least until these CEs can be updated (if possible).
My pull request looks for properties that start with
attr-
, and passes the associated value directly to the CE as an attribute. For this type of property, object/array values are also stringified before setting the element attribute. There are corresponding unit tests to verify this new behavior.Reactifying a custom element given only its tag name
This is especially important for web components that are loaded via HTML import. In those instances, the constructor can be accessed, but the process may not be intuitive. Most importantly, the CE may not be fully "created" at the time the React component wrapper is rendered. In this case, it is far easier to simply pass the tag-name. This cuts out the code that uses the CE constructor to determine the tag-name, which itself expects the CE to be fully created at that point.
You can see the changes I made to the index file along with a number of new unit tests. I also took care to update the Typescript definitions file to reflect the fact that either a CE constructor function or a tag-name (string) may be passed as the first parameter to the exported function.
Reactifying all type-extension custom elements
This one was pretty simple to address. If a property named
is
is passed to the reactified CE, ensure this value is set on the first render of the element. Once the element has been added to the DOM, specifying theis
property doesn't seem to have any affect. This is not a React-specific issue, though rendering the CE initially without theis
property produces the same problem.I've also updated the
README.md
file to include documentation for all newly supported features.