Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Helper listing (dashless helpers) #58

Merged
merged 1 commit into from
Jun 13, 2015
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
166 changes: 166 additions & 0 deletions active/0000-helper-listing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
- Start Date: 2015-05-24
- RFC PR: (leave this empty)
- Ember Issue: (leave this empty)

# Summary

This RFC outlines a new strategy for the registration of helpers in Ember 1.13.
In previous versions of Ember, helper lookup was a two-phase process of:

* Look in a whitelist of registered helpers. If in the whitelist, resolve that
path in the container.
* If the path has a dash, try to resolve it in the container
* If the container does not have a factory for this path, treat the path as a
bound value.

This logic runs for every `{{somePath}}` in an Ember application.

This proposal attempts to simplify and unify that logic in a a single pass
against a whitelist, thus removing the special behavior of dashed paths.
Additionally, it attempts to design a solution that removes the current
`registerHelper` ceremony for undashed helpers.

# Motivation

In [RFC #53](https://github.com/emberjs/rfcs/pull/53) a new API for helpers is
outlined. This RFC presumes helpers will continue to have the naming
requirement of including a dash character.

The dash requirement for helpers exists for two reasons:

* For every `{{path}}` in an Ember application, it must be decided if that path
is a bound value, component, or helper. Component and helper lookup (the
discovery of a class or template) is lazy in Ember, thus for every `{{path}}`
a lookup for that string in the container is required. Container lookups
(the first time) are fairly slow, and performing this lookup for every
path may significantly impact initial render time. Thus, helpers are either
added to a whitelist (with `registerHelper`) or require a way to differentiate
themselves from the majority of data-binding cases (the dash).
* In Ember 1.x, components were treated as helpers for certain code paths. This
made the dash requirement for components a natural extension to helpers.

The Glimmer engine has removed some of these concerns and limitations.

Addon authors and app authors have both felt a need for non-dashed helper
names, for example `{{t 'some-string-to-translate'}}`. New developers to Ember
often find the dash requirement arbitrary and the `registerHelper` work around
difficult to understand and use.

For the new helper API to provide feature parity with APIs available to addon
authors in 1.12, a path to dashless helpers must be present in 1.13.

Given that a solution exists that addresses the performance concern, dropping
the dash requirement would resolve a significant amount of developer pain and
confusion.

# Detailed design

At application boot, all known helper items (according to the resolver) are
iterated and added to a `helper-listing` service. This service is merely a
Set object with the names of all helpers.

When handling a `{{path}}`, the `helper-listing` service is consulted for the
presence of that `path`. If it is present, the path is looked up
on the container as a helper and the helper is used. Dashed paths are treated
no differently than any other path (for helpers).

### Boot time discovery

To discover what paths may be helpers in Ember-CLI, the module names are
iterated. For example:

```
not helper: app/components/foo-bar/component
not helper: app/controllers/foo-bar
not helper: app/foo-bar/route
helper "t": app/t/helper
helper "t": app/helpers/t
helper "foo-bar": app/helpers/foo-bar
helper "foo/bar": app/helpers/foo/bar
```

In a globals-mode application, The app namespace is iterated:

```
not helper: App.FooBarComponent
not helper: App.FooBarController
not helper: App.FooBarRoute
helper "t": App.THelper
helper "foo-bar": App.FooBarHelper <- should dasherize
```

In both cases **the resolver is responsible for providing a list of modules
by type**. The proposed API is `eachOfType`, here with Ember-CLI as an example:

```js
// Given helperListing as a Set:
resolver.eachOfType(‘helper’, function(parsedName, item) {
helperListing.add(parsedName.fullName);
})
```

In Ember-CLI, the `app/` tree of an addon is merged with the app tree of an
application. This means for a helper like `t` to be discovered, nothing besides
adding it to `app/helpers/t.js` must be done.

In 1.13, this will impact existing apps by discovering all helpers regardless
of if `registerHelper` has been called. This is a small behavior change that
should match intent, and will not impact sanely written apps.

Note that only the path of the helper is added to the listing. During discovery,
the helper is not looked up from the container, instead lookup still occurs
at render time.

The helper listing is intended to be a private service in Ember, and will be
registered at `services:-helper-listing`. If the discovery semantics described
here are not sufficient for some edge-cases, wrapping this service in a
public API on application instances may be required.

### Render-time lookup and use

Let us consider how a path is rendered. For example:

```hbs
{{date}}
```

* The `service:-helper-listing` service is fetched
* The path `date` is checked for on the listing: `helperListing.has(path)`
* If the path is not in the listing, `date` is treated like a bound value
* If the path is in the listing, the helper is looked up from Ember's
container as `helper:date`
* depending on the instance returned from the factory (a helper, shorthand
helper, or legacy `Ember.Handlebars` or `Ember.HTMLBars._registerHelper`
helper) the proper invocation for that helper is executed

Every rendered path will hit the `helper-listing` service, but the check
against a well-implemented Set should be inexpensive.

# Drawbacks

Removing the dash requirement will likely result in a larger number of naming
conflicts between addons and apps than has existed before now. In general,
encouraging verbose helper names may mitigate this concern. Long term, there
have been several discussions to date about how to implement namespaces in
Ember templates and for Ember engines.

That the helper listing is eagerly discovered at application boot time may
impact the design of Ember engines and lazy-loading parts of an app. The
discovery cache may need to be flushed and re-generated, however this limitation
already exists for the container lookup itself (which caches failures).

That the helper listing is not based on the container means helpers registered,
but not added to the listing because of non-standard naming, may need to
manually register against the private helper listing API.

# Alternatives

Instead of a new across-the-board solution, Ember could continue to use a
`registerHelper` pattern very similar to what exists today. This would
perpetuate the existing pain, but would perhaps be more similar to what devs
already know.

# Unresolved questions

The exact timing of helper discovery in Ember-CLI and globals mode has not been
decided.