-
-
Notifications
You must be signed in to change notification settings - Fork 4.2k
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
[QUEST] Glimmer Components in Ember #16301
Comments
We'll be coordinating work in the #st-glimmer-components channel on the Ember community Slack if you're interested in helping out! Lurkers welcome too if you're just curious how the sausage gets made. |
Regarding this; {{! src/ui/components/UserAvatar/template.hbs }}
<div ...attributes> {{! <-- attributes will be inserted here }}
<h1>Hello, {{@firstName}}!</h1>
</div> I don't understand why curly brackets are used to denote dynamic variables ( |
Are there RFCs for glimmer components where we can discuss how they should look and work? |
@tomdale the issue above mentions documenting the component compat mode, but it does not seem to mention documenting a migration guide. for example I was quite surprised that |
@dbbk The biggest reason we don't require I think it may also cause people to assume There has been some suggestion of adopting both @Gaurav0 The Glimmer component API would go through the RFC process before being enabled by default in Ember apps, should we ever want to do that. One nice thing about the custom component manager approach is that we don't canonize a "next generation" API but can let different designs compete on their merits via the addon ecosystem. @Turbo87 Great suggestion, I'll add a migration guide to the list. We should show existing patterns and how to approach solving the same problem with the new API. I'll address the case of The alternative is to switch from "push-based" initialization to "pull-based" initialization. For example, if you wanted to do some computation to generate or initialize the value of a component property, you would instead use a tracked computed property. This ensures the work is only done for values that actually get used. Example: Instead of this: import Component from "@ember/component";
import { computed } from "@ember/object";
export default Component.extend({
didReceiveAttrs() {
this.set('firstName', this.attrs.firstName || 'Tobias');
this.set('lastName', this.attrs.lastName || 'Bieniek');
},
fullName: computed('firstName', 'lastName', function() {
return `${this.firstName} ${this.lastName}`;
})
}); Do this: import Component, { tracked } from "@glimmer/component";
export default class extends Component {
@tracked get firstName() {
return this.args.firstName || 'Tobias';
}
@tracked get lastName() {
return this.args.lastName || 'Bieniek';
}
@tracked get fullName() {
return `${this.firstName} ${this.lastName}`;
}
} I'll admit I was skeptical of this at first, but @wycats persuaded me, and in my experience building a big Glimmer.js app over the last year or so, we never ran into a use case for |
@tomdale I agree that |
yep, I'm thinking of similar use cases. one example is animation. say I want to kick off an animation when |
|
Also |
@Turbo87 There might be a misconception around when @Gaurav0 You're moving the goalposts. ;) Your example was kicking off an ajax request in response to an argument change, which would not cause an error to be thrown if it happened in |
alright, that seems good enough for me. I didn't want to expand on this specific thing anyway, just wanted to highlight that thorough migration guides will be needed :) |
@tomdale is this still up to date? seems to be inactive for some time. |
@tomdale Ok, the scenario is that we need to kick off an ajax request when a particular attribute has changed, and only that attribute. We don't want to write an observer. Can we still do this in didInsertElement? |
@Gaurav0 What is causing the attribute to change? Following DDAU, I think whatever code is responsible for changing the data that causes the attribute to change should also be responsible for kicking off the AJAX request. |
The attribute is changing from the parent component / controller. DDAU. |
@Gaurav0 @tomdale Another example of this use case is a (mostly) "DOM-less" set of components which apply changes to some other "object". A current example would be ember-leaflet/ember-composability-tools which allows to declaratively construct the contents of a leaflet map. To me this does not seem possible in Glimmer components right now due to the lack of any kind of |
@nickschot I think I can take this one, we actually did an audit during the design process of various community addons and use cases, and specifically covered If you dig into In the general case, you can use There is a use case that is somewhat related, which is not possible with Glimmer components - a "render detector" like this one in the |
@pzuraq its been a while, but small update in this area. For my use case I cant use modifiers as there is no DOM at all within these components. What I've currently done is implement a custom component manager w/ glimmer semantics which re-enables the didUpdate hook. |
@nickschot our recommendation there is to actually use a helper. Helpers can return nothing and side-effect, similar to The class based helper API does need some work, ideally it should match the final user facing modifier API I think (plus we need to introduce helper managers in order to make it possible to redesign at all), but the current API should give you all the abilities you need to accomplish any side effects you're after. |
@pzuraq How does that work? Do we need to create a custom helper or any helper will do? Is the syntax like this? {{concat (did-insert this.myAction)}} It feels like passing an argument and not "modifying". Or are you suggesting that we should use helpers instead of modifiers? Something like:
If yes, please post some links to documentation and examples. Current public API of the class helper only has Please excuse stupid questions, but to regular Ember users this matter is quite cryptic, documentation is lacking and scattered. |
Right, exactly. You can see an example of this in ember-render-helpers for instance, which mimics the In general, If you need something along the lines of export default class didInsert extends Helper {
compute(fn) {
if (!this.rendered) {
fn();
this.rendered = true;
}
}
} And no worries, I understand that it may seem a bit frustrating and cryptic. That's the nature of being on the cutting edge/canary side of things, we haven't gotten all the new patterns documented yet and we're working on it 🙂 |
I believe this issue can be closed now that Octane is out 😄 |
This quest issue tracks the implementation of Glimmer components in Ember.js.
The Plan
Glimmer.js components have the following features:
tagName
,attributeBindings
, etc.)@
prefixed, like{{@firstName}}
this.args
@tracked
properties<AngleBracket />
syntax…attributes
In keeping with Ember’s spirit of incrementalism, we want to land this functionality piece by piece via an addon. This allows the community to start using features and providing feedback early in the process.
In fact, we’ve already started down the road to Glimmer components in Ember. The first two features have already started to land:
template-only-glimmer-components
optional feature has been enabled.)@
prefix (e.g.{{@firstName}}
).This issue proposes finishing the process of bringing Glimmer components to Ember by allowing addons to provide alternate component implementations, then transforming the
@glimmer/component
package into an Ember addon that implements the Glimmer.js component API.We’ll break that work into phases, each one unlocking benefits for existing Ember apps and addons. Phase 0 is about adding the necessary primitives to Ember.js to support alternate component implementations. Phases 1, 2 and 3 are about incrementally enabling Glimmer.js component API.
While we go into depth on Phases 0 and 1, we will defer exploring the technical details of later phases until the first phases are closer to completion.
Phase 0: Customizing Component Behavior
TL;DR: Add a CustomComponentManager API to Ember.js to allow addons to implement custom component API.
Currently, all components in an Ember app are assumed to be subclasses of
Ember.Component
. In order to support alternate component APIs in Ember, we need some way to tell Ember when and how component behavior should change.When we say “custom component behavior,” we specifically mean:
While Glimmer VM introduces the concept of a “component manager,” an object that makes these decisions, this API is very low level. It would be premature to adopt them directly as public API in Ember because they are difficult to write, easy to author in a way that breaks other components, and not yet stable.
Instead, we propose a new Ember API called
CustomComponentManager
that implements a delegate pattern. TheCustomComponentManager
provides a smaller API surface area than the full-fledgedComponentManager
Glimmer VM API, which allows addon authors to fall into a “pit of success.”So how does Ember know which component manager to use for a given component? The original iteration of the Custom Components RFC introduced the concept of a
ComponentDefinition
, a data structure that was eagerly registered with Ember and specified which component manager to use.One of the major benefits of the
ComponentDefinition
approach is that component manager resolution can happen at build time. Unfortunately, that means we have to design an API for exactly how these get registered, and likely means some sort of integration with the build pipeline.Instead, we propose an API for setting a component’s manager at runtime via an annotation on the component class. This incremental step allows work on custom component managers to continue while a longer term solution is designed.
Custom Component Manager Discovery
In this iteration, components must explicitly opt-in to alternate component managers. They do this via a special
componentManager
function, exported by Ember, that annotates at runtime which component manager should be used for a particular component class:Eventually, this could become a class decorator:
The first time this component is invoked, Ember inspects the class to see if it has a custom component manager annotation. If so, it uses the string value to perform a lookup on the container. In the example above, Ember would ask the container for the object with the container key
component-manager:glimmer
.Addons can thus use normal resolution semantics to provide custom component managers. Our Glimmer component addon can export a component manager from
addon/component-managers/glimmer.js
that will get automatically discovered through normal resolution rules.While this API is verbose and not particularly ergonomic, apps and addons can abstract it away by introducing their own base class with the annotation. For example, if an addon called
turbo-component
wanted to provide a custom component manager, it could export a base class like this:Users of this addon could subclass the
TurboComponent
base class to define components that use the correct component manager:Custom Component API
No component is an island, and for backwards compatibility reasons, it’s important that the introduction of new component API don’t break existing components.
One example of this is the existing view hierarchy API. Ember components can inspect their parent component via the
parentView
property. Even if the parent is not anEmber.Component
, the child Ember components should still have a non-nullparentView
property.Currently, the
CurlyComponentManager
in Ember is responsible for maintaining this state, as well as other ambient “scope state” like the target of actions.To prevent poorly implemented component managers from violating invariants in the existing system, we use a compositional pattern to customize behavior while hiding the sharp corners of the underlying API.
Phase 1: Ember Object Glimmer Components
The
Component
base class in Ember supports a long list of features, many of which are no longer heavily used. These features can impose a performance cost, even when they are unused.As a first step, we want to provide a way to opt in to the simplified Glimmer.js component API via the
@glimmer/component
package. To ease migration, we will provide an implementation of the GlimmerComponent
base class that inherits fromEmber.Object
at@glimmer/component/compat
.Here’s an example of what an “Ember-Glimmer component” looks like:
Notable characteristics of these components:
actions
hash.args
property rather than setting individual properties on the component directly.Just as important is what is not included:
@tracked
properties will not be supported until Phase 3.layout
ortemplate
properties on the component.childViews
,parentView
,nearestWithProperty
, etc.tagName
,attributeBindings
, or other custom JavaScript DSL for modifying the root element.send
orsendAction
for dispatching events.ember-view
class or auto-generated guid element ID.rerender()
method.attrs
property (useargs
instead).this.$()
to create a jQuery object for the component element.appendTo
of components into the DOM.willInsertElement
didRender
willRender
willClearRender
willUpdate
didReceiveAttrs
didUpdateAttrs
parentViewDidChange
on()
event listener for component lifecycle events; hooks must be implemented as methods.One interesting side effect of this set of features is that it dovetails with the effort to enable JavaScript classes. In conjunction with the design proposed in the ES Classes RFC, we can provide an alternate implementation of the above component:
Phase 2 - Angle Bracket Syntax
Phase 2 enables invoking components via angle brackets (
<UserAvatar @user={{currentUser}} />
) in addition to curlies ({{my-component user=currentUser}}
). Because this syntax disambiguates between component arguments and HTML attributes, this feature also enables “splatting” passed attributes into the component template via…attributes
.This would render output similar to the following:
Phase 3 - Tracked Properties
Phase 3 enables tracked properties via the
@tracked
decorator in Ember. The details of the interop between Ember’s object model and tracked properties is being worked out. Once tracked properties land, users will be able to drop the@glimmer/component/compat
module and use the normal, non-Ember.Object component base class.In tandem with the recently-merged “autotrack” feature (which infers computed property dependencies automatically), this should result in further simplification of application code:
Q&A
Can I add back things like the
ember-view
class name or auto-generatedid
attribute to Glimmer components for compatibility with existing CSS?Yes. Example:
Resources
custom-component-manager
branchCustomComponentManager
implementation behind a feature flagtracked
branch@tracked
properties and Ember objectsTasks
We’ll use the lists below to track ongoing work. As we learn more during implementation, we will add or remove items on the list.
Custom Component Manager API
glimmer-custom-component-manager
feature flagcomponentManager
function{ componentManager } from '@ember/custom-component-manager'
CustomComponentManager
APICustomComponentManagerDelegate
interfaceversion
create()
getContext()
update()
destroy?()
didCreate?()
didUpdate?()
getView?()
version
property upon creationchildViews
andparentView
in existing componentsGlimmer Component Addon
sparkles-components
addoncomponentManager
annotation when consumed from EmberCustomComponentManager
implementation should be discoverable via Ember’s container@glimmer/component
as an Ember addonimport Component from '@glimmer/component'
provides plain JavaScript base classimport Component from '@glimmer/component/compat'
providesEmber.Object
base classlookup
create(injections)
didInsertElement()
willDestroy()
didUpdate()
click()
etc.) should not be triggeredElement Accessthis.bounds
this.element
computed property alias tothis.bounds
this.args
is available in the constructorthis.args
is updated beforedidUpdate
is calledthis.args
should not trigger an infinite re-render cycle (need to verify)args
Open Questions
create()
method) as component classes? The protocol for initializing a component seems simple but is surprisingly tricky.CustomComponentManager
?CustomComponentManager
versioning?The text was updated successfully, but these errors were encountered: