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

Implement component bounds feature #19

Merged
merged 1 commit into from Sep 28, 2017
Merged

Implement component bounds feature #19

merged 1 commit into from Sep 28, 2017

Conversation

tomdale
Copy link
Contributor

@tomdale tomdale commented Sep 28, 2017

With the recent Glimmer VM changes to adopt IndustryStandardCase, Glimmer.js components now relax the requirement of a template having a single root element. In addition to having multiple elements, this also means that a template may have top-level nodes such as CommentNodes or TextNodes.

This change renders the previous element API incoherent because there is no guarantee of a single "main" element for a template. Instead, we now set the bounds property of a component, which contains pointers to the first node and last node.

This is obviously less convenient for cases where you do just have a single conceptual main element. For example, if you have a template with a single root element, you would probably prefer to use this.element instead of this.bounds.firstNode or this.bounds.lastNode.

We could automatically determine the "single element" case and assign element automatically, but we were afraid that it would become a refactoring hazard. If you had a template with a single root element, wrote a bunch of code that used this.element, and then someone came along and added a comment to the beginning, all of that code would suddenly stop working.

Instead, you can now indicate which element is the conceptual "main" element by using ...attributes, which also is the location where invocation-side attributes will get splatted into.
For example:

{{!-- MyComponent.hbs --}}
<h1>Title</h1>
<div class="content" ...attributes>
</div>

In this example, this.element would be the <div> element. If you invoked this component with <MyComponent id="component-id" />, this <div> would also receive the id attribute.

With the recent Glimmer VM changes to adopt IndustryStandardCase, Glimmer.js components now relax the requirement of a template having a single root element. In addition to having multiple elements, this also means that a template may have top-level nodes such as CommentNodes or TextNodes.

This change renders the previous `element` API incoherent because there is no guarantee of a single "main" element for a template. Instead, we now set the `bounds` property of a component, which contains pointers to the first node and last node.

This is obviously less convenient for cases where you do just have a single conceptual main template. For example, if you have a template with a single root element, you would probably prefer to use `this.element` instead of `this.bounds.firstNode` or `this.bounds.lastNode`.

We could automatically determine the "single element" case and assign element automatically, but we were afraid that it would become a refactoring hazard. If you had a template with a single root element, wrote a bunch of code that used `this.element`, and then someone came along and added a comment to the beginning, all of that code would suddenly stop working.

Instead, you can now indicate which element is the conceptual "main" element by using `...attributes`, which also is the location where invocation-side attributes will get splatted into.
For example:

```hbs
{{!-- MyComponent.hbs --}}
<h1>Title</h1>
<div class="content" ...attributes>
</div>
```

In this example, `this.element` would be the `<div>` element. If you invoked this component with `<MyComponent id="component-id" />`, this `<div>` would also receive the `id` attribute.
@rtablada
Copy link

rtablada commented Sep 28, 2017

@tomdale 👏 The ...attributes solves an issue I had talked to Yehuda about where class name and attribute bindings are lost when using things like data attributes or class names with Ember's current tagName: ''.

@locks
Copy link
Contributor

locks commented Sep 28, 2017

Clarifying question: do we need to always set ...attributes? If so, is it a compile time error if we don't?

@tomdale tomdale merged commit 529db04 into master Sep 28, 2017
@tomdale
Copy link
Contributor Author

tomdale commented Sep 28, 2017

@locks No, it is not required. You will get an error if you invoke a component with attributes and it doesn't have splattributes, and I want to add a development-time error that gives helpful guidance if you access this.element without ...attributes. (Working with @chadhietala on making sure we have stripping infrastructure for Glimmer.js.)

@tomdale tomdale deleted the component-bounds branch September 28, 2017 16:26
@rtablada
Copy link

Another question would be if ...attributes passes down events bindings.

Would <MyComponent onclick={{action "myThing}} /> pass the onclick binding to the ...attributes element?

@tomdale
Copy link
Contributor Author

tomdale commented Sep 28, 2017

@rtablada I don't believe this example works because attribute values can only be strings, and {{action}} returns a function. I think in this case we want an element modifier (<div {{on 'click' myThing}}>) which IIRC we plan to splat on to ...attributes as well, though I'm not sure if we have consensus on that.

@rtablada
Copy link

@tomdale cool. Good work. LMK if there's more discussion on how to handle passing down events.

@rtablada
Copy link

Just to clarify, I think some confusion will come down to this:

If people expect ...atributes to pass all native attributes, they will assume onclick to be a native attribute.

@tomdale saying that "...attributes only passes string attributes" might be clarification enough although I think that there could be a cool story for events too be passed too.

* You should not try to access this property until after the component's `didInsertElement()`
* lifecycle hook is called.
*/
element: Simple.Element = null;
element: HTMLElement;
Copy link
Member

Choose a reason for hiding this comment

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

I don't fully understand this change. How does this work when rendering in Node-land?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Accessing the DOM should only be done after didInsertElement(), which isn't invoked in Node-land.

Copy link
Member

@rwjblue rwjblue Sep 28, 2017

Choose a reason for hiding this comment

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

@tomdale - Ya, I don't really agree here. There are other hooks that are called where this.element might be used. A better solution (IMHO) is to pass element: HTMLElement in as an argument to didInsertElement. This preserves the proper typings when using this.element outside of didInsertElement, and allows usage inside didInsertElement without casting.

Also, generally speaking, this sort of discussion is super valuable and it is very unfortunate that it was not allowed before the PR was merged...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We are trying to move away from having intermediate DOM in SSR mode entirely because of the performance impact. Sorry if you feel the PR was rushed. I wrote it with @chadhietala and reviewed it with @wycats so was definitely not sneaking in unvetted code.

@tomdale
Copy link
Contributor Author

tomdale commented Sep 28, 2017

@rtablada Not sure what you mean by "native" attributes vs. "string" attributes. All attributes are coerced to strings when being set:

let div = document.createElement('div');
div.setAttribute('onclick', function() { });
div.getAttribute('onclick'); // => "function() { }"
typeof div.getAttribute('onclick'); // => "string"

@cibernox
Copy link

cibernox commented Sep 30, 2017

The multi root components and ...attributes are awesome!

I'm seeying on the comments the usage of <MyComponent> instead of <my-component>. Is that intentional? Did that change recently?

@locks
Copy link
Contributor

locks commented Sep 30, 2017

Did it? :shipit:

@tomdale
Copy link
Contributor Author

tomdale commented Oct 2, 2017

@cibernox Yes, this change was intentional. The use of CapitalComponents (and design of the Glimmer component API in general) dominated the agenda of our recent core team face-to-face meeting. 😄

We will have a write-up soon of the proposed new changes and their rationale. The TL;DR is that they unlock multi-root/fragment components in addition to being less cognitive overhead (you don't have to invent two words when one is the most descriptive).

topaxi pushed a commit to topaxi/glimmer.js that referenced this pull request Oct 27, 2017
Introduce public dir for images, robot.txt, favicon, etc.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants