From e80969a07cf1c4dcbfe09fc764a376b0296544ec Mon Sep 17 00:00:00 2001 From: Godfrey Chan Date: Thu, 20 Dec 2018 13:32:14 -0800 Subject: [PATCH 01/24] [WIP] contextual helpers --- text/0000-contextual-helpers.md | 707 ++++++++++++++++++++++++++++++++ 1 file changed, 707 insertions(+) create mode 100644 text/0000-contextual-helpers.md diff --git a/text/0000-contextual-helpers.md b/text/0000-contextual-helpers.md new file mode 100644 index 0000000000..3adc4a1ed4 --- /dev/null +++ b/text/0000-contextual-helpers.md @@ -0,0 +1,707 @@ +- Start Date: 2018-12-17 +- RFC PR: (leave this empty) +- Ember Issue: (leave this empty) + +# Contextual Helpers (a.k.a. "first-class helpers") + +## Summary + +We propose to extend the semantics of Handlebars helpers such that they can be +passed around as first-class values in templates. + +For example: + +```hbs +{{#let (helper "concat" "foo") as |foo|}} + {{#let (helper foo "bar") as |foo-bar|}} + {{foo-bar "baz"}} + {{/let}} +{{/let}} +``` + +This template will print "foobarbaz". + +## Motivation + +[RFC #64](https://github.com/emberjs/rfcs/pull/64) introduced a feature known +as "contextual components", which allowed components to be passed around as +first-class values. While this is a somewhat advanced feature, it allowed addon +authors to encapsulate internal state and logic, which in turns, allowed them +to create easy-to-use and easy-to-understand DSL-like APIs that could benefit +users of all level. + +For example, the original RFC used form controls as a motivating example. +Without contextual compoments, an addon that provides form-building components +might have to expose an API like this: + +```hbs + + + + + +``` + +As you can see, this is far from ideal for serveral reasons. First, to avoid +collision, the addon author had to prefix all the components. Second, the +`@model` argument has to be passed to all the controls that needs it. Finally, +in cases where the compoments need to communicate with each other (the form and +the submit button in the example), they would have to expose some internal state +(the `|f|` block param) that the user would have to manually thread through. Not +only does this make the API very verbose, it also breaks encapsulation. + +Instead, the contextual components feature allows the addon author to expose an +API like this: + +```hbs + + + + + +``` + +Behind the scene, the `` compoment's template would look something +like this: + +```hbs +
+ {{yield (hash + Input=(component "super-input" form={{this}} model={{this.model}}) + Textarea=(component "super-textarea" form={{this}} model={{this.model}}) + Submit=(component "super-submit" form={{this}}) + )}} +
+``` + +Here, the `component` helper looked up the components by name (first argument) +and packaged up ("curried") any additional arguments (`form={{this}}`) into an +internal object (known as a "component definition" in the Glimmer VM). This +object can then be passed around like any other values and invoked at a later +time. + +(Typically, a number of them are passed to the `hash` helper to "bundle" them +into a single object, but this is not required.) + +While this is indeed a pretty advanced feature, the users of `SuperForm` do not +need to be aware of these implementation details in order to use the addon. +This had proved to be a very useful and powerful feature and enabled a number +of popular addons, such as **@kategengler send halp**. + +The original RFC left an important question unanswered – [should this feature +be available for helpers, too?](https://github.com/emberjs/rfcs/blob/master/text/0064-contextual-component-lookup.md#unresolved-questions) + +In this RFC, we argue – yes, this feature will be just as useful for helpers as +well as modifiers. + +For example, the `SuperForm` addon API can be expanded to include some extra +helpers and modifiers, like so: + +```hbs + + + + {{!-- f.is-valid and f.error-for are contextual helpers --}} + {{#unless (f.is-valid "title")}} +
This field {{f.error-for "title"}}
+ {{/unless}} + + {{!-- f.auto-resize is a contextual modifier --}} + + + +
+``` + +For reference, the `` compoment's template would look something like +this: + +```hbs +
+ {{yield (hash + is-valid=(helper "super-is-valid" form=this model=this.model) + error-for=(helper "super-error-for" form=this model=this.model) + auto-resize=(modifier "super-auto-resize") + ... + )}} +
+``` + +This RFC proposes a complete design for enabling this capability. + +## Detailed design + +### The `helper` and `modifier` helpers + +This RFC introduce two new helpers named `helper` and `modifier`, which work +similarly to the `component` helper: + +* When passed a string (e.g. `(helper "foo")`) as the first argument, it will + produce an opaque, internal helper definition" object that can be passed + around and used to invoke the corresponding helper (the `foo` helper in this + case) in Handlebars. + +* When passed an "helper definition" as the first argument, it will produce a + functionally equivialent "helper definition" object. + +* In either case, any additional positional and/or named arguments will be + stored ("curried") inside the "helper definition" object, such that, when + eventually invoked, these arguments will be passed along to the referenced + helper. + +Some additional details: + +* The string will be used to resolve a helper with the same name. If the string + does not correspond to a valid helper, it will result in a runtime error. + However, the timing of this lookup is unspecified. `(helper "not-a-helper")` + may result in an immediate error, or it may happen when it is later passed + into the `helper` helper a second time, or when it is invoked. If it is never + invoked, the error may not be reported at all. This timing may change between + versions, and should not be relied upon. + +* Positional arguments are "curried" the same way as the `component` helper + (which matches the behavior of `Function.prototype.bind`). + + ```hbs + {{#let (helper "concat") as |my-concat|}} + {{my-concat "foo" "bar" "baz"}} {{!-- "foobarbaz" --}} + + {{#let (helper concat "foo") as |foo|}} + {{foo "bar" "baz"}} {{!-- "foobarbaz" --}} + + {{#let (helper foo "bar") as |foo-bar|}} + {{foo-bar "baz"}} {{!-- "foobarbaz" --}} + {{/let}} + + {{/let}} + + {{/let}} + ``` + +* Named arguments are "curried" the same way as the `component` helper (which + is "last-write-wins", matching the behavior of `Object.assign`). + + ```hbs + {{#let (helper "hash") as |my-hash|}} + {{my-hash value="foo"}} {{!-- hash with value="foo" --}} + + {{#let (helper my-hash value="foo") as |foo|}} + {{foo value="bar"}} {{!-- hash with value="bar" --}} + + {{#let (helper foo value="bar") as |bar|}} + {{bar value="baz"}} {{!-- hash with value="baz" --}} + {{/let}} + + {{/let}} + + {{/let}} + ``` + +* When a "helper definition" is passed into JavaScipt, the resulting value is + left unspecified (which is why it is described as "opaque"). In particular, + it is _not_ guarenteed that it will be an invokable JavaScript function. The + only guarentee provided is that, when passed back into Handlebars, it will be + functionally equivilant to the original "helper definition". Hanging onto a + "helper definition" object in JavaScript may result in unexpected memory + leaks, as these objects may "close over" arbitrary template states to allow + currying. + +### Invoking contextual helpers + +For the most part, invoking a contextual helper is no different from invoking +any other helpers: + +```hbs +{{#let (helper "concat" "foo" "bar") as |foo-bar|}} + {{!-- content position --}} + {{foo-bar "baz"}} + + {{!-- not necessary, but works --}} + {{helper foo-bar "baz"}} + + {{!-- attribute position --}} +
...
+ + {{!-- curly invokcation, argument position --}} + {{MyComponent value=(foo-bar "baz")}} + + {{!-- angle bracket invokation, argument position --}} + + + {{!-- sub-expression positions --}} + {{yield (hash foo-bar=foo-bar)}} + + {{#if (eq (foo-bar "baz") "foobarbaz")}}...{{/if}} + + {{!-- runtime error: not a component --}} + + + {{!-- runtime error: not a modifier --}} +
+{{/let}} +``` + +### Ambigious invocations (invoking without arguments) + +When invoking a contextual helper without arguments, the invokation becomes +ambigious. Consider this: + +```hbs +{{#let (helper "concat" "foo" "bar") as |foo-bar|}} + {{!-- content position --}} + + {{!-- does this render "foobar", or something like "[object Object]" ? --}} + {{foo-bar}} + + {{!-- attribute position --}} + + {{!-- class="foobar", or something like class="[object Object]" ? --}} +
...
+ + {{!-- curly invokcation, argument position --}} + + {{!-- is this.value the string "foobar", or the "helper definition" ? --}} + {{MyComponent value=foo-bar}} + + {{!-- angle bracket invokation, argument position --}} + + {{!-- is @value the string "foobar", or the "helper definition" ? --}} + + + {{!-- sub-expression positions --}} + + {{!-- are we yielding the helper, or the string "foobar" ? --}} + {{yield (hash foo-bar=foo-bar)}} + + {{!-- is this true or false? --}} + {{#if (eq foo-bar "foobar")}}...{{/if}} +{{/let}} +``` + +In these cases, these are ambigious between passing the _result_ of the helper +(first invoking the helper, then pass along the result), or passing the helper +itself (the "helper definition" object, so that it can be invoked or "curried" +again on the receiving side). + +Since this RFC proposes to treat helpers and modifiers as first-class values, +they should generally be passed through as _values_. This is particularly +important in arguments and sub-expression positions. To invoke the helper, the +explicit `(some-helper)` syntax can be used instead: + +```hbs +{{#let (helper "concat" "foo" "bar") as |foo-bar|}} + {{!-- curly invokcation, argument position --}} + + {{!-- the component will receive the "helper definition" --}} + {{MyComponent value=foo-bar}} + + {{!-- the component will receive the string "foobar" --}} + {{MyComponent value=(foo-bar)}} + + {{!-- angle bracket invokation, argument position --}} + + {{!-- @value will be the "helper definition" --}} + + + {{!-- @value will be the string "foobar" --}} + + + {{!-- sub-expression positions --}} + + {{!-- yielding the helper --}} + {{yield (hash foo-bar=foo-bar)}} + + {{!-- yielding the string "foobar" --}} + {{yield (hash foo-bar=(foo-bar))}} + + {{!-- false --}} + {{#if (eq foo-bar "foobar")}}...{{/if}} + + {{!-- true --}} + {{#if (eq (foo-bar) "foobar")}}...{{/if}} +{{/let}} +``` + +However, in the case of content and attribute positions, it would be overly +pendantic to insist on the `{{(some-helper)}}` syntax, as the alternative of +printing `[object Object]` to the DOM is almost certainly not what the +developer had in mind. Therefore, we propose to allow contextual helpers to be +auto-invoked in these positions. + +```hbs +{{#let (helper "concat" "foo" "bar") as |foo-bar|}} + {{!-- content position --}} + + {{!-- "foobar" --}} + {{foo-bar}} + + {{!-- not necessary, but works --}} + {{(foo-bar)}} + + {{!-- attribute position --}} + + {{!-- class="foobar" --}} +
...
+ + {{!-- class="foobar" --}} +
...
+{{/let}} +``` + +It should also be noted that modifiers doe not have the same problem, since +there are no other possible meaning in that position: + +```hbs +{{#let (modifier "foo-bar") as |foo-bar|}} + {{!-- modifier position: not ambigious --}} +
+ + {{!-- not necessary, but works --}} +
+ + {{!-- undefined behavior: runtime error or [object Object] ? --}} + {{foo-bar}} + + {{!-- undefined behavior: runtime error or [object Object] ? --}} +
+{{/let}} +``` + +### Deprecation + +In today's Ember, "global helpers" (as opposed to "contextual helpers") does +not always follow the rules laid out above. In particular, they do not behave +the same way in arguments and subexpression positions: + +```hbs +{{#let (helper "concat") as |my-concat|}} + {{!-- curly invokcation, argument position --}} + + {{!-- this.value is the helper --}} + {{MyComponent value=my-concat}} + + {{!-- this.value is the undefined --}} + {{MyComponent value=concat}} + + {{!-- angle bracket invokation, argument position --}} + + {{!-- @value is the helper --}} + + + {{!-- @value is an empty string (invoking concat with no arguments) --}} + + + {{!-- sub-expression positions --}} + + {{!-- yielding the helper --}} + {{yield (hash value=my-concat)}} + + {{!-- yielding undefined --}} + {{yield (hash value=concat)}} + + {{!-- false: it compares the helper with undefined --}} + {{#if (eq my-concat concat)}}...{{/if}} +{{/let}} +``` + +This illustrates the problem: "global helpers" are not modelled as first-class +values today, they exists in a different "namespace" distinct from the "local +variables" namespace. + +While this is not so different from other programming languages like Java and +Ruby which also do not treat functions (methods) as first-class values, it is +distinctly different from JavaScript which does. For example, `alert("hello")` +is the same as `let a = alert; a("hello");`, whereas in Java and Ruby (and +today's Handlebars), the moral equivilant of the latter would fail with `alert` +being an undefined reference. + +The goal of this RFC is to iterate the Ember Handlebars programming model +towards a world closer to JavaScript's, where global names exists in the same +namespace as local names. We propose to deprecate all cases where global names +("global helpers", "global modifiers" and "global components") can be observed +to behave differently. + +Specifically: + +```hbs +{{#let (helper "concat") as |my-concat|}} + {{!-- curly invokcation, argument position --}} + + {{!-- deprecation: use `this.concat` instead --}} + {{MyComponent value=concat}} + + {{!-- angle bracket invokation, argument position --}} + + {{!-- deprecation: use `{{(concat)}}` instead --}} + + + {{!-- sub-expression positions --}} + + {{!-- deprecation: use `this.concat` instead --}} + {{yield (hash value=concat)}} + + {{!-- depreaction: use `this.concat` instead --}} + {{#if (eq my-concat concat)}}...{{/if}} +{{/let}} +``` + +Overall, we expect the effect of this deprecation to be quite minimal. For the +cases that triggers a property lookup today, they are already covered in the +[Property Lookup Fallback Deprecation RFC](https://github.com/emberjs/rfcs/blob/master/text/0308-deprecate-property-lookup-fallback.md), +plus it would already be quite confusing to name an instance variable after a +global helper. For the cases where the "global helper" is implicitly invoked +without arguments, since helpers are supposed to be pure computations, a helper +that doesn't accept any arguments have very limited utility thus should also be +quite rare. + +In addition, another natural fallout of this plan is that it is not be possible +to have helpers, components or modifiers with the same name, as they ultimately +will share the same namesapce. These conflicts will need to be detected and +deprecated as well. + +### Local helpers (and modifiers) + +A nice fallout of this plan is that developers will be able to define helpers +specific to a component locally (i.e. in the same JavaScript file): + +```js +// app/components/date-picker.js + +import Component from '@ember/component'; +import { helper } from '@ember/component/helper'; + +export default Component.extend({ + date: null, // passed in + + 'format-date': helper(function(params, hash) { + /* ... */ + }) +}); +``` + +```hbs +{{!-- app/templates/components/date-picker.hbs --}} + +{{input value=(this.format-date this.date)}} +``` + +In additional to encapsulation and namespacing, this will also enable even more +advanced stateful use cases: + +```js +// app/components/filtered-each.js + +import Component from '@ember/component'; +import { helper } from '@ember/component/helper'; +import { computed } from '@ember/object'; + +export default Component.extend({ + list: null, // passed in + callback: null, // passed in + + filter: computed('callback', function() { + return helper(params => this.callback(params[0])); + }); +}); +``` + +```hbs +{{!-- app/templates/components/filtered-each.hbs --}} + +{{#each this.list as |item|}} + {{#if (this.filter item)}} + {{yield item}} + {{/if}} +{{/each}} +``` + +## How we teach this + +There are two sides to this feature. + +First, there is the `helper` and `modifier` helpers that produces the "curried +values". As with the `component` helper and other "higher-order functions" in +JavaScript, this is a somewhat advanced concept that is mainly targeted at +addon authors and advanced developers. + +For this group of users, we expect this feature to complement and complete the +"contextual components" feature. Developers who are already familiar with that +feature should feel right at home. We expect to be able to introduce this new +feature at places where we currently teach contextual components. + +The second side of this feature is the invoking side. We expect intermediate +developers (and perhaps even beginners) to enounter this mainly through addons +that other developers have written. So long as there is adequate documentation +from the addon authors, we expect that this group of users can be immediately +productive by simply treating these APIs as DSLs (similar to the Router DSL). + +In other words, while this group of developer may not immediately understand +how to _author_ these kind of APIs, or what is involved under-the-hood to make +it work, the design goal is that it should feel straightforward to _consume_ +this style of API. + +### Rationalization + +We propose to rationalize the existing and proposed semantics into a coherent +model at a deeper level. This knowledge is necessary for day-to-day use, but +could be helpful for guiding the implementation as well as design of future +feature proposals. + +On the high-level, the guiding principles are: + +1. Components, helpers and modifiers are first-and-foremost values that are + bound to Handlebars identifiers. When referring to these identifiers, they + should consistently behave as inert values unless they are _explicitly_ + invoked. + +2. Without violating the first principle, for ergonomic reasons, in places + where it unambigiously would not make sense for them to behave as values, + they should be _implicitly_ invoked rather than raisng an error or giving + nonsensical results. + +For helpers, the explicit invocation syntax is `(...)`, i.e. `(foo-bar)`, +`(foo-bar "baz")`, `(foo-bar baz="bat")`, etc. This is already mandatory in all +sub-expression posisitions today. + +It follows that, the explicit syntax for invoking a helper and appending its +result to the DOM would be: + + +```hbs +
    +
  • No arguments: {{(foo-bar)}}
  • +
  • Positional argument: {{(foo-bar "baz")}}
  • +
  • Named argument: {{(foo-bar baz="bat")}}
  • +
+``` + +The `{{(...)}}` form results in a syntax error today. For completeness, we +propose to modify the grammar to allow this explicit form. However, it is +neither required nor necessarily encouraged, as it adds a lot of visual noise +to the template. Following the second guiding principle, the implicit helper +invocation syntax will continue to work: + +```hbs +
    +
  • No arguments: {{foo-bar}}
  • +
  • Positional argument: {{foo-bar "baz"}}
  • +
  • Named argument: {{foo-bar baz="bat"}}
  • +
+``` + +The last two forms (with arguments) are non-ambigious: they could not possibly +mean anything else other than to invoke `foo-bar` with the given arguments, +therefore, in these cases, the parentheses will be automatically inserted in +parsing time. + +The first form (without arguments) is indeed ambigious – in fact it will be a +violation of the first guiding principle if this is interpreted as anything +other than referring to the value. To be precise, it has to mean: "append the +value referred to by the `foo-bar` identifier (which happens to be a helper) as +content into the DOM". Therefore, we propose to rationalize this case +differently. + +Indeed, the helper is passed as a value here (as opposed to being invoked). +However, at runtime, the rendering engine has to decide exactly how to append a +particular value, which happens to be a helper here, as content into the DOM. + +There are serveral options here, such as rasing an error ("I don't know how to +turn a helper into content"), using JavaScript's default `toString` (resulting +in "[object Object]") or first invoke the helper with no arguments and then +append the _result_ as content. We argue that the first option is too pedantic, +the second option is not useful and therefore the third option is unambigiously +the only reasonable option. This allows all three existing form to work while +still staisfying the guiding principles. + +This works similarly in attribute positions too. + +Explicit invocation forms: + +```hbs +
    +
  • No arguments
  • +
  • Positional argument
  • +
  • Named argument
  • +
+``` + +Implicit invocation forms: + +```hbs +
    +
  • No arguments
  • +
  • Positional argument
  • +
  • Named argument
  • +
+``` + +Here, the first form relies on the runtime to invoke the helper before its +added to the DOM, and the last two relies on automatic parentheses insertion +at parse time. + +For named arguments position, it is slightly different. + +Explicit invocation forms: + +```hbs +No arguments +Positional argument +Named argument +``` + +While the parentheses in the last two forms can be unambigiously omitted, the +same is not true about the first form. It is conceivable that there is both the +need to pass helpers as value and the results of their invocations. Therefore, +when there are no arguments, the parentheses are mandatory for invocations to +disambiguate between the two. + +For components, the explict invocation syntax is `<...>`, i.e. ``, +``, `...`, etc. + +When a component is "invoked" in sub-expression form, we propose that it should +produce a new curried component with the given arguments. That is, if `foo-bar` +refers to a component value, then `(foo-bar)`, `(foo-bar "baz")` and +`(foo-bar bat="baz")` has the same semantics as `(component foo-bar)`, +`(component foo-bar "baz")` and `(component foo-bar bat="baz")`, respectively. + +This allows curly invocations to work: + +```hbs +{{foo-bar}} +{{foo-bar "baz"}} +{{foo-bar baz="bat"}} +``` + +In the first case, it refers to the component by value. Since the value happens +to be a component in this case, the rendering engine will invoke it at runtime. +The second and the third case relies on automatic parentheses insertion – they +desugars into `{{(foo-bar "baz")}}` and `{{(foo-bar baz="bat")}}`, respectively, +which produces anymous curried components, which are also invoked at runtime. + +If a component value, curried or not, is passed to an attribute position, it +will result in a runtime error as there is no sensible behavior there. + +We propose to apply these same rules to modifiers as well – sub-expression +invocations will curry the arguments. If values other than (possibly curried) +modifiers are passed to a modifier position, it will result in a runtime error. +Conversely, if a modifier value is passed to content or attribute position, it +will also result in a runtime error. + +## Drawbacks + +> Why should we *not* do this? Please consider the impact on teaching Ember, +on the integration of this feature with other existing and planned features, +on the impact of the API churn on existing apps, etc. + +> There are tradeoffs to choosing any path, please attempt to identify them here. + +## Alternatives + +> What other designs have been considered? What is the impact of not doing this? + +> This section could also include prior art, that is, how other frameworks in the same domain have solved this problem. + +## Unresolved questions + +> Optional, but suggested for first drafts. What parts of the design are still +TBD? From 69f37a58dd5321311575735696e1899c8754e0ac Mon Sep 17 00:00:00 2001 From: Ricardo Mendes Date: Thu, 3 Jan 2019 04:43:04 +0000 Subject: [PATCH 02/24] Update 0000-contextual-helpers.md --- text/0000-contextual-helpers.md | 87 ++++++++++++++++----------------- 1 file changed, 43 insertions(+), 44 deletions(-) diff --git a/text/0000-contextual-helpers.md b/text/0000-contextual-helpers.md index 3adc4a1ed4..3bae9f5be3 100644 --- a/text/0000-contextual-helpers.md +++ b/text/0000-contextual-helpers.md @@ -31,7 +31,7 @@ to create easy-to-use and easy-to-understand DSL-like APIs that could benefit users of all level. For example, the original RFC used form controls as a motivating example. -Without contextual compoments, an addon that provides form-building components +Without contextual components, an addon that provides form-building components might have to expose an API like this: ```hbs @@ -42,7 +42,7 @@ might have to expose an API like this: ``` -As you can see, this is far from ideal for serveral reasons. First, to avoid +As you can see, this is far from ideal for several reasons. First, to avoid collision, the addon author had to prefix all the components. Second, the `@model` argument has to be passed to all the controls that needs it. Finally, in cases where the compoments need to communicate with each other (the form and @@ -113,7 +113,7 @@ helpers and modifiers, like so: ``` -For reference, the `` compoment's template would look something like +For reference, the `` component's template would look something like this: ```hbs @@ -133,16 +133,16 @@ This RFC proposes a complete design for enabling this capability. ### The `helper` and `modifier` helpers -This RFC introduce two new helpers named `helper` and `modifier`, which work +This RFC introduces two new helpers named `helper` and `modifier`, which work similarly to the `component` helper: * When passed a string (e.g. `(helper "foo")`) as the first argument, it will - produce an opaque, internal helper definition" object that can be passed - around and used to invoke the corresponding helper (the `foo` helper in this - case) in Handlebars. + produce an opaque, internal "helper definition" object that can be passed + around and used to invoke the corresponding helper in Handlebars. + The `foo` helper in this case. * When passed an "helper definition" as the first argument, it will produce a - functionally equivialent "helper definition" object. + functionally equivalent "helper definition" object. * In either case, any additional positional and/or named arguments will be stored ("curried") inside the "helper definition" object, such that, when @@ -159,8 +159,8 @@ Some additional details: invoked, the error may not be reported at all. This timing may change between versions, and should not be relied upon. -* Positional arguments are "curried" the same way as the `component` helper - (which matches the behavior of `Function.prototype.bind`). +* Positional arguments are "curried" the same way as the `component` helper. + This matches the behavior of `Function.prototype.bind`. ```hbs {{#let (helper "concat") as |my-concat|}} @@ -178,8 +178,8 @@ Some additional details: {{/let}} ``` -* Named arguments are "curried" the same way as the `component` helper (which - is "last-write-wins", matching the behavior of `Object.assign`). +* Named arguments are curried the same way as the `component` helper. + This matches the "last-write-wins" behavior of `Object.assign`. ```hbs {{#let (helper "hash") as |my-hash|}} @@ -198,10 +198,10 @@ Some additional details: ``` * When a "helper definition" is passed into JavaScipt, the resulting value is - left unspecified (which is why it is described as "opaque"). In particular, + left unspecified, which is why it is described as "opaque". In particular, it is _not_ guarenteed that it will be an invokable JavaScript function. The only guarentee provided is that, when passed back into Handlebars, it will be - functionally equivilant to the original "helper definition". Hanging onto a + functionally equivalent to the original "helper definition". Hanging onto a "helper definition" object in JavaScript may result in unexpected memory leaks, as these objects may "close over" arbitrary template states to allow currying. @@ -222,7 +222,7 @@ any other helpers: {{!-- attribute position --}}
...
- {{!-- curly invokcation, argument position --}} + {{!-- curly invocation, argument position --}} {{MyComponent value=(foo-bar "baz")}} {{!-- angle bracket invokation, argument position --}} @@ -243,7 +243,7 @@ any other helpers: ### Ambigious invocations (invoking without arguments) -When invoking a contextual helper without arguments, the invokation becomes +When invoking a contextual helper without arguments, the invocation becomes ambigious. Consider this: ```hbs @@ -278,7 +278,7 @@ ambigious. Consider this: {{/let}} ``` -In these cases, these are ambigious between passing the _result_ of the helper +In these cases, these are ambiguities between passing the _result_ of the helper (first invoking the helper, then pass along the result), or passing the helper itself (the "helper definition" object, so that it can be invoked or "curried" again on the receiving side). @@ -369,13 +369,13 @@ there are no other possible meaning in that position: ### Deprecation -In today's Ember, "global helpers" (as opposed to "contextual helpers") does +In today's Ember, "global helpers" (as opposed to "contextual helpers") do not always follow the rules laid out above. In particular, they do not behave the same way in arguments and subexpression positions: ```hbs {{#let (helper "concat") as |my-concat|}} - {{!-- curly invokcation, argument position --}} + {{!-- curly invocation, argument position --}} {{!-- this.value is the helper --}} {{MyComponent value=my-concat}} @@ -383,7 +383,7 @@ the same way in arguments and subexpression positions: {{!-- this.value is the undefined --}} {{MyComponent value=concat}} - {{!-- angle bracket invokation, argument position --}} + {{!-- angle bracket invocation, argument position --}} {{!-- @value is the helper --}} @@ -412,7 +412,7 @@ While this is not so different from other programming languages like Java and Ruby which also do not treat functions (methods) as first-class values, it is distinctly different from JavaScript which does. For example, `alert("hello")` is the same as `let a = alert; a("hello");`, whereas in Java and Ruby (and -today's Handlebars), the moral equivilant of the latter would fail with `alert` +today's Handlebars), the moral equivalent of the latter would fail with `alert` being an undefined reference. The goal of this RFC is to iterate the Ember Handlebars programming model @@ -425,12 +425,12 @@ Specifically: ```hbs {{#let (helper "concat") as |my-concat|}} - {{!-- curly invokcation, argument position --}} + {{!-- curly invocation, argument position --}} {{!-- deprecation: use `this.concat` instead --}} {{MyComponent value=concat}} - {{!-- angle bracket invokation, argument position --}} + {{!-- angle bracket invocation, argument position --}} {{!-- deprecation: use `{{(concat)}}` instead --}} @@ -440,18 +440,18 @@ Specifically: {{!-- deprecation: use `this.concat` instead --}} {{yield (hash value=concat)}} - {{!-- depreaction: use `this.concat` instead --}} + {{!-- deprecation: use `this.concat` instead --}} {{#if (eq my-concat concat)}}...{{/if}} {{/let}} ``` Overall, we expect the effect of this deprecation to be quite minimal. For the -cases that triggers a property lookup today, they are already covered in the +cases that trigger a property lookup today, they are already covered in the [Property Lookup Fallback Deprecation RFC](https://github.com/emberjs/rfcs/blob/master/text/0308-deprecate-property-lookup-fallback.md), plus it would already be quite confusing to name an instance variable after a global helper. For the cases where the "global helper" is implicitly invoked without arguments, since helpers are supposed to be pure computations, a helper -that doesn't accept any arguments have very limited utility thus should also be +that doesn't accept any arguments has very limited utility thus should also be quite rare. In addition, another natural fallout of this plan is that it is not be possible @@ -556,17 +556,16 @@ On the high-level, the guiding principles are: 2. Without violating the first principle, for ergonomic reasons, in places where it unambigiously would not make sense for them to behave as values, - they should be _implicitly_ invoked rather than raisng an error or giving + they should be _implicitly_ invoked rather than raising an error or giving nonsensical results. For helpers, the explicit invocation syntax is `(...)`, i.e. `(foo-bar)`, `(foo-bar "baz")`, `(foo-bar baz="bat")`, etc. This is already mandatory in all sub-expression posisitions today. -It follows that, the explicit syntax for invoking a helper and appending its +It follows that the explicit syntax for invoking a helper and appending its result to the DOM would be: - ```hbs
  • No arguments: {{(foo-bar)}}
  • @@ -590,9 +589,9 @@ invocation syntax will continue to work: ``` The last two forms (with arguments) are non-ambigious: they could not possibly -mean anything else other than to invoke `foo-bar` with the given arguments, -therefore, in these cases, the parentheses will be automatically inserted in -parsing time. +mean anything else other than to invoke `foo-bar` with the given arguments. +Therefore, the parentheses will be automatically inserted at +parsing time in these cases. The first form (without arguments) is indeed ambigious – in fact it will be a violation of the first guiding principle if this is interpreted as anything @@ -601,17 +600,17 @@ value referred to by the `foo-bar` identifier (which happens to be a helper) as content into the DOM". Therefore, we propose to rationalize this case differently. -Indeed, the helper is passed as a value here (as opposed to being invoked). +Indeed, the helper is passed as a value here, as opposed to being invoked. However, at runtime, the rendering engine has to decide exactly how to append a particular value, which happens to be a helper here, as content into the DOM. -There are serveral options here, such as rasing an error ("I don't know how to -turn a helper into content"), using JavaScript's default `toString` (resulting -in "[object Object]") or first invoke the helper with no arguments and then -append the _result_ as content. We argue that the first option is too pedantic, -the second option is not useful and therefore the third option is unambigiously -the only reasonable option. This allows all three existing form to work while -still staisfying the guiding principles. +There are several options here: +1. Raising an error ("I don't know how to turn a helper into content"). +2. Using JavaScript's default `toString` (resulting in "[object Object]"). +3. Invoking the helper with no arguments and then appending the _result_ as content. +We argue that the first option is too pedantic, the second option is not useful and +therefore the third option is unambigiously the only reasonable option. +This allows all three existing form to work while still staisfying the guiding principles. This works similarly in attribute positions too. @@ -635,8 +634,8 @@ Implicit invocation forms:
``` -Here, the first form relies on the runtime to invoke the helper before its -added to the DOM, and the last two relies on automatic parentheses insertion +Here, the first form relies on the runtime to invoke the helper before it is +added to the DOM, and the last two rely on automatic parentheses insertion at parse time. For named arguments position, it is slightly different. @@ -661,7 +660,7 @@ For components, the explict invocation syntax is `<...>`, i.e. ``, When a component is "invoked" in sub-expression form, we propose that it should produce a new curried component with the given arguments. That is, if `foo-bar` refers to a component value, then `(foo-bar)`, `(foo-bar "baz")` and -`(foo-bar bat="baz")` has the same semantics as `(component foo-bar)`, +`(foo-bar bat="baz")` have the same semantics as `(component foo-bar)`, `(component foo-bar "baz")` and `(component foo-bar bat="baz")`, respectively. This allows curly invocations to work: @@ -676,7 +675,7 @@ In the first case, it refers to the component by value. Since the value happens to be a component in this case, the rendering engine will invoke it at runtime. The second and the third case relies on automatic parentheses insertion – they desugars into `{{(foo-bar "baz")}}` and `{{(foo-bar baz="bat")}}`, respectively, -which produces anymous curried components, which are also invoked at runtime. +which produces anonymous curried components, which are also invoked at runtime. If a component value, curried or not, is passed to an attribute position, it will result in a runtime error as there is no sensible behavior there. From f999d9554d91709314d78865fca583dd985b908a Mon Sep 17 00:00:00 2001 From: Ricardo Mendes Date: Thu, 3 Jan 2019 04:43:59 +0000 Subject: [PATCH 03/24] Update 0000-contextual-helpers.md --- text/0000-contextual-helpers.md | 1 + 1 file changed, 1 insertion(+) diff --git a/text/0000-contextual-helpers.md b/text/0000-contextual-helpers.md index 3bae9f5be3..77d4166e8c 100644 --- a/text/0000-contextual-helpers.md +++ b/text/0000-contextual-helpers.md @@ -608,6 +608,7 @@ There are several options here: 1. Raising an error ("I don't know how to turn a helper into content"). 2. Using JavaScript's default `toString` (resulting in "[object Object]"). 3. Invoking the helper with no arguments and then appending the _result_ as content. + We argue that the first option is too pedantic, the second option is not useful and therefore the third option is unambigiously the only reasonable option. This allows all three existing form to work while still staisfying the guiding principles. From 9fe5472afd509f86968ff1e9d4be4d90eba7d84d Mon Sep 17 00:00:00 2001 From: Godfrey Chan Date: Sat, 12 Jan 2019 12:16:22 -0800 Subject: [PATCH 04/24] updates --- text/0000-contextual-helpers.md | 303 ++++++++++++++++++++++++++++---- 1 file changed, 268 insertions(+), 35 deletions(-) diff --git a/text/0000-contextual-helpers.md b/text/0000-contextual-helpers.md index 77d4166e8c..0ebaa7e161 100644 --- a/text/0000-contextual-helpers.md +++ b/text/0000-contextual-helpers.md @@ -12,14 +12,38 @@ passed around as first-class values in templates. For example: ```hbs -{{#let (helper "concat" "foo") as |foo|}} - {{#let (helper foo "bar") as |foo-bar|}} - {{foo-bar "baz"}} +{{join "foo" "bar" "baz" seperator=" "}} + +{{!-- ...is functionally equivilant to... --}} + +{{#let (helper "join" seperator=" ") as |join|}} + {{#let (helper join "foo") as |foo|}} + {{#let (helper foo "bar") as |foo-bar|}} + {{foo-bar "baz"}} + {{/let}} {{/let}} {{/let}} ``` -This template will print "foobarbaz". +```hbs + + +{{!-- ...is functionally equivilant to... --}} + +{{#let (modifier "on") as |on|}} + {{#let (modifier on click=(action "submit")) as |on-click|}} + {{#let (modifier on-click mouseenter=(action "highlight")) as |on-click-enter|}} + {{#let (modifier on-click-enter mouseleave=(action "unhighlight")) as |on-click-enter-leave|}} + + {{/let}} + {{/let}} + {{/let}} +{{/let}} +``` ## Motivation @@ -86,7 +110,15 @@ into a single object, but this is not required.) While this is indeed a pretty advanced feature, the users of `SuperForm` do not need to be aware of these implementation details in order to use the addon. This had proved to be a very useful and powerful feature and enabled a number -of popular addons, such as **@kategengler send halp**. +of popular addons, such as +[ember-cli-addon-docs](https://ember-learn.github.io/ember-cli-addon-docs/), +[ember-bootstrap](https://www.ember-bootstrap.com), +[ember-table](https://opensource.addepar.com/ember-table/), +[ember-paper](https://miguelcobain.github.io/ember-paper/), +[ember-power-calendar](https://ember-power-calendar.com), +[ember-accordion](http://khorus.github.io/ember-accordion/), +[emberx-select](https://emberx-select.netlify.com/), +[ember-light-table](https://offirgolan.github.io/ember-light-table/). The original RFC left an important question unanswered – [should this feature be available for helpers, too?](https://github.com/emberjs/rfcs/blob/master/text/0064-contextual-component-lookup.md#unresolved-questions) @@ -137,27 +169,57 @@ This RFC introduces two new helpers named `helper` and `modifier`, which work similarly to the `component` helper: * When passed a string (e.g. `(helper "foo")`) as the first argument, it will - produce an opaque, internal "helper definition" object that can be passed - around and used to invoke the corresponding helper in Handlebars. - The `foo` helper in this case. + produce an opaque, internal "helper definition" or "modifier definition" + object that can be passed around and invoked elsewhere. -* When passed an "helper definition" as the first argument, it will produce a - functionally equivalent "helper definition" object. - -* In either case, any additional positional and/or named arguments will be - stored ("curried") inside the "helper definition" object, such that, when - eventually invoked, these arguments will be passed along to the referenced - helper. +* Any additional positional and/or named arguments will be stored ("curried") + inside the definition object, such that, when invoked, these arguments will + be passed along to the referenced helper or modifier. Some additional details: -* The string will be used to resolve a helper with the same name. If the string - does not correspond to a valid helper, it will result in a runtime error. +* When the first argument passed to the `helper` or `modifier` helper is + `null`, `undefined` or an empty string, it will produce a no-op definition + object. In the case of the `helper` helper, this will produce `undefined` + when invoked, regardless of the arguments that are passed to the invocation. + In the case of the `modifier` helper, it will not perform any operations on + the target element. + +* When the first argument passed to the `helper` or `modifier` helper is a + string, it will be used to resolve a helper or modifier (respectively) with + the same name. If the resolution failed, it will result in a runtime error. However, the timing of this lookup is unspecified. `(helper "not-a-helper")` may result in an immediate error, or it may happen when it is later passed - into the `helper` helper a second time, or when it is invoked. If it is never - invoked, the error may not be reported at all. This timing may change between - versions, and should not be relied upon. + into the `helper` helper a second time, or it may happen when it is invoked. + If it is never invoked, the error may not be reported at all. This timing may + change between releases and should not be relied upon. + +* Some built-in helpers or modifiers may not be resolvable with the `helper` + and `modifier` helpers. For example, `(helper "debugger")` and + `(helper "yield")` will not work, as they are considered _keywords_. + +* Similarly, contextual helpers cannot be named after keywords. For example, + `{{#let ... as |yield|}} {{yield}} {{/let}}` will not work. We propose to + turn these cases into syntax errors. + +* A contextual helper or modifier can be further "curried" by passing them back + into the `helper` or `modifier` helper again, as shown in the example in the + [summary](#Summary) section. This will produce a new definition object. + +* When the first argument passed to the `helper` or `modifier` helper is a + bound value, a new definition object will be produced whenever the value + changes. This will _invalidate_ all downstream invocations. If the previous + value is a [simple helper](https://emberjs.com/api/ember/3.7/functions/@ember%2Fcomponent%2Fhelper/helper), + this has no observable effect and Ember will simply invoke the new helper + value. If the previous value is a [class-based helper](https://emberjs.com/api/ember/3.7/classes/Helper), + or a modifier, the existing instance will be destroyed before the new value + is invoked. On the other hand, if only the curried arguments has changed, the + helper or modifier instances (if any) will remain. + +* An important implication of the teardown semantics is that it is possible for + a modifier to be destroyed while its target element lives on for much longer. + Therefore, it is important to actually teardown any event listeners and + cleanup any associated states in the `destroyModifier` hook. * Positional arguments are "curried" the same way as the `component` helper. This matches the behavior of `Function.prototype.bind`. @@ -197,47 +259,218 @@ Some additional details: {{/let}} ``` -* When a "helper definition" is passed into JavaScipt, the resulting value is - left unspecified, which is why it is described as "opaque". In particular, - it is _not_ guarenteed that it will be an invokable JavaScript function. The - only guarentee provided is that, when passed back into Handlebars, it will be - functionally equivalent to the original "helper definition". Hanging onto a - "helper definition" object in JavaScript may result in unexpected memory - leaks, as these objects may "close over" arbitrary template states to allow - currying. +* When a definition object is passed into JavaScipt (e.g. as an argument to a + JavaScript helper), the resulting value is unspecified (hence "opaque"). In + particular, for helpers, it is _not_ guarenteed that it will be an invokable + JavaScript function. The only guarentee provided is that, when passed back + into Handlebars it will be an invokable value. Hanging onto a definition + object in JavaScript may result in unexpected memory leaks, as these objects + may close over arbitrary template states. ### Invoking contextual helpers -For the most part, invoking a contextual helper is no different from invoking -any other helpers: +Invoking a contextual helper is no different from invoking any other helpers: ```hbs -{{#let (helper "concat" "foo" "bar") as |foo-bar|}} +{{#let (helper "join" "foo" "bar" seperator=" ") as |foo-bar|}} + {{!-- content position --}} + + {{foo-bar}} + {{foo-bar "baz"}} + {{foo-bar seperator=","}} + {{!-- not necessary, but works --}} + + {{helper foo-bar}} + {{helper foo-bar "baz"}} + {{helper foo-bar seperator=","}} + {{!-- attribute position --}} + +
...
+
...
+
...
+ + {{!-- not necessary, but works --}} + +
...
+ +
...
+ +
...
+ {{!-- curly invocation, argument position --}} - {{MyComponent value=(foo-bar "baz")}} + + {{my-component value=(foo-bar)}} + + {{my-component value=(foo-bar "baz")}} + + {{my-component value=(foo-bar seperator=",")}} + + {{!-- these will pass the helper itself into the component, instead of invoking it now --}} + + {{my-component helper=foo-bar}} + + {{my-component helper=(helper foo-bar)}} + + {{my-component helper=(helper foo-bar "baz")}} + + {{my-component helper=(helper foo-bar seperator=",")}} {{!-- angle bracket invokation, argument position --}} + + + + + + {{!-- these will pass the helper itself into the component, instead of invoking it now --}} + + + + + + + + + {{!-- sub-expression positions --}} - {{yield (hash foo-bar=foo-bar)}} - {{#if (eq (foo-bar "baz") "foobarbaz")}}...{{/if}} + {{yield (foo-bar)}} + + {{yield (foo-bar "baz")}} + + {{yield (foo-bar seperator=",")}} + + {{!-- these will yield the helper itself ("contextual helper"), instead of invoking it now --}} + + {{yield foo-bar}} + + {{yield (helper foo-bar)}} + + {{yield (helper foo-bar "baz")}} + + {{yield (helper foo-bar seperator=",")}} + + {{!-- deeply nested sub-expression --}} + + {{#if (eq (concat ">>> " (foo-bar "baz") " <<<") ">>> foo bar baz <<<")}} + This is true. + {{/if}} {{!-- runtime error: not a component --}} {{!-- runtime error: not a modifier --}} -
+
+{{/let}} +``` + +### Invoking contextual modifiers + +Invoking a contextual helper is no different from invoking any other modifiers: + +```hbs +{{#let (modifier "on" click=(action "submit")) as |on-click|}} + + {{!-- HTML elements --}} + + + + + + + + {{!-- not necessary, but works --}} + + + + + + + + {{!-- components --}} + + + + + + + + {{!-- not necessary, but works --}} + + + + + + + + {{!-- these will pass the modifier itself into the component, instead of invoking it now --}} + + + + + + + + + + {{my-component modifier=on-click}} + + {{my-component modifier=(modifier on-click)}} + + {{my-component modifier=(modifier on-click "extra" "args")}} + + {{my-component modifier=(modifier on-click mouseenter=(action "highlight") mouseleave=(action "unhighlight"))}} + + {{!-- these will yield the modifier itself ("contextual modifier"), instead of invoking it now --}} + + {{yield on-click}} + + {{yield (modifier on-click)}} + + {{yield (modifier on-click "extra" "args")}} + + {{yield (modifier on-click mouseenter=(action "highlight") mouseleave=(action "unhighlight"))}} + + {{!-- runtime error: cannot invoke a modifier as a helper --}} + + {{yield (on-click)}} + + {{yield (on-click "extra" "args")}} + + {{yield (on-click mouseenter=(action "highlight") mouseleave=(action "unhighlight"))}} + + {{!-- runtime error: cannot append a modifier --}} + + {{on-click}} + + {{on-click "extra" "args"}} + + {{on-click mouseenter=(action "highlight") mouseleave=(action "unhighlight")}} + + {{!-- runtime error: cannot set an attribute to a modifier --}} + +
+ +
+ +
+ + {{!-- runtime error: not a component --}} + {{/let}} ``` From 4586ffc5ed76f49be118d95fff555adb85835d8c Mon Sep 17 00:00:00 2001 From: Godfrey Chan Date: Mon, 14 Jan 2019 14:54:49 -0800 Subject: [PATCH 05/24] zomg --- text/0000-contextual-helpers.md | 521 ++++++++++++-------------------- 1 file changed, 186 insertions(+), 335 deletions(-) diff --git a/text/0000-contextual-helpers.md b/text/0000-contextual-helpers.md index 0ebaa7e161..5e0d4c0a3d 100644 --- a/text/0000-contextual-helpers.md +++ b/text/0000-contextual-helpers.md @@ -2,21 +2,21 @@ - RFC PR: (leave this empty) - Ember Issue: (leave this empty) -# Contextual Helpers (a.k.a. "first-class helpers") +# Contextual Helpers and Modifiers (a.k.a. "first-class helpers/modifiers") ## Summary -We propose to extend the semantics of Handlebars helpers such that they can be -passed around as first-class values in templates. +We propose to extend the semantics of Handlebars helpers and modifiers such +that they can be passed around as first-class values in templates. For example: ```hbs -{{join "foo" "bar" "baz" seperator=" "}} +{{join-words "foo" "bar" "baz" seperator=" "}} {{!-- ...is functionally equivilant to... --}} -{{#let (helper "join" seperator=" ") as |join|}} +{{#let (helper "join-words" seperator=",") as |join|}} {{#let (helper join "foo") as |foo|}} {{#let (helper foo "bar") as |foo-bar|}} {{foo-bar "baz"}} @@ -141,7 +141,7 @@ helpers and modifiers, like so: {{!-- f.auto-resize is a contextual modifier --}} - + ``` @@ -272,7 +272,7 @@ Some additional details: Invoking a contextual helper is no different from invoking any other helpers: ```hbs -{{#let (helper "join" "foo" "bar" seperator=" ") as |foo-bar|}} +{{#let (helper "join-words" "foo" "bar" seperator=" ") as |foo-bar|}} {{!-- content position --}} @@ -474,228 +474,192 @@ Invoking a contextual helper is no different from invoking any other modifiers: {{/let}} ``` -### Ambigious invocations (invoking without arguments) - -When invoking a contextual helper without arguments, the invocation becomes -ambigious. Consider this: - -```hbs -{{#let (helper "concat" "foo" "bar") as |foo-bar|}} - {{!-- content position --}} - - {{!-- does this render "foobar", or something like "[object Object]" ? --}} - {{foo-bar}} - - {{!-- attribute position --}} - - {{!-- class="foobar", or something like class="[object Object]" ? --}} -
...
- - {{!-- curly invokcation, argument position --}} - - {{!-- is this.value the string "foobar", or the "helper definition" ? --}} - {{MyComponent value=foo-bar}} +### Relationship with globals - {{!-- angle bracket invokation, argument position --}} - - {{!-- is @value the string "foobar", or the "helper definition" ? --}} - - - {{!-- sub-expression positions --}} +Today, Ember apps rely heavily on the global namespace as the main mechanism of +making components, helpers and modifiers available. Ideally, in a world where +"everything is a value", the global and local namespace should behave the same +way. Global components, helpers and modifiers should just be global variables +that are implicitly defined around every templates in the app. - {{!-- are we yielding the helper, or the string "foobar" ? --}} - {{yield (hash foo-bar=foo-bar)}} +In other words, it is as if every template has the following hidden "prelude" +around its content: - {{!-- is this true or false? --}} - {{#if (eq foo-bar "foobar")}}...{{/if}} +```hbs +{{!-- prelude --}} +{{#let (component "input") as |input|}} + {{#let (helper "concat") as |concat|}} + {{#let (modifier "action") as |action|}} + {{!-- ...other global components, helpers and modifiers omitted... --}} + + {{!-- begin template content --}} + Your name: + {{concat this.firstName " " this.lastName}} + + Change it: + {{input value=this.firstName}} + {{input value=this.lastName}} + + + {{!-- end tempalte content ---}} + {{/let}} + {{/let}} {{/let}} ``` -In these cases, these are ambiguities between passing the _result_ of the helper -(first invoking the helper, then pass along the result), or passing the helper -itself (the "helper definition" object, so that it can be invoked or "curried" -again on the receiving side). +While this largely matches how things work today, there are a few notable +differences where globals behave "unexpectedly" in this world. -Since this RFC proposes to treat helpers and modifiers as first-class values, -they should generally be passed through as _values_. This is particularly -important in arguments and sub-expression positions. To invoke the helper, the -explicit `(some-helper)` syntax can be used instead: +First of all, it is not possible to reference a component, helper or modifier +in templates without invoking them today: ```hbs -{{#let (helper "concat" "foo" "bar") as |foo-bar|}} - {{!-- curly invokcation, argument position --}} - - {{!-- the component will receive the "helper definition" --}} - {{MyComponent value=foo-bar}} - - {{!-- the component will receive the string "foobar" --}} - {{MyComponent value=(foo-bar)}} - - {{!-- angle bracket invokation, argument position --}} - - {{!-- @value will be the "helper definition" --}} - - - {{!-- @value will be the string "foobar" --}} - +{{!-- if `join-words` is a global helper, this works as expected --}} +{{!-- this invokes the helper and yield the result --}} - {{!-- sub-expression positions --}} +{{yield (join-words "foo" "bar" seperator=",")}} + ~~~~~~~~~~ - {{!-- yielding the helper --}} - {{yield (hash foo-bar=foo-bar)}} +{{!-- however, in this position, Ember does not "see" the helper --}} +{{!-- this falls back to looking up the `join-words` property on `this` --}} - {{!-- yielding the string "foobar" --}} - {{yield (hash foo-bar=(foo-bar))}} +{{yield join-words}} + ~~~~~~~~~~ - {{!-- false --}} - {{#if (eq foo-bar "foobar")}}...{{/if}} +{{!-- as opposed to a "true" variable/value binding... --}} +{{!-- this yields the helper as a value, as expected --}} - {{!-- true --}} - {{#if (eq (foo-bar) "foobar")}}...{{/if}} +{{#let (helper "join-words") as |join-words|}} + {{yield join-words}} + ~~~~~~~~~~ {{/let}} ``` -However, in the case of content and attribute positions, it would be overly -pendantic to insist on the `{{(some-helper)}}` syntax, as the alternative of -printing `[object Object]` to the DOM is almost certainly not what the -developer had in mind. Therefore, we propose to allow contextual helpers to be -auto-invoked in these positions. +In the long term, we poropose to unify the semantics such that globals will +behave exactly like local bindings (i.e. we should make this second case work). -```hbs -{{#let (helper "concat" "foo" "bar") as |foo-bar|}} - {{!-- content position --}} +However, is not possible in the short term. This is due to the ambiguity +between referencing a global variable and the +[property lookup fallback](https://github.com/emberjs/rfcs/blob/master/text/0308-deprecate-property-lookup-fallback.md) +feature. We propose we simply wait until the property lookup fallback is fully +deprecated and removed, at which point we can reclaim the syntax. - {{!-- "foobar" --}} - {{foo-bar}} - - {{!-- not necessary, but works --}} - {{(foo-bar)}} - - {{!-- attribute position --}} - - {{!-- class="foobar" --}} -
...
- - {{!-- class="foobar" --}} -
...
-{{/let}} -``` +In the mean time, globals can be referenced explicitly using the `component`, +`helper`, and `modifier` helpers. -It should also be noted that modifiers doe not have the same problem, since -there are no other possible meaning in that position: +Another difference is how global helpers can be invoked without arguments in +named arguments position today: ```hbs -{{#let (modifier "foo-bar") as |foo-bar|}} - {{!-- modifier position: not ambigious --}} -
+{{!-- if `pi` is a global helper that returns the value of the constant 𝛑 --}} +{{!-- this invokes the helper and passes the value 3.1415... --}} + + ~~ - {{!-- not necessary, but works --}} -
+{{!-- as opposed to a "true" variable/value binding... --}} - {{!-- undefined behavior: runtime error or [object Object] ? --}} - {{foo-bar}} +{{#let (helper "pi") as |pi|}} + {{!-- this passes the helper into the component --}} + + ~~ - {{!-- undefined behavior: runtime error or [object Object] ? --}} -
+ {{!-- this invokes the helper and passes the value 3.1415... --}} + + ~~ {{/let}} ``` -### Deprecation - -In today's Ember, "global helpers" (as opposed to "contextual helpers") do -not always follow the rules laid out above. In particular, they do not behave -the same way in arguments and subexpression positions: - -```hbs -{{#let (helper "concat") as |my-concat|}} - {{!-- curly invocation, argument position --}} +Notably, this problem only exists in the named arguments position. For content +and attribute positions, it would not makae sense to pass a helper "by value", +so the ambiguity does not exist (so it always invokes). For sub-expression +positions (which includes argument positions for curly invocations), the +parentheses are already mandatory (otherwise it invokes the property fallback). - {{!-- this.value is the helper --}} - {{MyComponent value=my-concat}} +We propose to deprecate invoking global helpers in named argument positions and +require the parentheses instead. This will make room for unifying the global +semantics in the future. - {{!-- this.value is the undefined --}} - {{MyComponent value=concat}} +It is also worth pointing out that, since helpers tend to be pure, helpers +that take no arguments are exceedingly rare. - {{!-- angle bracket invocation, argument position --}} +Finally, the last challenge to the unification is it is entirely possible to +have any combinations of components, helpers and modifiers all with the same +name today. This works, as they currently live in different "namespaces", and +each lookup is contextually scoped to their respective "namespace" depending on +the position where it is invoked: - {{!-- @value is the helper --}} - - - {{!-- @value is an empty string (invoking concat with no arguments) --}} - - - {{!-- sub-expression positions --}} - - {{!-- yielding the helper --}} - {{yield (hash value=my-concat)}} +```hbs +{{!-- foo-bar, the modifier here --}} +
+ ~~~~~~~ - {{!-- yielding undefined --}} - {{yield (hash value=concat)}} +{{!-- foo-bar, the helper here --}} +
+ ~~~~~~~ - {{!-- false: it compares the helper with undefined --}} - {{#if (eq my-concat concat)}}...{{/if}} +{{!-- foo-bar, the helper here --}} +{{#let (foo-bar) as |result|}} + ~~~~~~~ {{/let}} -``` -This illustrates the problem: "global helpers" are not modelled as first-class -values today, they exists in a different "namespace" distinct from the "local -variables" namespace. +{{!-- foo-bar, the component here --}} +{{#foo-bar}}...{{/foo-bar}} + ~~~~~~~ ~~~~~~~ -While this is not so different from other programming languages like Java and -Ruby which also do not treat functions (methods) as first-class values, it is -distinctly different from JavaScript which does. For example, `alert("hello")` -is the same as `let a = alert; a("hello");`, whereas in Java and Ruby (and -today's Handlebars), the moral equivalent of the latter would fail with `alert` -being an undefined reference. +{{!-- prefers foo-bar, the component here --}} +{{!-- if not found, then foo-bar, the helper --}} +{{foo-bar}} + ~~~~~~~ +``` -The goal of this RFC is to iterate the Ember Handlebars programming model -towards a world closer to JavaScript's, where global names exists in the same -namespace as local names. We propose to deprecate all cases where global names -("global helpers", "global modifiers" and "global components") can be observed -to behave differently. +Since this could get pretty confusing, most developers already avoid giving +these unrelated the same names. However, it is certainly possible that they +may happen by accident and go unnoticed (e.g. an addon introducing a global +helper that "conflicts" with a component in the app). -Specifically: +These kind of naming conflicts would not make sense in the value-based world. +Imagine if this is how JavaScript works: -```hbs -{{#let (helper "concat") as |my-concat|}} - {{!-- curly invocation, argument position --}} +```js +class FooBar {} - {{!-- deprecation: use `this.concat` instead --}} - {{MyComponent value=concat}} +function FooBar() {} - {{!-- angle bracket invocation, argument position --}} +const FooBar = 1; - {{!-- deprecation: use `{{(concat)}}` instead --}} - +class FooBarBaz extends FooBar {} +// ~~~~~~ FooBar, the class here - {{!-- sub-expression positions --}} +console.log(FooBar()); +// ~~~~~~ FooBar, the function here - {{!-- deprecation: use `this.concat` instead --}} - {{yield (hash value=concat)}} +console.log(FooBar + 1); +// ~~~~~~ FooBar, the constant here - {{!-- deprecation: use `this.concat` instead --}} - {{#if (eq my-concat concat)}}...{{/if}} -{{/let}} +someObj.FooBar = FooBar; +// ~~~~~~ well, which one is this? ``` -Overall, we expect the effect of this deprecation to be quite minimal. For the -cases that trigger a property lookup today, they are already covered in the -[Property Lookup Fallback Deprecation RFC](https://github.com/emberjs/rfcs/blob/master/text/0308-deprecate-property-lookup-fallback.md), -plus it would already be quite confusing to name an instance variable after a -global helper. For the cases where the "global helper" is implicitly invoked -without arguments, since helpers are supposed to be pure computations, a helper -that doesn't accept any arguments has very limited utility thus should also be -quite rare. +Clearly, this would be unacceptable and is similar to the situation we find +ourselves in here. -In addition, another natural fallout of this plan is that it is not be possible -to have helpers, components or modifiers with the same name, as they ultimately -will share the same namesapce. These conflicts will need to be detected and -deprecated as well. +We propose to issue deprecation warnings whenever we detect these conflicts, +both at build time and at invocation time, while maintaining the same lookup +precedence for the time being. For example, when invoking a component in the +content position, if we see that there is also a helper with the same name, it +should result in a deprecation asking the developer to remain one or the other. -### Local helpers (and modifiers) +Notably, there is such a conflict in Ember today where `action` is both a +helper and a modifier. Instead of deprecating one of them, we propose to use an +internal mechanism to produce a single special value such that it will be +invokable as either a modifier or a helper context. This is different the +"namespace" semantics in there is only one context-independent value in this +special case, i.e. `(helper "action") === (modifier "action")`. + +### Local helpers and modifiers A nice fallout of this plan is that developers will be able to define helpers -specific to a component locally (i.e. in the same JavaScript file): +and modifiers specific to a component locally in the same JavaScript file: ```js // app/components/date-picker.js @@ -714,12 +678,11 @@ export default Component.extend({ ```hbs {{!-- app/templates/components/date-picker.hbs --}} - -{{input value=(this.format-date this.date)}} +{{this.format-date this.date}} ``` In additional to encapsulation and namespacing, this will also enable even more -advanced stateful use cases: +advanced use cases that uses the component's state: ```js // app/components/filtered-each.js @@ -760,181 +723,69 @@ addon authors and advanced developers. For this group of users, we expect this feature to complement and complete the "contextual components" feature. Developers who are already familiar with that feature should feel right at home. We expect to be able to introduce this new -feature at places where we currently teach contextual components. +feature at the point where we currently teach contextual components today. -The second side of this feature is the invoking side. We expect intermediate -developers (and perhaps even beginners) to enounter this mainly through addons -that other developers have written. So long as there is adequate documentation -from the addon authors, we expect that this group of users can be immediately -productive by simply treating these APIs as DSLs (similar to the Router DSL). +The second side of this feature is the invoking side. We expect developers to +enounter this mainly through addons that others have written. So long as there +is adequate documentation from the addon authors, we expect that this group of +users can be immediately productive by simply treating these APIs as DSLs, +similar to the Router DSL. In other words, while this group of developer may not immediately understand how to _author_ these kind of APIs, or what is involved under-the-hood to make it work, the design goal is that it should feel straightforward to _consume_ this style of API. -### Rationalization - -We propose to rationalize the existing and proposed semantics into a coherent -model at a deeper level. This knowledge is necessary for day-to-day use, but -could be helpful for guiding the implementation as well as design of future -feature proposals. - -On the high-level, the guiding principles are: - -1. Components, helpers and modifiers are first-and-foremost values that are - bound to Handlebars identifiers. When referring to these identifiers, they - should consistently behave as inert values unless they are _explicitly_ - invoked. - -2. Without violating the first principle, for ergonomic reasons, in places - where it unambigiously would not make sense for them to behave as values, - they should be _implicitly_ invoked rather than raising an error or giving - nonsensical results. - -For helpers, the explicit invocation syntax is `(...)`, i.e. `(foo-bar)`, -`(foo-bar "baz")`, `(foo-bar baz="bat")`, etc. This is already mandatory in all -sub-expression posisitions today. - -It follows that the explicit syntax for invoking a helper and appending its -result to the DOM would be: - -```hbs -
    -
  • No arguments: {{(foo-bar)}}
  • -
  • Positional argument: {{(foo-bar "baz")}}
  • -
  • Named argument: {{(foo-bar baz="bat")}}
  • -
-``` - -The `{{(...)}}` form results in a syntax error today. For completeness, we -propose to modify the grammar to allow this explicit form. However, it is -neither required nor necessarily encouraged, as it adds a lot of visual noise -to the template. Following the second guiding principle, the implicit helper -invocation syntax will continue to work: - -```hbs -
    -
  • No arguments: {{foo-bar}}
  • -
  • Positional argument: {{foo-bar "baz"}}
  • -
  • Named argument: {{foo-bar baz="bat"}}
  • -
-``` - -The last two forms (with arguments) are non-ambigious: they could not possibly -mean anything else other than to invoke `foo-bar` with the given arguments. -Therefore, the parentheses will be automatically inserted at -parsing time in these cases. - -The first form (without arguments) is indeed ambigious – in fact it will be a -violation of the first guiding principle if this is interpreted as anything -other than referring to the value. To be precise, it has to mean: "append the -value referred to by the `foo-bar` identifier (which happens to be a helper) as -content into the DOM". Therefore, we propose to rationalize this case -differently. - -Indeed, the helper is passed as a value here, as opposed to being invoked. -However, at runtime, the rendering engine has to decide exactly how to append a -particular value, which happens to be a helper here, as content into the DOM. - -There are several options here: -1. Raising an error ("I don't know how to turn a helper into content"). -2. Using JavaScript's default `toString` (resulting in "[object Object]"). -3. Invoking the helper with no arguments and then appending the _result_ as content. - -We argue that the first option is too pedantic, the second option is not useful and -therefore the third option is unambigiously the only reasonable option. -This allows all three existing form to work while still staisfying the guiding principles. - -This works similarly in attribute positions too. - -Explicit invocation forms: - -```hbs -
    -
  • No arguments
  • -
  • Positional argument
  • -
  • Named argument
  • -
-``` - -Implicit invocation forms: - -```hbs -
    -
  • No arguments
  • -
  • Positional argument
  • -
  • Named argument
  • -
-``` - -Here, the first form relies on the runtime to invoke the helper before it is -added to the DOM, and the last two rely on automatic parentheses insertion -at parse time. - -For named arguments position, it is slightly different. - -Explicit invocation forms: - -```hbs -No arguments -Positional argument -Named argument -``` +## Drawbacks -While the parentheses in the last two forms can be unambigiously omitted, the -same is not true about the first form. It is conceivable that there is both the -need to pass helpers as value and the results of their invocations. Therefore, -when there are no arguments, the parentheses are mandatory for invocations to -disambiguate between the two. +This RFC introduces another feature that developers may encounter and have to +learn when consuming addons. However, on the whole, we think this will simplify +things more than adding to the concepts – as it ultimiately try to unify the +behavior of components, helpers and modifiers (and in the future, globals). +This should make things feel more consistent and allow developers to apply +their knowledge consistently across the board. -For components, the explict invocation syntax is `<...>`, i.e. ``, -``, `...`, etc. +In the short term, this feature may amplify some of the mismatches and causes +confusions where the legacy semantics does not perfectly match the new world we +are building. This could be mitigated with helpful deprecation messages. -When a component is "invoked" in sub-expression form, we propose that it should -produce a new curried component with the given arguments. That is, if `foo-bar` -refers to a component value, then `(foo-bar)`, `(foo-bar "baz")` and -`(foo-bar bat="baz")` have the same semantics as `(component foo-bar)`, -`(component foo-bar "baz")` and `(component foo-bar bat="baz")`, respectively. +## Alternatives -This allows curly invocations to work: +As proposed, this RFC relies heavily on context-dependent syntatic positions to +disambiguate between component, helper and modifier invocations. For example, +while they may look similar, the following syntax does not produce the same +result: ```hbs -{{foo-bar}} -{{foo-bar "baz"}} {{foo-bar baz="bat"}} -``` -In the first case, it refers to the component by value. Since the value happens -to be a component in this case, the rendering engine will invoke it at runtime. -The second and the third case relies on automatic parentheses insertion – they -desugars into `{{(foo-bar "baz")}}` and `{{(foo-bar baz="bat")}}`, respectively, -which produces anonymous curried components, which are also invoked at runtime. - -If a component value, curried or not, is passed to an attribute position, it -will result in a runtime error as there is no sensible behavior there. - -We propose to apply these same rules to modifiers as well – sub-expression -invocations will curry the arguments. If values other than (possibly curried) -modifiers are passed to a modifier position, it will result in a runtime error. -Conversely, if a modifier value is passed to content or attribute position, it -will also result in a runtime error. - -## Drawbacks +{{(foo-bar baz="bar")}} +``` -> Why should we *not* do this? Please consider the impact on teaching Ember, -on the integration of this feature with other existing and planned features, -on the impact of the API churn on existing apps, etc. +If `foo-bar` is a helper, either would work. However, if `foo-bar` is a +component, only the first form would work and the second form would result in +a runtime error (trying to invoke a component as helper). -> There are tradeoffs to choosing any path, please attempt to identify them here. +A different design has been considered where the first form is just strictly a +syntatic surgar for the latter and `(...)` invocation is one true primitive +that ties everything together. -## Alternatives +Specifically, when "invoked" with `(...)`, a component or modifier simply +produces a value, which is a definition object with the curried arguments, i.e. +`(...)` is a syntatic surgar for currying using the `helper` and `modifier` +helpers. The `{{...}}` syntax then simply "append" the curried definition +object by first invoking it. -> What other designs have been considered? What is the impact of not doing this? +This design turned out to add more complexities and confusions than the +unification has brought to the table, and so that design was abandoned in favor +of what is proposed here. -> This section could also include prior art, that is, how other frameworks in the same domain have solved this problem. +Another alternative is to keep the global namespace separate from the local +namespace, thus avoiding the need for most deprecations. In practice, we +believe this would result in much more confusion when things do not behave the +way you would expect, but only in some niche corner cases. ## Unresolved questions -> Optional, but suggested for first drafts. What parts of the design are still -TBD? +What are the list of built-in components, helpers and modifiers that should be +accessible from the `component`, `helper` and `modifier` helpers, if any? From b48a4363360eaf373f0ea66f032e27b994368b9c Mon Sep 17 00:00:00 2001 From: Godfrey Chan Date: Mon, 14 Jan 2019 15:03:06 -0800 Subject: [PATCH 06/24] input helper --- text/0000-contextual-helpers.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/text/0000-contextual-helpers.md b/text/0000-contextual-helpers.md index 5e0d4c0a3d..bcc2b22db1 100644 --- a/text/0000-contextual-helpers.md +++ b/text/0000-contextual-helpers.md @@ -787,5 +787,11 @@ way you would expect, but only in some niche corner cases. ## Unresolved questions -What are the list of built-in components, helpers and modifiers that should be -accessible from the `component`, `helper` and `modifier` helpers, if any? +* What are the list of built-in components, helpers and modifiers that should be + accessible from the `component`, `helper` and `modifier` helpers, if any? + +* If we make the `input` helper available as a global variable binding, it would + shadow the HTML tag with the same name (i.e. `` would invoke the + Ember component, or perhaps a runtime error?), making it impossible to invoke + the HTML element. What do we do? Are there other names with the same problem, + and how do we avoid this in general, when all globals becomes a true value? From d56686c9c013c2aade2bf5298d69b173aab8add7 Mon Sep 17 00:00:00 2001 From: Godfrey Chan Date: Mon, 14 Jan 2019 15:03:57 -0800 Subject: [PATCH 07/24] typos --- text/0000-contextual-helpers.md | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/text/0000-contextual-helpers.md b/text/0000-contextual-helpers.md index bcc2b22db1..0398775ff3 100644 --- a/text/0000-contextual-helpers.md +++ b/text/0000-contextual-helpers.md @@ -12,11 +12,11 @@ that they can be passed around as first-class values in templates. For example: ```hbs -{{join-words "foo" "bar" "baz" seperator=" "}} +{{join-words "foo" "bar" "baz" separator=","}} -{{!-- ...is functionally equivilant to... --}} +{{!-- ...is functionally equivalent to... --}} -{{#let (helper "join-words" seperator=",") as |join|}} +{{#let (helper "join-words" separator=",") as |join|}} {{#let (helper join "foo") as |foo|}} {{#let (helper foo "bar") as |foo-bar|}} {{foo-bar "baz"}} @@ -32,7 +32,7 @@ For example: mouseleave=(action "unhighlight") }} /> -{{!-- ...is functionally equivilant to... --}} +{{!-- ...is functionally equivalent to... --}} {{#let (modifier "on") as |on|}} {{#let (modifier on click=(action "submit")) as |on-click|}} @@ -272,7 +272,7 @@ Some additional details: Invoking a contextual helper is no different from invoking any other helpers: ```hbs -{{#let (helper "join-words" "foo" "bar" seperator=" ") as |foo-bar|}} +{{#let (helper "join-words" "foo" "bar" separator=" ") as |foo-bar|}} {{!-- content position --}} @@ -280,7 +280,7 @@ Invoking a contextual helper is no different from invoking any other helpers: {{foo-bar "baz"}} - {{foo-bar seperator=","}} + {{foo-bar separator=","}} {{!-- not necessary, but works --}} @@ -288,7 +288,7 @@ Invoking a contextual helper is no different from invoking any other helpers: {{helper foo-bar "baz"}} - {{helper foo-bar seperator=","}} + {{helper foo-bar separator=","}} {{!-- attribute position --}} @@ -296,7 +296,7 @@ Invoking a contextual helper is no different from invoking any other helpers:
...
-
...
+
...
{{!-- not necessary, but works --}} @@ -304,7 +304,7 @@ Invoking a contextual helper is no different from invoking any other helpers:
...
-
...
+
...
{{!-- curly invocation, argument position --}} @@ -312,7 +312,7 @@ Invoking a contextual helper is no different from invoking any other helpers: {{my-component value=(foo-bar "baz")}} - {{my-component value=(foo-bar seperator=",")}} + {{my-component value=(foo-bar separator=",")}} {{!-- these will pass the helper itself into the component, instead of invoking it now --}} @@ -322,7 +322,7 @@ Invoking a contextual helper is no different from invoking any other helpers: {{my-component helper=(helper foo-bar "baz")}} - {{my-component helper=(helper foo-bar seperator=",")}} + {{my-component helper=(helper foo-bar separator=",")}} {{!-- angle bracket invokation, argument position --}} @@ -330,7 +330,7 @@ Invoking a contextual helper is no different from invoking any other helpers: - + {{!-- these will pass the helper itself into the component, instead of invoking it now --}} @@ -340,7 +340,7 @@ Invoking a contextual helper is no different from invoking any other helpers: - + {{!-- sub-expression positions --}} @@ -348,7 +348,7 @@ Invoking a contextual helper is no different from invoking any other helpers: {{yield (foo-bar "baz")}} - {{yield (foo-bar seperator=",")}} + {{yield (foo-bar separator=",")}} {{!-- these will yield the helper itself ("contextual helper"), instead of invoking it now --}} @@ -358,7 +358,7 @@ Invoking a contextual helper is no different from invoking any other helpers: {{yield (helper foo-bar "baz")}} - {{yield (helper foo-bar seperator=",")}} + {{yield (helper foo-bar separator=",")}} {{!-- deeply nested sub-expression --}} @@ -517,7 +517,7 @@ in templates without invoking them today: {{!-- if `join-words` is a global helper, this works as expected --}} {{!-- this invokes the helper and yield the result --}} -{{yield (join-words "foo" "bar" seperator=",")}} +{{yield (join-words "foo" "bar" separator=",")}} ~~~~~~~~~~ {{!-- however, in this position, Ember does not "see" the helper --}} From ca7c3804188d57af9fe2bf6418201bac5ab2fba6 Mon Sep 17 00:00:00 2001 From: Godfrey Chan Date: Mon, 14 Jan 2019 15:04:55 -0800 Subject: [PATCH 08/24] more typos --- text/0000-contextual-helpers.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text/0000-contextual-helpers.md b/text/0000-contextual-helpers.md index 0398775ff3..404bc3176a 100644 --- a/text/0000-contextual-helpers.md +++ b/text/0000-contextual-helpers.md @@ -69,7 +69,7 @@ might have to expose an API like this: As you can see, this is far from ideal for several reasons. First, to avoid collision, the addon author had to prefix all the components. Second, the `@model` argument has to be passed to all the controls that needs it. Finally, -in cases where the compoments need to communicate with each other (the form and +in cases where the components need to communicate with each other (the form and the submit button in the example), they would have to expose some internal state (the `|f|` block param) that the user would have to manually thread through. Not only does this make the API very verbose, it also breaks encapsulation. @@ -85,7 +85,7 @@ API like this: ``` -Behind the scene, the `` compoment's template would look something +Behind the scene, the `` component's template would look something like this: ```hbs From 2d067c9a74c19862b834fe01aed8a3189235421e Mon Sep 17 00:00:00 2001 From: Godfrey Chan Date: Mon, 14 Jan 2019 15:10:03 -0800 Subject: [PATCH 09/24] teaching --- text/0000-contextual-helpers.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/text/0000-contextual-helpers.md b/text/0000-contextual-helpers.md index 404bc3176a..1c0a672bf6 100644 --- a/text/0000-contextual-helpers.md +++ b/text/0000-contextual-helpers.md @@ -725,6 +725,12 @@ For this group of users, we expect this feature to complement and complete the feature should feel right at home. We expect to be able to introduce this new feature at the point where we currently teach contextual components today. +In the long term, the unifications proposed in this RFC should make these +concepts easier to teach for this group of developers, as components, helpers +and modifiers, whether global or contextual, will all behave uniformly. The +value-based semantics also better matches JavaScript which they are probably +already familiar with. + The second side of this feature is the invoking side. We expect developers to enounter this mainly through addons that others have written. So long as there is adequate documentation from the addon authors, we expect that this group of From 63cd7da2836b3c8f9a90a8fead5d2f5c491a1e32 Mon Sep 17 00:00:00 2001 From: Godfrey Chan Date: Mon, 14 Jan 2019 15:25:16 -0800 Subject: [PATCH 10/24] update example for consistency --- text/0000-contextual-helpers.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/text/0000-contextual-helpers.md b/text/0000-contextual-helpers.md index 1c0a672bf6..e0911d41c1 100644 --- a/text/0000-contextual-helpers.md +++ b/text/0000-contextual-helpers.md @@ -225,14 +225,14 @@ Some additional details: This matches the behavior of `Function.prototype.bind`. ```hbs - {{#let (helper "concat") as |my-concat|}} - {{my-concat "foo" "bar" "baz"}} {{!-- "foobarbaz" --}} + {{#let (helper "join-words" separator=",") as |join|}} + {{join "foo" "bar" "baz"}} {{!-- "foo,bar,baz" --}} - {{#let (helper concat "foo") as |foo|}} - {{foo "bar" "baz"}} {{!-- "foobarbaz" --}} + {{#let (helper join "foo") as |foo|}} + {{foo "bar" "baz"}} {{!-- "foo,bar,baz" --}} {{#let (helper foo "bar") as |foo-bar|}} - {{foo-bar "baz"}} {{!-- "foobarbaz" --}} + {{foo-bar "baz"}} {{!-- "foo,bar,baz" --}} {{/let}} {{/let}} @@ -244,14 +244,14 @@ Some additional details: This matches the "last-write-wins" behavior of `Object.assign`. ```hbs - {{#let (helper "hash") as |my-hash|}} - {{my-hash value="foo"}} {{!-- hash with value="foo" --}} + {{#let (helper "join-words" "foo" "bar" "baz") as |join|}} + {{join separator=","}} {{!-- foo,bar,baz --}} - {{#let (helper my-hash value="foo") as |foo|}} - {{foo value="bar"}} {{!-- hash with value="bar" --}} + {{#let (helper join separator=",") as |comma|}} + {{comma separator=" "}} {{!-- foo bar baz --}} - {{#let (helper foo value="bar") as |bar|}} - {{bar value="baz"}} {{!-- hash with value="baz" --}} + {{#let (helper comma separator=" ") as |space|}} + {{space separator="-"}} {{!-- foo-bar-baz --}} {{/let}} {{/let}} From 9a33939a57d53f4d50aec8e3d1b44f8a08f45f10 Mon Sep 17 00:00:00 2001 From: Godfrey Chan Date: Wed, 16 Jan 2019 11:33:34 -0800 Subject: [PATCH 11/24] Reference RFC #208 --- text/0000-contextual-helpers.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/text/0000-contextual-helpers.md b/text/0000-contextual-helpers.md index e0911d41c1..9f3421fd29 100644 --- a/text/0000-contextual-helpers.md +++ b/text/0000-contextual-helpers.md @@ -757,6 +757,14 @@ are building. This could be mitigated with helpful deprecation messages. ## Alternatives +[RFC #208](https://github.com/emberjs/rfcs/pull/208) has previously explored +the same design space. It solves the same fundamental problems, but proposes +two seperate helpers resolution/currying and invocation. This is largely due to +limitations and ambiguities in Handlebars. This RFC attempts to remove the need +of a separate invocation helper by resolving the ambiguities and integrating +more tightly with Handlebars. If accepted, this RFC will supersede the design +proposed in RFC #208. + As proposed, this RFC relies heavily on context-dependent syntatic positions to disambiguate between component, helper and modifier invocations. For example, while they may look similar, the following syntax does not produce the same From 3590506b450e9df2b088ccc779b7737c257cbfc7 Mon Sep 17 00:00:00 2001 From: Godfrey Chan Date: Wed, 16 Jan 2019 11:34:17 -0800 Subject: [PATCH 12/24] Rename to match RFC number --- text/{0000-contextual-helpers.md => 0432-contextual-helpers.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename text/{0000-contextual-helpers.md => 0432-contextual-helpers.md} (100%) diff --git a/text/0000-contextual-helpers.md b/text/0432-contextual-helpers.md similarity index 100% rename from text/0000-contextual-helpers.md rename to text/0432-contextual-helpers.md From 37e138fc74640de69ee0ee4291e4227c87b30029 Mon Sep 17 00:00:00 2001 From: Godfrey Chan Date: Wed, 16 Jan 2019 11:35:42 -0800 Subject: [PATCH 13/24] Add RFC link --- text/0432-contextual-helpers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0432-contextual-helpers.md b/text/0432-contextual-helpers.md index 9f3421fd29..43813c247d 100644 --- a/text/0432-contextual-helpers.md +++ b/text/0432-contextual-helpers.md @@ -1,5 +1,5 @@ - Start Date: 2018-12-17 -- RFC PR: (leave this empty) +- RFC PR: https://github.com/emberjs/rfcs/pull/432 - Ember Issue: (leave this empty) # Contextual Helpers and Modifiers (a.k.a. "first-class helpers/modifiers") From f67bcdbbbe46caf2e7e0ecb09a05e9177645c6a7 Mon Sep 17 00:00:00 2001 From: Robert Jackson Date: Wed, 16 Jan 2019 17:10:31 -0800 Subject: [PATCH 14/24] Update text/0432-contextual-helpers.md Co-Authored-By: chancancode --- text/0432-contextual-helpers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0432-contextual-helpers.md b/text/0432-contextual-helpers.md index 43813c247d..e7a0bf3ad9 100644 --- a/text/0432-contextual-helpers.md +++ b/text/0432-contextual-helpers.md @@ -781,7 +781,7 @@ component, only the first form would work and the second form would result in a runtime error (trying to invoke a component as helper). A different design has been considered where the first form is just strictly a -syntatic surgar for the latter and `(...)` invocation is one true primitive +syntactic sugar for the latter and `(...)` invocation is one true primitive that ties everything together. Specifically, when "invoked" with `(...)`, a component or modifier simply From 4cc4cd76695660acbe1629f9d0b526d68209067b Mon Sep 17 00:00:00 2001 From: Robert Jackson Date: Wed, 16 Jan 2019 17:10:39 -0800 Subject: [PATCH 15/24] Update text/0432-contextual-helpers.md Co-Authored-By: chancancode --- text/0432-contextual-helpers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0432-contextual-helpers.md b/text/0432-contextual-helpers.md index e7a0bf3ad9..d800f557e4 100644 --- a/text/0432-contextual-helpers.md +++ b/text/0432-contextual-helpers.md @@ -653,7 +653,7 @@ Notably, there is such a conflict in Ember today where `action` is both a helper and a modifier. Instead of deprecating one of them, we propose to use an internal mechanism to produce a single special value such that it will be invokable as either a modifier or a helper context. This is different the -"namespace" semantics in there is only one context-independent value in this +"namespace" semantics in that there is only one context-independent value in this special case, i.e. `(helper "action") === (modifier "action")`. ### Local helpers and modifiers From f14712319fccca6670ff9dc9e9d4c2b3e3cd08aa Mon Sep 17 00:00:00 2001 From: Robert Jackson Date: Wed, 16 Jan 2019 17:10:47 -0800 Subject: [PATCH 16/24] Update text/0432-contextual-helpers.md Co-Authored-By: chancancode --- text/0432-contextual-helpers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0432-contextual-helpers.md b/text/0432-contextual-helpers.md index d800f557e4..a39f066158 100644 --- a/text/0432-contextual-helpers.md +++ b/text/0432-contextual-helpers.md @@ -652,7 +652,7 @@ should result in a deprecation asking the developer to remain one or the other. Notably, there is such a conflict in Ember today where `action` is both a helper and a modifier. Instead of deprecating one of them, we propose to use an internal mechanism to produce a single special value such that it will be -invokable as either a modifier or a helper context. This is different the +invokable as either a modifier or a helper context. This is different than "namespace" semantics in that there is only one context-independent value in this special case, i.e. `(helper "action") === (modifier "action")`. From 47a5adcbd81a5ee6e97e52f13d5903dd86cd4574 Mon Sep 17 00:00:00 2001 From: Robert Jackson Date: Wed, 16 Jan 2019 17:12:03 -0800 Subject: [PATCH 17/24] Update text/0432-contextual-helpers.md Co-Authored-By: chancancode --- text/0432-contextual-helpers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0432-contextual-helpers.md b/text/0432-contextual-helpers.md index a39f066158..29ad1e4c88 100644 --- a/text/0432-contextual-helpers.md +++ b/text/0432-contextual-helpers.md @@ -570,7 +570,7 @@ named arguments position today: ``` Notably, this problem only exists in the named arguments position. For content -and attribute positions, it would not makae sense to pass a helper "by value", +and attribute positions, it would not make sense to pass a helper "by value", so the ambiguity does not exist (so it always invokes). For sub-expression positions (which includes argument positions for curly invocations), the parentheses are already mandatory (otherwise it invokes the property fallback). From dab15b1315e04884f7aecffbdf2f1c236e53d117 Mon Sep 17 00:00:00 2001 From: Robert Jackson Date: Wed, 16 Jan 2019 17:17:44 -0800 Subject: [PATCH 18/24] Update text/0432-contextual-helpers.md Co-Authored-By: chancancode --- text/0432-contextual-helpers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0432-contextual-helpers.md b/text/0432-contextual-helpers.md index 29ad1e4c88..9a838c0625 100644 --- a/text/0432-contextual-helpers.md +++ b/text/0432-contextual-helpers.md @@ -535,7 +535,7 @@ in templates without invoking them today: {{/let}} ``` -In the long term, we poropose to unify the semantics such that globals will +In the long term, we propose to unify the semantics such that globals will behave exactly like local bindings (i.e. we should make this second case work). However, is not possible in the short term. This is due to the ambiguity From aafa23b7ea746af7dfa99599ce88d6016efcadf7 Mon Sep 17 00:00:00 2001 From: Godfrey Chan Date: Thu, 17 Jan 2019 14:46:58 -0800 Subject: [PATCH 19/24] clarify --- text/0432-contextual-helpers.md | 84 +++++++++++++++++++++++---------- 1 file changed, 58 insertions(+), 26 deletions(-) diff --git a/text/0432-contextual-helpers.md b/text/0432-contextual-helpers.md index 9a838c0625..a9d2be92a9 100644 --- a/text/0432-contextual-helpers.md +++ b/text/0432-contextual-helpers.md @@ -172,9 +172,10 @@ similarly to the `component` helper: produce an opaque, internal "helper definition" or "modifier definition" object that can be passed around and invoked elsewhere. -* Any additional positional and/or named arguments will be stored ("curried") - inside the definition object, such that, when invoked, these arguments will - be passed along to the referenced helper or modifier. +* Any additional positional and/or named arguments (a.k.a. params and hash) + will be stored ("curried") inside the definition object, such that, when + invoked, these arguments will be passed along to the referenced helper or + modifier. Some additional details: @@ -548,7 +549,7 @@ In the mean time, globals can be referenced explicitly using the `component`, `helper`, and `modifier` helpers. Another difference is how global helpers can be invoked without arguments in -named arguments position today: +named arguments position for angle bracket invocations: ```hbs {{!-- if `pi` is a global helper that returns the value of the constant 𝛑 --}} @@ -569,15 +570,16 @@ named arguments position today: {{/let}} ``` -Notably, this problem only exists in the named arguments position. For content -and attribute positions, it would not make sense to pass a helper "by value", -so the ambiguity does not exist (so it always invokes). For sub-expression -positions (which includes argument positions for curly invocations), the -parentheses are already mandatory (otherwise it invokes the property fallback). +Notably, this problem only exists in the named arguments position and only in +angle bracket invocations. For content and attribute positions, it would not +make sense to pass a helper "by value", so the ambiguity does not exist (so it +always invokes). For sub-expression positions (which includes argument +positions for curly invocations), the parentheses are already mandatory +(otherwise it invokes the property fallback). -We propose to deprecate invoking global helpers in named argument positions and -require the parentheses instead. This will make room for unifying the global -semantics in the future. +We propose to deprecate auto-invoking global helpers with no arguments in named +argument positions for angle bracket invocations and require the parentheses +instead. This will make room for unifying the global semantics in the future. It is also worth pointing out that, since helpers tend to be pure, helpers that take no arguments are exceedingly rare. @@ -713,12 +715,26 @@ export default Component.extend({ ## How we teach this -There are two sides to this feature. +There are two sides to this feature – the consumption side and the authoring +side. -First, there is the `helper` and `modifier` helpers that produces the "curried -values". As with the `component` helper and other "higher-order functions" in -JavaScript, this is a somewhat advanced concept that is mainly targeted at -addon authors and advanced developers. +The consumption side refers to learning how the contextual helper and modifier +values can be used (invoked). We expect developers to enounter this mainly +through addons that others have written. So long as there is adequate +documentation from the addon authors, we expect that this group of users can be +immediately productive by simply treating these APIs as DSLs, similar to the +Router DSL. + +In other words, while this group of developer may not immediately understand +how to _author_ these kind of APIs, or what is involved under-the-hood to make +it work, the design goal is that it should feel straightforward to _consume_ +this style of API. + +The authoring side refers to using the `helper` and `modifier` helpers, and +more importantly, the advanced composition patterns that motivated their +existence in the first place As with the `component` helper and other +"higher-order functions" in JavaScript, this is a somewhat advanced topic that +is mainly targeted at addon authors and advanced developers. For this group of users, we expect this feature to complement and complete the "contextual components" feature. Developers who are already familiar with that @@ -731,16 +747,32 @@ and modifiers, whether global or contextual, will all behave uniformly. The value-based semantics also better matches JavaScript which they are probably already familiar with. -The second side of this feature is the invoking side. We expect developers to -enounter this mainly through addons that others have written. So long as there -is adequate documentation from the addon authors, we expect that this group of -users can be immediately productive by simply treating these APIs as DSLs, -similar to the Router DSL. +The official documentations should be updated to include this feature: -In other words, while this group of developer may not immediately understand -how to _author_ these kind of APIs, or what is involved under-the-hood to make -it work, the design goal is that it should feel straightforward to _consume_ -this style of API. +* The new `helper` and `modifier` helpers need be be added to the API docs. + We should consider cross-linking between the `helper`, `modifier` and + `component` helpers since they solve a similar problem. + +* The guide should be updated to teach this feature as well. We recommend + teaching the two sides of the feature separately, and prioritize the + consumption side, as that is what beginners are likely to encounter first. + + For example, when teaching component invocations, there can be a section that + mentions: + + > Sometimes, components may be yielded to you as a block param. These are + called contextual components, and they can be invoked just like any other + components you have encountered so far. + > + > ...examples... + > + > To learn how to do this yourself, skip ahead to the "Composition Patterns" + > section (link). + +* For the authoring side, we recommend teaching the helper, modifier and + component version of the feature in a single place (such as a "Composition + Patterns" section), cross-linked from their respective sections, rather than + repeating it three times. ## Drawbacks From 8427ebdcf6b39364940d0132d16facef0627234d Mon Sep 17 00:00:00 2001 From: Godfrey Chan Date: Fri, 18 Jan 2019 17:19:52 -0800 Subject: [PATCH 20/24] fix some code errors --- text/0432-contextual-helpers.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/text/0432-contextual-helpers.md b/text/0432-contextual-helpers.md index a9d2be92a9..1128fe0e37 100644 --- a/text/0432-contextual-helpers.md +++ b/text/0432-contextual-helpers.md @@ -91,15 +91,15 @@ like this: ```hbs
{{yield (hash - Input=(component "super-input" form={{this}} model={{this.model}}) - Textarea=(component "super-textarea" form={{this}} model={{this.model}}) - Submit=(component "super-submit" form={{this}}) + Input=(component "super-input" form=this model=this.model) + Textarea=(component "super-textarea" form=this model=this.model) + Submit=(component "super-submit" form=this model=this.model) )}}
``` Here, the `component` helper looked up the components by name (first argument) -and packaged up ("curried") any additional arguments (`form={{this}}`) into an +and packaged up ("curried") any additional arguments (`form=this`) into an internal object (known as a "component definition" in the Glimmer VM). This object can then be passed around like any other values and invoked at a later time. @@ -566,7 +566,7 @@ named arguments position for angle bracket invocations: {{!-- this invokes the helper and passes the value 3.1415... --}} - ~~ + ~~~~ {{/let}} ``` From e8c47691227f9444ca094720dbdbde86c48bafce Mon Sep 17 00:00:00 2001 From: Godfrey Chan Date: Thu, 24 Jan 2019 10:52:42 -0800 Subject: [PATCH 21/24] resolve questions --- text/0432-contextual-helpers.md | 57 ++++++++++++++++++++++++++------- 1 file changed, 45 insertions(+), 12 deletions(-) diff --git a/text/0432-contextual-helpers.md b/text/0432-contextual-helpers.md index 1128fe0e37..60e902964f 100644 --- a/text/0432-contextual-helpers.md +++ b/text/0432-contextual-helpers.md @@ -197,11 +197,16 @@ Some additional details: * Some built-in helpers or modifiers may not be resolvable with the `helper` and `modifier` helpers. For example, `(helper "debugger")` and - `(helper "yield")` will not work, as they are considered _keywords_. + `(helper "yield")` will not work, as they are considered _keywords_. For + implementation simplicity, we propose to forbid resolving built-in helpers, + components and modifiers this way across the board (i.e. a runtime error). + We acknowledge that there are good use cases for this feature such as + currying the `array` and `hash` helpers, and will consider enabling them in + the future on a case-by-case basis. -* Similarly, contextual helpers cannot be named after keywords. For example, - `{{#let ... as |yield|}} {{yield}} {{/let}}` will not work. We propose to - turn these cases into syntax errors. +* Similarly, contextual helpers cannot be named after certain keywords. For + example, `{{#let ... as |yield|}} {{yield}} {{/let}}` will not work. We + propose to turn these cases into syntax errors. * A contextual helper or modifier can be further "curried" by passing them back into the `helper` or `modifier` helper again, as shown in the example in the @@ -658,6 +663,41 @@ invokable as either a modifier or a helper context. This is different than "namespace" semantics in that there is only one context-independent value in this special case, i.e. `(helper "action") === (modifier "action")`. +We also acknowledge that, so long as there are _implicit_ globals, we may never +be able to truly unify global bindings with local ones, as implicit global +bindings have a high risk of conflicting with HTML elements. Consider the +built-in `input` helper, or an in-app `style` helper. If these were implicitly +turned into global identifiers, they would conflict with the HTML elements with +the same name: + +```hbs + + ~~~~~ now refers to the global `input` identifier? + + + ~~~~~ now refers to the global `style` identifier? +``` + +While the problem exists for local bindings also, it was already addressed in +[RFC #311](https://github.com/emberjs/rfcs/pull/311). With local bindings, this +problem is fairly noticible and understandable since the conflict is introduced +nearby. The solution is also fairly simple – just rename the local variable to +avoid the conflict. With proper linting, this could be quite easily avoided +altogether. + +With _implicit_ global bindings, this problem is might more difficult to spot +and reason about. There is also no quick way out, other than to rename the +global component, helper or modifer which could be difficult or not an option +at all for addon authors trying to maintain compatibility. To truly resolve +this conflict, we would have to eliminate implicit globals, which is out of +scope for this RFC. This also wouldn't be a problem until all the proposed +deprecations are implemented and removed, which would be quite some time. + +We _speculate_ that when all the of that is said and done, we would have an +alternative resolution mechanism ("template imports") that does not have this +problem. Alternatively, we could exclude the angle bracket invocation position +from being able to "see" implicit global identifiers. + ### Local helpers and modifiers A nice fallout of this plan is that developers will be able to define helpers @@ -833,11 +873,4 @@ way you would expect, but only in some niche corner cases. ## Unresolved questions -* What are the list of built-in components, helpers and modifiers that should be - accessible from the `component`, `helper` and `modifier` helpers, if any? - -* If we make the `input` helper available as a global variable binding, it would - shadow the HTML tag with the same name (i.e. `` would invoke the - Ember component, or perhaps a runtime error?), making it impossible to invoke - the HTML element. What do we do? Are there other names with the same problem, - and how do we avoid this in general, when all globals becomes a true value? +None From fe8a0d9466bc956bd6c3ca99f92f2d70a21a1283 Mon Sep 17 00:00:00 2001 From: Godfrey Chan Date: Thu, 24 Jan 2019 15:09:15 -0800 Subject: [PATCH 22/24] typo --- text/0432-contextual-helpers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0432-contextual-helpers.md b/text/0432-contextual-helpers.md index 60e902964f..cf05c08a30 100644 --- a/text/0432-contextual-helpers.md +++ b/text/0432-contextual-helpers.md @@ -685,7 +685,7 @@ nearby. The solution is also fairly simple – just rename the local variable to avoid the conflict. With proper linting, this could be quite easily avoided altogether. -With _implicit_ global bindings, this problem is might more difficult to spot +With _implicit_ global bindings, this problem is much more difficult to spot and reason about. There is also no quick way out, other than to rename the global component, helper or modifer which could be difficult or not an option at all for addon authors trying to maintain compatibility. To truly resolve From 0a1967c23e448a73db1733dc899143d9fbef43e9 Mon Sep 17 00:00:00 2001 From: Godfrey Chan Date: Thu, 24 Jan 2019 15:36:28 -0800 Subject: [PATCH 23/24] fix syntax highlight --- text/0432-contextual-helpers.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/text/0432-contextual-helpers.md b/text/0432-contextual-helpers.md index cf05c08a30..57e74bc736 100644 --- a/text/0432-contextual-helpers.md +++ b/text/0432-contextual-helpers.md @@ -666,7 +666,7 @@ special case, i.e. `(helper "action") === (modifier "action")`. We also acknowledge that, so long as there are _implicit_ globals, we may never be able to truly unify global bindings with local ones, as implicit global bindings have a high risk of conflicting with HTML elements. Consider the -built-in `input` helper, or an in-app `style` helper. If these were implicitly +built-in `input` helper, or an in-app `main` helper. If these were implicitly turned into global identifiers, they would conflict with the HTML elements with the same name: @@ -674,8 +674,8 @@ the same name: ~~~~~ now refers to the global `input` identifier? - - ~~~~~ now refers to the global `style` identifier? +
...
+ ~~~~ now refers to the global `main` identifier? ``` While the problem exists for local bindings also, it was already addressed in From 2574f0869066f0ec6c269b9dad02cc26d3537938 Mon Sep 17 00:00:00 2001 From: Godfrey Chan Date: Fri, 25 Jan 2019 10:38:04 -0800 Subject: [PATCH 24/24] clarify local components --- text/0432-contextual-helpers.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/text/0432-contextual-helpers.md b/text/0432-contextual-helpers.md index 57e74bc736..3e3f0bbc60 100644 --- a/text/0432-contextual-helpers.md +++ b/text/0432-contextual-helpers.md @@ -753,6 +753,18 @@ export default Component.extend({ {{/each}} ``` +This feature would work with element modifiers as well. + +Ideally, this should also work with components. However, currently there are +two pieces to a component – a template and a JavaScript class, either could be +optional. This poses a challenge to invoking components this way – without +going through the component helper, there is no easy way to import or package +a component into a single value. This is a solvable problem, but to design a +solution for that would be out of scope for this RFC. For the time being, the +only way to get a handle of a "component defition value" would be through the +component helper. Attempting to "invoke" just the component template or class +this way will result in a runtime error. + ## How we teach this There are two sides to this feature – the consumption side and the authoring