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

Initial commit of custom-elements-scoped proof-of-concept #383

Merged
merged 15 commits into from
Jan 26, 2021

Conversation

kevinpschaaf
Copy link
Collaborator

@kevinpschaaf kevinpschaaf commented Sep 23, 2020

Initial commit of scoped CustomElementRegistry proof-of-concept polyfill, to open up community collaboration on moving the polyfill forward (continuation of work done in this gist).

Big tasks needed to move this forward (in subsequent PR's):

  • Add test suite
  • Convert source to TS (to match convention in this monorepo)
  • Test and work out layering strategy with standard custom-elements and shady-dom polyfills
  • Consider adding benchmarks
    ===

Edit: remaining TODO's moved to issues:

  • #419: Convert source to TS (to match convention in this monorepo)
  • #420: Test and work out layering strategy with standard custom-elements polyfill
  • #421: Test and work out layering strategy with shady-dom & shady-css polyfills
  • #422: Add benchmarks

Copy link
Contributor

@aomarks aomarks left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really cool implementation!


Scoped CustomElementRegistry polyfill based on discussion in
https://github.com/w3c/webcomponents/issues/716 and spec proposal in
https://github.com/w3c/webcomponents/pull/865.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

// Once the registry is patched, this is easy to allow (previously upgraded
// elements won't be affected, but new elements will be upgraded with the
// new definition)
this._allowRedefinition = options && options.allowRedefinition;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

options?.allowRedefinition

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed, per below.

// Once the registry is patched, this is easy to allow (previously upgraded
// elements won't be affected, but new elements will be upgraded with the
// new definition)
this._allowRedefinition = options && options.allowRedefinition;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think I saw redefinition in Justin's proposal -- what's the reason to support it here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was a little gratuitous addition I made while experimenting; basically the implementation makes it easy to allow last-in re-definition. I should probably pull it out, good catch.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed.

packages/custom-elements-scoped/custom-elements-scoped.js Outdated Show resolved Hide resolved
// Helper to patch CE class setAttribute/getAttribute to implement
// attributeChangedCallback
const patchAttributes = (elementClass, observedAttributes, attributeChangedCallback) => {
if (observedAttributes.size && attributeChangedCallback) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit suggestion: could invert this condition and return, since the whole function body is inside the if.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

if (!isValidScope(scope)) {
scope = creationContext[creationContext.length-1].getRootNode();
if (!isValidScope(scope)) {
throw new Error('Element is being upgrade outside of a valid tree scope');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

upgraded

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

}
adoptedCallback() {
const definition = definitionForElement.get(this);
if (definition && definition.adoptedCallback) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

definition?.adoptedCallback?.apply

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

const awaiting = this._awaitingUpgrade.get(tagName);
if (awaiting) {
this._awaitingUpgrade.delete(tagName);
awaiting.forEach(element => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

regular for loop? (or is there some advantage to forEach here?)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

}
get(tagName) {
const definition = this._definitions.get(tagName);
return definition ? definition.elementClass : undefined;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

definition?.elementClass

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@@ -0,0 +1,32 @@
{
"name": "@webcomponents/custom-elements-scoped",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe scoped-custom-elements? (For some reason custom-elements-scoped to me kinda sounds like it's a variant of the full custom-elements polyfill that also supports scoped). But I don't have a strong opinion.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Yeah, I take your point. I'm not sure flipping it helps a ton with that particular confusion (i.e. do I need both custom-elements and scoped-custom-elements?). But at a minimum it reads less awkward that way.

Copy link

@LarsDenBakker LarsDenBakker Dec 1, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we have registry in the name?

  • @webcomponents/scoped-registry
  • @webcomponents/scoped-elements-registry
  • @webcomponents/scoped-custom-elements-registry

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to scoped-custom-elements-registry

@googlebot
Copy link
Collaborator

All (the pull request submitter and all commit authors) CLAs are signed, but one or more commits were authored or co-authored by someone other than the pull request submitter.

We need to confirm that all authors are ok with their commits being contributed to this project. Please have them confirm that by leaving a comment that contains only @googlebot I consent. in this pull request.

Note to project maintainer: There may be cases where the author cannot leave a comment, or the comment is not properly detected as consent. In those cases, you can manually confirm consent of the commit author(s), and set the cla label to yes (if enabled on your project).

ℹ️ Googlers: Go here for more info.

1 similar comment
@google-cla
Copy link

google-cla bot commented Dec 10, 2020

All (the pull request submitter and all commit authors) CLAs are signed, but one or more commits were authored or co-authored by someone other than the pull request submitter.

We need to confirm that all authors are ok with their commits being contributed to this project. Please have them confirm that by leaving a comment that contains only @googlebot I consent. in this pull request.

Note to project maintainer: There may be cases where the author cannot leave a comment, or the comment is not properly detected as consent. In those cases, you can manually confirm consent of the commit author(s), and set the cla label to yes (if enabled on your project).

ℹ️ Googlers: Go here for more info.

@kevinpschaaf
Copy link
Collaborator Author

@manolakis Sorry for the delay, thanks so much for the work on this! I got the tests passing now.

Could you add the "googlebot I consent" comment, and also review the changes I made in this commit: 825dc92, particularly the change I made to this test, which I think was a mistake in the test, but I may heave mis-understood the intention: 825dc92#diff-b6d5700c31b8bc7d7578717fa944702bb7441e04cfadeeb426fe04db42e45a1dR42

@kevinpschaaf
Copy link
Collaborator Author

kevinpschaaf commented Dec 10, 2020

The algorithm for determining the registry during upgrade is pretty shaky, and I think feedback from this is gonna need to be fed back into the spec proposal. Particularly this case:

const registry = new CustomElementRegistry();
registry.define('x-foo', class extends HTMLElement{});
const shadowRoot = document.body.attachShadow({mode: 'open', registry});
const div = shadowRoot.createElement('div');
div.innerHTML = `<x-foo></x-foo>`;

Without having stored state on the div noting its creation tree scope (which the proposal was trying to avoid), I don't see a good way for the innerHTML to know to use the shadow root's registry. I did in the polyfill (introducing a scopeForNode WeakMap) to get the test to pass, but I only store this for nodes directly created by createElement; if you had innerHTML'ed some DOM into that div, and then did a further innerHTML into one of those nodes, it would fail, since I'm trying to avoid needing to treewalk every node ever created to store this state. But it may be unavoidable. cc @justinfagnani

@manolakis
Copy link
Contributor

@googlebot I consent

@googlebot
Copy link
Collaborator

CLAs look good, thanks!

ℹ️ Googlers: Go here for more info.

1 similar comment
@google-cla
Copy link

google-cla bot commented Dec 11, 2020

CLAs look good, thanks!

ℹ️ Googlers: Go here for more info.

@manolakis
Copy link
Contributor

manolakis commented Dec 11, 2020

@kevinpschaaf I agree that the test is a bit confusing.

My intention was to check what should happen if there is a node in the light DOM which contains an upgraded WC defined in the global registry, and we want to import it into a shadow DOM with a custom registry. What should be the behaviour in this case? Should the instance of the component be of the class defined in the global registry or should be a non upgraded component because that tag name is not defined in the custom registry? I guess we could assume that importing nodes always tries to upgrade them using the current scope. Thoughts?

@leobalter
Copy link

@kevinpschaaf I'd like to support with some traction here. Are the OP checkboxes up to date? Is there anything specific to take a look?

@kevinpschaaf
Copy link
Collaborator Author

@leobalter Sorry, haven't been able to focus on this lately. I think this PR is basically ready to land, save for figuring out what we want to do for the test I raised with @manolakis, which I hadn't had a chance to circle back yet.

And then yeah, I think the next things would be from that checklist. Switching the code to TS is more of a chore sort of thing, not necessarily critical path but would be nice. Working out polyfill compatibility is probably the more important chunk of work, assuming that's important to your use cases. I expect custom-elements to be fairly straightforward, but ShadyDOM (ShadyCSS) is going to be pretty rough, since right now it scopes element CSS by tag name; the ability to have multiple tag names scoped differently would require adding an additional scoping class/attribute per registry. So I guess a question is... how important is the legacy polyfill story to your use of the scoped registry polyfill, and are you interested in looking at those integrations?

@kevinpschaaf
Copy link
Collaborator Author

@manolakis Re: the test we were discussing, if we're talking about importing a node, my expectation is that should be purely a function of the node's tagName/attributes and the new context it is being imported into (i.e. whether or not it had a custom element identity before it was imported would be immaterial)... So I could change the test back but make the expectation to be an un-upgrade HTMLElement? Or else we just leave the test as-is and it just tests a different behavior? Wdyt?

@manolakis
Copy link
Contributor

@kevinpschaaf Yes, you are right. I tried to clarify the test intention in this PR against this branch #412. All test green 👍

test: make test intention clearer
@kevinpschaaf
Copy link
Collaborator Author

@manolakis Right! Sorry, that LGTM, I merged that in. And I'll go ahead and merge this into master, and we can take new PR's from there.

@kevinpschaaf
Copy link
Collaborator Author

@leobalter For sake of merging this PR, I moved the outstanding TODO's to issues and noted them in the README:

  • #419: Convert source to TS (to match convention in this monorepo)
  • #420: Test and work out layering strategy with standard custom-elements polyfill
  • #421: Test and work out layering strategy with shady-dom & shady-css polyfills
  • #422: Add benchmarks

@kevinpschaaf kevinpschaaf merged commit bed5d99 into master Jan 26, 2021
@kevinpschaaf kevinpschaaf deleted the custom-elements-scoped branch January 26, 2021 01:10
@kevinpschaaf kevinpschaaf restored the custom-elements-scoped branch February 12, 2021 03:31
@kevinpschaaf kevinpschaaf deleted the custom-elements-scoped branch February 12, 2021 03:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants