Join GitHub today
GitHub is home to over 50 million developers working together to host and review code, manage projects, and build software together.Sign up
feat: Add native support for custom elements. #715
This requires Babel 6+ because it fixes extending HTMLElement, which is required for the tests to run. For that reason, I've based these changes off this dependant PR: #683.
It's desirable to be able to pass a custom element constructor as the node name of a virtual element, just as you can with functions and components.
While the ability to do this is aesthetic, it enables consumers of both Preact components and custom elements (written with any library) to treat them as the same thing, for all intents and purposes. Furthermore, it means that no matter who registers the custom element, the Preact-based consumer doesn't need to worry about what it was called, it simply uses the exported constructor.
For example, this is what you can do with this change:
import PreactComponent from 'preact-component'; import WebComponent from 'web-component'; export default ( <div> <PreactComponent> <WebComponent /> </PreactComponent> </div> );
As opposed to what you currently have to do:
import PreactComponent from 'preact-component'; // We're assuming it's already defined as "web-component". import 'web-component'; export default ( <div> <PreactComponent> <web-component /> </PreactComponent> </div> );
This was implemented at the diff level so that it could efficiently, and correctly, check based on constructor equivalence, as opposed to somehow determining the node name and reusing the existing algorithms. This is both more efficient than the aforementioned and spec compliant.
I wrote these as separate
I thought it was best to also test not only the node-to-node diffing and patching, but also how it behaved with keyed nodes and unkeyed nodes (plucking, as it's called in the source) because there are separate code paths for elements and descending into child lists.
Most tests seem to test integration between source files as opposed to units within them, gaining coverage implicitly. Many favour this approach, so I opted to follow that, convention or not. Happy to add specific unit tests if you feel it's necessary.
I've decided to only test against native versions of custom elements to reduce the amount of polyfills one must include, and amount of variables they could introduce. Since native implementations require the use of ES2015 classes, and the source / tests are transpiled to ES5, this requires the use of the ES5 adapter which I've had to include in this PR because Karma doesn't allow the dynamic inclusion of files from a CDN (as far as I could tell).
There were several minor refactors along the way to consolidate certain checks into functions, and to reuse a bit more code. I'll annotate the parts that I've done this to along with my rationale.
I ran the tests and noted the performance both before the changes, and after.
As you can see, the ones after the changes are actually an improvement. They're so close, this could be due to environmental variables, but nonetheless, it's a good thing!
You can now pass custom element constructors as the node name of a virtual element. This was implemented at the diff level so that it could efficiently, and correctly, check based on constructors as opposed to somehow determining the node name and reusing the existing algorithms. This is both more efficient than the aforementioned and spec compliant.
I need to spend some time with this one. There are things in here that could impact the non-WC use-cases and I'd like to write some tests / benches around those to be very sure.
Definitely a vote for the duck-type checking of HTMLElement - that's super important, we had to do the same thing for
Ok, so I've done the following:
If I tried moving the custom element check up where you suggested (where it checks if the
I just rebased and squashed the refactors so there's no breaking commits in between.
- No destructuring - No lowercase() function, use .toLowerCase() instead - Favour property access (no variable saving, unless necessary) - Use `'nodeName' in vnodeName.prototype` instead of HTMLElement instance checking