From a2d7b16f564f67afb530453e6637eead78c15c1c Mon Sep 17 00:00:00 2001 From: Godfrey Chan Date: Fri, 1 Feb 2019 12:06:04 -0800 Subject: [PATCH 1/9] all my secrets --- text/0000-handlebars-strict-mode.md | 465 ++++++++++++++++++++++++++++ text/0000-template-imports.md | 87 ++++++ 2 files changed, 552 insertions(+) create mode 100644 text/0000-handlebars-strict-mode.md create mode 100644 text/0000-template-imports.md diff --git a/text/0000-handlebars-strict-mode.md b/text/0000-handlebars-strict-mode.md new file mode 100644 index 0000000000..eb50bc61e9 --- /dev/null +++ b/text/0000-handlebars-strict-mode.md @@ -0,0 +1,465 @@ +- Start Date: 2019-02-14 +- Relevant Team(s): Ember.js +- RFC PR: (after opening the RFC PR, update this with a link to it and update the file name) +- Tracking: (leave this empty) + +# Handlebars Strict Mode + +## Summary + +In this RFC, we propose a set of changes to Ember's variant of Handlebars that +are aimed at codifying best practices, improving clarity and simplifying the +language. Together, these changes are bundled into a "strict mode" that Ember +developers can opt-into. In contrast, the non-strict mode (i.e. what developers +are using today) will be referred to as "sloppy mode" in this RFC. + +## Motivation + +Ember has been using Handlebars since it was released 7 years ago (!). Over +time, we have evloved, adapted and in some case repurposed the Handlebars +language significantly (remember "context-shifting" `{{#each}}`?). This RFC +propose to provide a "strict mode" opt-in to remedy some of Handlebars' design +decisions that we have come to regret over the years, or are otherwise not a +good fit for Ember. We believe this will make the Handlbars language easier to +learn, understand and implement, as well as enable better tooling to support +common development workflows for Ember developers. + +We propose the following changes: + +1. No implicit globals +2. No implicit `this` fallback +3. No dynamic resolution +4. No evals (no partials) + +### 1. No implicit globals + +Today, Ember implicitly introduce a set of implicit globals into a template's +scope, such as built-in helpers, components, modifiers. Apps and addons also +have the ability to introduce additional implicit globals by placing files into +the `app` folder or broccoli tree. It is also possible to further influence +this behavior by using the intimate resolver API (such as the alternative +"pods" layout). + +This adds a fair amount of dynamism, ambiguity and confusion when reading +templates. When an identifier is encountered, it's not always clear where this +value comes from or what kind of value it may be. This problem is especially +acute for the "ambigious content" position, i.e. `
{{foo-bar}}
`, +which could be a local variable `foo-bar`, a global component or a helper named +`foo-bar` provided by the app or an addon, or `{{this.foo-bar}}` (see the next +section). This problem is even worse when there is a custom resolver involved, +as the resolver may return a component or helper not found in the "expected" +location at runtime. + +Not only is this confusing for the human reader, it also makes it difficult for +the Glimmer VM implementation as well as other ecosystem tooling. For example, +if the developer made a typo, `{{food-bar}}`, it would be impossible to issue +a static error (build time error, inline error in IDEs) because the value may +be resolvable at runtime. It is also difficult, and in some cases impossible, +to implement IDE features such as "Jump to definition" without running code. + +[RFC #432](https://github.com/emberjs/rfcs/blob/master/text/0432-contextual-helpers.md#relationship-with-globals) +described some additional issues with the current implicit globals semantics. + +We propose to remove support for implicit globals in strict mode. All values +must be explicitly brought into scope, either through block params or defined +in the "ambient scope" (see [Detailed design](#detailed-design) section). + +### 2. No implicit `this` fallback + +Today, when Ember sees a path like `{{foo}}` in the template, after exhausting +the possibilities of implicit globals, it falls back to `{{this.foo}}`. This +adds to the same confusion outlined above. More details about the motivation +can be found in the accepted [RFC #308](https://github.com/emberjs/rfcs/pull/308). + +We propose to remove support for implicit `this` fallback in strict mode. The +explicit form `{{this.foo}}` must be used to refer to instance state, otherwise +it will trigger the same errors mentioned in the previous section. + +It is worth mentioning that [RFC #432](https://github.com/emberjs/rfcs/pull/432) +laid out a transition path towards a world where "everything is a value". +Between this and the previous restriction, we essentially have completed that +transition. To recap, here is a list of the +[outstanding issues](https://github.com/emberjs/rfcs/blob/master/text/0432-contextual-helpers.md#relationship-with-globals): + +1. Not possible to reference globals outside of invocation positions +2. Invocation of global helpers in angle bracket named arguments positions +3. Naming collisions between global components, helpers and element modifiers + +All of these problems are all related to implicit globals and/or implicit +`this` fallback. Since neither of these features are supported in strict mode, +they are no longer a concern for us. + +### 3. No dynamic resolution + +Today, Ember supports passing strings to the `component` helper (as well as the +`helper` and `modifier` helpers proposed in [RFC #432](https://github.com/emberjs/rfcs/pull/432)). +This can either be passed as a literal `{{component "foo-bar"}}` or passed as a +runtime value `{{component this.someString}}`. In either case, Ember will +attempt to _resolve_ the string as a component. + +It shares some of the same problems with implicit globals (where did this come +from?), but the dynamic form makes the problem more acute, as it is difficult +or impossible to tell which components a given template is dependent on. As +usual, if it is difficult for the human reader, the same is true for tools as +well. Specifically, this is hostile to "tree shaking" and other forms of static +(build time) dependency-graph analysis, since the dynamic form of the component +helper can invoke _any_ component available to the app. + +We propose to remove support for these forms of dynamic resolutions in strict +mode. Specifically, passing a string to the `component` helper (as well as the +`helper` and `modifier` helpers), whether as a literal or at runtime, will +result in an error. + +In practice, it is almost always the case that these dynamic resolutions are +switching between a small and bounded number of known components. For this +purpose, they can be replaced by patterns similar to this (using syntax from +the [template imports RFC](./0000-template-imports.md)): + +```hbs +--- +import { eq, get, hash } from '@ember/template/helpers'; +import { First, Second, Third } form './contextual-components'; +--- + +{{#if (eq this.selected "first")}} + {{yield First}} +{{else if (eq this.selected "second")}} + {{yield Second}} +{{else if (eq this.selected "third")}} + {{yield Third}} +{{/if}} + +{{!-- alternatively... --}} + +{{#let (hash first=First second=Second third=Third) as |options|}} + {{yield (get options this.selected)}} +{{/let}} +``` + +This will both make it clear to the human reader and enables tools to perform +optimizations, such as tree-shaking, by following the explict dependency graph. + +### 4. No evals (no partials) + +Ember current supports the `partial` feature. It takes a template name, which +can either be passed as a literal `{{partial "foo-bar"}}` or passed as a +runtime value `{{partial this.someString}}`. In either case, Ember will resolve +the template with the given name (with a prefix dash, like `-foo-bar.hbs`) and +render its content _as if_ they were copy and pasted into the same position. + +In either case, the rendered partials have full access to anything that is "in +scope" in the original template. This includes any local variables, instance +variables (via implicit `this` fallback or explicitly), implicit globals, named +arguments, blocks, etc. + +This feature has all of the same problems as above, but worse. In addition to +the usual sources (globals, `this` fallback etc), each variable found in a +partial template could also be coming from the "outer scope" from the caller +template. Conversely, on the caller side, "unused" variables may not be safe +to refactor away, because they may be consumed in a nested partial template. + +Not only do these make it difficult for humans to follow, the same is true for +tools as well. For example, linters cannot provide accurate undefined/unused +variables warning. Whenever the Glimmer VM encounter partials, it has to emit +a large amount of extra metadata just so they can be "wired up" correctly at +runtime. + +We propose to remove support for partials completely in strict mode. Invoking +the `{{partial ...}}` keyword in strict mode will be a static (build time) +error. + +The use case of extracting pieces of a template into smaller chunks can be +replaced by [template-only components](https://github.com/emberjs/rfcs/pull/278). +While this requires any variables to be passed explicitly as arguments, it also +removes the ambiguity and confusions. + +It should also be mentioned that the `{{debugger}}` keyword also falls into the +category of "eval" in today's implementation, since it will be able to access +any variable available to the current scope, including `this` fallback and when +nested inside a partial template. However, with the other changes proposed in +this RFC, we will be able to statically determine which variables the debugger +will have access to. Therefore we would still be able to support debugger usage +in strict mode without a very high performance penalty. + +## Detailed design + +This RFC aim to introduce and define the semantics of the Handlebars strict +mode, as well as the low-level primitive APIs to enable it. However, it does +not introduce any new user-facing syntax or conveniences for opting into this +mode. + +The intention is that primary way for users to opt-in to strict mode will be +defined by the [template imports](./0000-template-imports.md) proposal using +the APIs proposed in this RFC. However, these low-level APIs will also enable +other build tools (such as [ember-cli-htmlbars-inline-precompile](https://github.com/ember-cli/ember-cli-htmlbars-inline-precompile)) +to provide their own API for users to opt-in. In addition, these APIs will +allow other experiments to be built in userland, such as explorations for a +"single-file component" format. + +### Low-level APIs + +We propose to add an option to Ember's template compiler to enable strict mode +compilation. + +There three primitive APIs involved in compiling Ember templates. + +The `precompile` function (a.k.a. [`precompileTemplate`](https://github.com/ember-cli/ember-rfc176-data)) +is responsible for taking a template string, running AST plugins, checking for +errors and returning the "wire format" representation of the template. The +exact details of this "wire format" is unspecified and changes from time to +time across minor Ember versions. The only guarentee is that it returns a +string whose content is a valid JavaScript expression. + +For example: + +```js +import { precompileTemplate } from '@ember/template-compilation'; + +precompileTemplate('Hello, {{name}}!', { + moduleName: 'hello.hbs' +}); /* => `{ + "id": "AoL2bkKU", + "block": "{\"statements\":[\"...\"]}", + "meta": {"moduleName":"hello.hbs"} +}` */ +``` + +Again, the exact wire format changes from time to time, but the key is that the +content is valid JavaScript. This allows build tools to take this output and +insert in into any context where JavaScript expressions are allowed. + +At runtime, the "wire format" can be "rehydrated" into something consumable by +Ember via the `template` function (a.k.a. [`createTemplateFactory`](https://github.com/ember-cli/ember-rfc176-data)). + +Build tools typically compile templates into JavaScript modules by combining +these two pieces. In our example, the `hello.hbs` template is typically +compiled into a module similar to this: + +```js +import { createTemplateFactory } from '@ember/template-factory'; + +export default createTemplateFactory({ + "id": "AoL2bkKU", + "block": "{\"statements\":[\"...\"]}", + "meta": { "moduleName": "hello.hbs" } +}); +``` + +Finally, the `compile` function (a.k.a. [`compileTemplate`](https://github.com/ember-cli/ember-rfc176-data)) +is a convenience helper that simply combines the two steps by taking a raw +template string and returning a ready-to-be-consumed template object (the +output of `createTemplateFactory`), instead of the wire format. This is +mostly used for compiling templates at runtime, which is pretty rare. + +We propose to introduce a new `strict` option to the `precompile` and `compile` +functions to enable strict mode compilation: + +```js +import { precompileTemplate } from '@ember/template-compilation'; + +precompileTemplate('Hello, {{name}}!', { + moduleName: 'hello.hbs', + strict: true +}); +``` + +### The ambient scope + +Since there are no implicit globals in strict mode, there has to be an +alternative mechanism to introduce helpers and components into scope. + +Whenever the strict mode compiler encounters an undefined reference, i.e. an +identifier that is not a currently in-scope local variable (block param), the +default behavior is to assume that these are references to variables in the +_ambient scope_. That is, the compiler will emit JavaScript code that contains +JavaScript references to these variables. + +For example, consider the following template: + +```hbs +{{#let this.session.currentUser as |user|}} + +{{/let}} +``` + +Here, `this.session.currentUser` is an explicit refernce to the component's +instance state, `user` is a local variable introduced by the `#let` helper, +`@model` is a reference to a named argument. They all have obvious semantics. + +On the other hand, `BlogPost` and `titleize` are undefined references. The +compiler will assume that they are defined in the surrounding _ambient scope_ +at runtime and produce an output like this: + +```js +import { precompileTemplate } from '@ember/template-compilation'; + +precompileTemplate(`{{#let this.session.currentUser as |user|}} + +{{/let}}`, { + moduleName: 'index.hbs', + strict: true +}); /* => `{ + "id": "ANJ73B7b", + "block": "{\"statements\":[\"...\"]}", + "meta": { "moduleName": "index.hbs" }, + "scope": () => [BlogPost, titleize] +}` */ +``` + +Again, the specific format here is unimportant and subject to change. The key +here is that the JavaScript code produced by the compiler contains references +(via the `scope` closure in this hypothetical compilation) to the JavaScript +variables `BlogPost` and `titleize` in the surrounding JavaScript scope. + +The build tool is responsible for "linking" these undefined references by +putting the compiled JavaScript code inside a JavaScirpt context where these +variables are defined. Otherwise, depending on the configuration, the undefined +references will either cause a static (build-time) error from the linter, +transpiler (e.g. babel) or packager (e.g. rollup or webpack), or a runtime +`ReferenceError` when the code is evaluated by a JavaScript engine. + +This low-level, primitive feature is mainly useful for building the user-facing +[template imports](./0000-template-imports.md) feature. The user would likely +write a template like this: + +```hbs +--- +import { titleize } from '@ember/template-helpers'; +import BlogPost from './components/blog-post'; +--- + +{{#let this.session.currentUser as |user|}} + +{{/let}} +``` + +The build tool can then compile this into a JavaScript module like this: + +```js +import { createTemplateFactory } from '@ember/template-factory'; +import { titleize } from '@ember/template-helpers'; +import BlogPost from './components/blog-post'; + +export default createTemplateFactory({ + "id": "ANJ73B7b", + "block": "{\"statements\":[\"...\"]}", + "meta": { "moduleName": "index.hbs" }, + "scope": () => [BlogPost, titleize] +}); +``` + +When this is evaulated by a JavaScript engine, the references in the `scope` +closure will automatically be "linked up" with the imports, and Ember will be +able reference these values when rendering the template. Note that these +references are _static_– the values are essentially "snapshotted" by the +rendering engine whenever the template is instantiated. Updates to these values +in the JavaScript scope will _not_ be observable by the rendering engine, even +in conjunction with `Ember.set` or `@tracked`. + +Optionally, the build tool can choose to restrict the set of allowed ambient +references by suppling an array of available identifiers to the compiler: + +```js +import { precompileTemplate } from '@ember/template-compilation'; + +precompileTemplate(`{{#let this.session.currentUser as |user|}} + +{{/let}}`, { + moduleName: 'index.hbs', + strict: true, + scope: ['BlogPost', 'titleize'] +}); +``` + +If the template compiler encounters any undefined references outside of this +allowed list, it will throw an error with the appropiate location info. It also +follows that build tools can choose to disable this feature completely by +passing an empty array. + +### Deprecations + +Implicit `this` fallback and partials should be deprecated in sloppy mode. + +## How we teach this + +Strict mode is intended to become the main way Ember developers author +templates going forward. We anticipate this is going to be a slow transition, +but once the majority of Ember developers have migrated, we expect them to find +it clearer, more intuitive and more productive. + +Two of the strict mode restrictions – no implicit `this` fallback and no eval, +can be incrementally adopted in sloppy mode templates. The former is already +covered by [RFC #432](https://github.com/emberjs/rfcs/pull/432). We should +continue implementing the transition path laid out by that RFC and encourage +developers to start adopting the explicit `this` style. The latter (partials) +should also phased out and deprecated as soon as possible, as suggested in +[Pre-RFC #390](https://github.com/emberjs/rfcs/issues/390). We should encourage +developers to transition to components for these use cases. + +On the other hand, implicit globals and dynamic resolutions are not going away +anytime soon in sloppy mode. These features are intrinsically tied to sloppy +mode, and we expect developers to migrate to template imports over time (which +would also opt them into strict mode). + +The guides should be updated to feature template imports and therefore strict +mode. This will be discussed in more details in its own RFC. + +As for the low-level APIs, we should update the API documentation to cover the +new flags (`strict` and `scope`). The documentation should cover the details of +the "ambient scope" feature discussed in this RFC, and emphasize that it is +intended for linking static values such as helpers and components. + +## Drawbacks + +We could just deprecate without removing `this` fallback and partials, and let +implicit globals and dynamic resolution co-exists with template imports (the +primary consumer of the proposed strict mode). However, this will create a very +confusing compromise and users will not get most of the benefits of having +template imports in the first place. We will also lose out on the opportunity +to improve on the static guarentees in order to build better tools. Leaving +around implicit globals also has the [issues](https://github.com/emberjs/rfcs/blob/master/text/0432-contextual-helpers.md#relationship-with-globals) +discussed in the contextual helpers RFC. + +## Alternatives + +1. Instead of bundling these into a single "strict mode" opt-in, we could just + allow developers to opt-in to each of these restrictions individually. + + In addition to the teaching and discoverability problems, we will also need + to build additional tooling and configuration mechanism (`handlebars.json`?) + for this. + + By adopting these piecemeal, we will also have to define the interaction and + combined semantics for any possible combinations of these flags, and tooling + will be unable to take advantage of the improved static guarentees without + doing a lot of work to account for all these possibilities. + +2. Instead of proposing a standalone strict mode, we could just bundle these + semantics into the templates imports proposal. + + That would make it a very long anc complex RFC. In addition, other build + tools like [ember-cli-htmlbars-inline-precompile](https://github.com/ember-cli/ember-cli-htmlbars-inline-precompile) + will not be able to adopt the same semantics. + +3. Switch to HTML attributes by default in strict mode. + + Today, Glimmer uses a complicated set of huristics to decide if a bound HTML + "attribute" syntax should indeed be set using `setAttribute` or set as a + JavaScript property using `element[...] = ...;`. This does not always work + well in practice, and it causes a lot of confusion and complexity. + + We intend to move to an "attributes syntax always mean attributes" (and use + modifiers for the rare cases of setting properties). We briefly considered + groupping that change into the strict mode opt-in, but ultimately decided it + would be too confusing for strict mode to include such a change. It's better + to deprecate the feature and make this an app-wide setting. + +4. Fix `(action ...)` binding semantics in strict mode. + + Similarly, there are some not ideal semantics issues with `(action ...)` + around how the function's `this` is bound. We similarly considered fixing it + in strict mode but ultimately decided it wouldn't be appropiate. + +## Unresolved questions + +None diff --git a/text/0000-template-imports.md b/text/0000-template-imports.md new file mode 100644 index 0000000000..dded2f93c0 --- /dev/null +++ b/text/0000-template-imports.md @@ -0,0 +1,87 @@ +- Start Date: 2019-02-14 +- Relevant Team(s): Ember.js +- RFC PR: (after opening the RFC PR, update this with a link to it and update the file name) +- Tracking: (leave this empty) + +# TODO Template Imports + +TODO: + +* import * from https://github.com/ef4/rfcs/blob/js-compatible-resolving/text/0000-js-compatible-resolution.md; + +* import * from https://github.com/emberjs/rfcs/blob/template-import/text/0000-template-imports.md; + +* define this as a preprocessor directive, not an extension to core handlebars + +* scroll down and see unresolved questions + +## Summary + +> One paragraph explanation of the feature. + +## Motivation + +> Why are we doing this? What use cases does it support? What is the expected +outcome? + +## Detailed design + +> This is the bulk of the RFC. + +> Explain the design in enough detail for somebody +familiar with the framework to understand, and for somebody familiar with the +implementation to implement. This should get into specifics and corner-cases, +and include examples of how the feature is used. Any new terminology should be +defined here. + +## How we teach this + +> What names and terminology work best for these concepts and why? How is this +idea best presented? As a continuation of existing Ember patterns, or as a +wholly new one? + +> Would the acceptance of this proposal mean the Ember guides must be +re-organized or altered? Does it change how Ember is taught to new users +at any level? + +> How should this feature be introduced and taught to existing Ember +users? + +## 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? + +TODO: + +* how to use/import things from new world in sloppy templates? +* how to use/import things from addons/node_modules? +* how to avoid importing `../../../../../../..`? `$src`? (does it work in JS too?) +* how to "import" resolver things from strict mode? `$app`? (does it work in JS too?) + * how does "association" work for old things? +* { `foo-bar/component.js`, `foo-bar/template.hbs` } or { `foo-bar.hbs`, `foo-bar.js` }? + * if the latter, how does the template import things from the js file? `import { someHelper } from '.'`? +* what is the recommended file/tree layout? +* how do you opt-in to strict mode without any imports? (does it matter?) +* keywords/syntax vs imports list +* something something DI injections +* do we allow JS comments inside `--- ... ---` (to temporarily comment out imports, or docs??) + * do we allow an optional new line after "closing" ---? +* to answer Robert's question: `--- ... ---` is a "preprocessor directive" and not a handlebars thing (?) + * implies that hbs` doesn't automatically get imports (which is good/correct/important) +* linting/errors + * imports and comments only From 06e524380b658612caf7643fc91ac4205a224356 Mon Sep 17 00:00:00 2001 From: Ricardo Mendes Date: Fri, 15 Feb 2019 00:04:33 +0000 Subject: [PATCH 2/9] Update 0000-handlebars-strict-mode.md --- text/0000-handlebars-strict-mode.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/text/0000-handlebars-strict-mode.md b/text/0000-handlebars-strict-mode.md index eb50bc61e9..a4ece4b7a2 100644 --- a/text/0000-handlebars-strict-mode.md +++ b/text/0000-handlebars-strict-mode.md @@ -16,9 +16,9 @@ are using today) will be referred to as "sloppy mode" in this RFC. ## Motivation Ember has been using Handlebars since it was released 7 years ago (!). Over -time, we have evloved, adapted and in some case repurposed the Handlebars +time, we have evolved, adapted and in some case repurposed the Handlebars language significantly (remember "context-shifting" `{{#each}}`?). This RFC -propose to provide a "strict mode" opt-in to remedy some of Handlebars' design +proposes to provide a "strict mode" opt-in to remedy some of Handlebars' design decisions that we have come to regret over the years, or are otherwise not a good fit for Ember. We believe this will make the Handlbars language easier to learn, understand and implement, as well as enable better tooling to support @@ -33,7 +33,7 @@ We propose the following changes: ### 1. No implicit globals -Today, Ember implicitly introduce a set of implicit globals into a template's +Today, Ember implicitly introduces a set of implicit globals into a template's scope, such as built-in helpers, components, modifiers. Apps and addons also have the ability to introduce additional implicit globals by placing files into the `app` folder or broccoli tree. It is also possible to further influence From 32e78bc2a8c4df628702f7611a7ade1fb44ba330 Mon Sep 17 00:00:00 2001 From: Ricardo Mendes Date: Fri, 15 Feb 2019 00:38:51 +0000 Subject: [PATCH 3/9] Update 0000-handlebars-strict-mode.md --- text/0000-handlebars-strict-mode.md | 33 +++++++++++++++-------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/text/0000-handlebars-strict-mode.md b/text/0000-handlebars-strict-mode.md index a4ece4b7a2..a8cc99a051 100644 --- a/text/0000-handlebars-strict-mode.md +++ b/text/0000-handlebars-strict-mode.md @@ -136,12 +136,12 @@ import { First, Second, Third } form './contextual-components'; {{/let}} ``` -This will both make it clear to the human reader and enables tools to perform +This will make it clear to the human reader and enable tools to perform optimizations, such as tree-shaking, by following the explict dependency graph. ### 4. No evals (no partials) -Ember current supports the `partial` feature. It takes a template name, which +Ember currently supports the `partial` feature. It takes a template name, which can either be passed as a literal `{{partial "foo-bar"}}` or passed as a runtime value `{{partial this.someString}}`. In either case, Ember will resolve the template with the given name (with a prefix dash, like `-foo-bar.hbs`) and @@ -183,12 +183,12 @@ in strict mode without a very high performance penalty. ## Detailed design -This RFC aim to introduce and define the semantics of the Handlebars strict +This RFC aims to introduce and define the semantics of the Handlebars strict mode, as well as the low-level primitive APIs to enable it. However, it does not introduce any new user-facing syntax or conveniences for opting into this mode. -The intention is that primary way for users to opt-in to strict mode will be +The intention is that the primary way for users to opt-in to strict mode will be defined by the [template imports](./0000-template-imports.md) proposal using the APIs proposed in this RFC. However, these low-level APIs will also enable other build tools (such as [ember-cli-htmlbars-inline-precompile](https://github.com/ember-cli/ember-cli-htmlbars-inline-precompile)) @@ -201,7 +201,8 @@ allow other experiments to be built in userland, such as explorations for a We propose to add an option to Ember's template compiler to enable strict mode compilation. -There three primitive APIs involved in compiling Ember templates. +There are three primitive APIs involved in compiling Ember templates, `precompile`, +`template` and `compile`. The `precompile` function (a.k.a. [`precompileTemplate`](https://github.com/ember-cli/ember-rfc176-data)) is responsible for taking a template string, running AST plugins, checking for @@ -226,7 +227,7 @@ precompileTemplate('Hello, {{name}}!', { Again, the exact wire format changes from time to time, but the key is that the content is valid JavaScript. This allows build tools to take this output and -insert in into any context where JavaScript expressions are allowed. +insert it into any context where JavaScript expressions are allowed. At runtime, the "wire format" can be "rehydrated" into something consumable by Ember via the `template` function (a.k.a. [`createTemplateFactory`](https://github.com/ember-cli/ember-rfc176-data)). @@ -282,8 +283,8 @@ For example, consider the following template: {{/let}} ``` -Here, `this.session.currentUser` is an explicit refernce to the component's -instance state, `user` is a local variable introduced by the `#let` helper, +Here, `this.session.currentUser` is an explicit reference to the component's +instance state, `user` is a local variable introduced by the `#let` helper and `@model` is a reference to a named argument. They all have obvious semantics. On the other hand, `BlogPost` and `titleize` are undefined references. The @@ -307,12 +308,12 @@ precompileTemplate(`{{#let this.session.currentUser as |user|}} ``` Again, the specific format here is unimportant and subject to change. The key -here is that the JavaScript code produced by the compiler contains references +here is that the JavaScript code produced by the compiler contains references (via the `scope` closure in this hypothetical compilation) to the JavaScript variables `BlogPost` and `titleize` in the surrounding JavaScript scope. The build tool is responsible for "linking" these undefined references by -putting the compiled JavaScript code inside a JavaScirpt context where these +putting the compiled JavaScript code inside a JavaScript context where these variables are defined. Otherwise, depending on the configuration, the undefined references will either cause a static (build-time) error from the linter, transpiler (e.g. babel) or packager (e.g. rollup or webpack), or a runtime @@ -350,8 +351,8 @@ export default createTemplateFactory({ When this is evaulated by a JavaScript engine, the references in the `scope` closure will automatically be "linked up" with the imports, and Ember will be -able reference these values when rendering the template. Note that these -references are _static_– the values are essentially "snapshotted" by the +able to reference these values when rendering the template. Note that these +references are _static_–the values are essentially "snapshotted" by the rendering engine whenever the template is instantiated. Updates to these values in the JavaScript scope will _not_ be observable by the rendering engine, even in conjunction with `Ember.set` or `@tracked`. @@ -387,8 +388,8 @@ templates going forward. We anticipate this is going to be a slow transition, but once the majority of Ember developers have migrated, we expect them to find it clearer, more intuitive and more productive. -Two of the strict mode restrictions – no implicit `this` fallback and no eval, -can be incrementally adopted in sloppy mode templates. The former is already +Two of the strict mode restrictions—no implicit `this` fallback and no eval—can +be incrementally adopted in sloppy mode templates. The former is already covered by [RFC #432](https://github.com/emberjs/rfcs/pull/432). We should continue implementing the transition path laid out by that RFC and encourage developers to start adopting the explicit `this` style. The latter (partials) @@ -412,7 +413,7 @@ intended for linking static values such as helpers and components. ## Drawbacks We could just deprecate without removing `this` fallback and partials, and let -implicit globals and dynamic resolution co-exists with template imports (the +implicit globals and dynamic resolution co-exist with template imports (the primary consumer of the proposed strict mode). However, this will create a very confusing compromise and users will not get most of the benefits of having template imports in the first place. We will also lose out on the opportunity @@ -422,7 +423,7 @@ discussed in the contextual helpers RFC. ## Alternatives -1. Instead of bundling these into a single "strict mode" opt-in, we could just +1. Instead of bundling these into a single "strict mode" opt-in, we could allow developers to opt-in to each of these restrictions individually. In addition to the teaching and discoverability problems, we will also need From 56fbd95172371bc48635d1414fc06ffd1337f94a Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 14 Feb 2019 18:36:28 -0700 Subject: [PATCH 4/9] Cleaning up small typos (#447) --- text/0000-handlebars-strict-mode.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/text/0000-handlebars-strict-mode.md b/text/0000-handlebars-strict-mode.md index a8cc99a051..0498a1a3eb 100644 --- a/text/0000-handlebars-strict-mode.md +++ b/text/0000-handlebars-strict-mode.md @@ -118,7 +118,7 @@ the [template imports RFC](./0000-template-imports.md)): ```hbs --- import { eq, get, hash } from '@ember/template/helpers'; -import { First, Second, Third } form './contextual-components'; +import { First, Second, Third } from './contextual-components'; --- {{#if (eq this.selected "first")}} @@ -208,7 +208,7 @@ The `precompile` function (a.k.a. [`precompileTemplate`](https://github.com/embe is responsible for taking a template string, running AST plugins, checking for errors and returning the "wire format" representation of the template. The exact details of this "wire format" is unspecified and changes from time to -time across minor Ember versions. The only guarentee is that it returns a +time across minor Ember versions. The only guarantee is that it returns a string whose content is a valid JavaScript expression. For example: @@ -417,7 +417,7 @@ implicit globals and dynamic resolution co-exist with template imports (the primary consumer of the proposed strict mode). However, this will create a very confusing compromise and users will not get most of the benefits of having template imports in the first place. We will also lose out on the opportunity -to improve on the static guarentees in order to build better tools. Leaving +to improve on the static guarantees in order to build better tools. Leaving around implicit globals also has the [issues](https://github.com/emberjs/rfcs/blob/master/text/0432-contextual-helpers.md#relationship-with-globals) discussed in the contextual helpers RFC. @@ -438,7 +438,7 @@ discussed in the contextual helpers RFC. 2. Instead of proposing a standalone strict mode, we could just bundle these semantics into the templates imports proposal. - That would make it a very long anc complex RFC. In addition, other build + That would make it a very long and complex RFC. In addition, other build tools like [ember-cli-htmlbars-inline-precompile](https://github.com/ember-cli/ember-cli-htmlbars-inline-precompile) will not be able to adopt the same semantics. @@ -451,7 +451,7 @@ discussed in the contextual helpers RFC. We intend to move to an "attributes syntax always mean attributes" (and use modifiers for the rare cases of setting properties). We briefly considered - groupping that change into the strict mode opt-in, but ultimately decided it + grouping that change into the strict mode opt-in, but ultimately decided it would be too confusing for strict mode to include such a change. It's better to deprecate the feature and make this an app-wide setting. @@ -459,7 +459,7 @@ discussed in the contextual helpers RFC. Similarly, there are some not ideal semantics issues with `(action ...)` around how the function's `this` is bound. We similarly considered fixing it - in strict mode but ultimately decided it wouldn't be appropiate. + in strict mode but ultimately decided it wouldn't be appropriate. ## Unresolved questions From 727331d552c860960774694a0fe7e594de1ceebd Mon Sep 17 00:00:00 2001 From: Godfrey Chan Date: Sat, 1 Jun 2019 21:51:06 -0700 Subject: [PATCH 5/9] Assign RFC number --- ...handlebars-strict-mode.md => 0496-handlebars-strict-mode.md} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename text/{0000-handlebars-strict-mode.md => 0496-handlebars-strict-mode.md} (99%) diff --git a/text/0000-handlebars-strict-mode.md b/text/0496-handlebars-strict-mode.md similarity index 99% rename from text/0000-handlebars-strict-mode.md rename to text/0496-handlebars-strict-mode.md index 0498a1a3eb..e0ae6b383e 100644 --- a/text/0000-handlebars-strict-mode.md +++ b/text/0496-handlebars-strict-mode.md @@ -1,6 +1,6 @@ - Start Date: 2019-02-14 - Relevant Team(s): Ember.js -- RFC PR: (after opening the RFC PR, update this with a link to it and update the file name) +- RFC PR: https://github.com/emberjs/rfcs/pull/496 - Tracking: (leave this empty) # Handlebars Strict Mode From 77a20aaaf8a77f03c9e0aa83286333836a8d9c98 Mon Sep 17 00:00:00 2001 From: Godfrey Chan Date: Fri, 10 Apr 2020 11:05:31 -0700 Subject: [PATCH 6/9] hbml wip --- text/0000-hbml-templates.md | 269 ++++++++++++++++++++++++++++++++++++ 1 file changed, 269 insertions(+) create mode 100644 text/0000-hbml-templates.md diff --git a/text/0000-hbml-templates.md b/text/0000-hbml-templates.md new file mode 100644 index 0000000000..9eedbb849d --- /dev/null +++ b/text/0000-hbml-templates.md @@ -0,0 +1,269 @@ +- Start Date: 2020-01-09 +- Relevant Team(s): Ember.js +- RFC PR: (after opening the RFC PR, update this with a link to it and update the file name) +- Tracking: (leave this empty) + +# HBML Templates + +## Summary + +In this RFC, we propose to introduce support for a new kind of templates into +Ember.js – the Handlebars Markup Language, or HBML, with an `.hbml` extension +to distinguish them from regular `.hbs` tempaltes. + +HBML templates are a variant of the current `.hbs` templates with the following +differences: + +1. It specifically refers to the Ember's dialect of the Handlebars-in-HTML + templating language, unlike regular `.hbs` template which generically refers + to any applications of Handlebars, such as in Markdown or plain text files. + +2. HBML templates always has [strict mode](./0496-handlebars-strict-mode.md) + semantics, whereas `.hbs` which has "sloppy mode" semantics by default. + +3. HBML templates supports template imports via a frontmatter syntax. + +Today, a `.hbs` template in Ember may look like this: + +```hbs +{{#if isNewUser}} + +{{else}} + + Hello {{user.name}}, welcome back! + + +{{/if}} + +{{outlet}} +``` + +The equivilant HBML template would look like this: + +```hbs +--- +import { on } from '@ember/modifier'; +import Icon from 'ember-icon-pack'; +import Notification from 'my-app/components/notification'; +import ProductTour from 'my-app/components/product-tour'; +--- + +{{#if this.isNewUser}} + +{{else}} + + Hello {{this.user.name}}, welcome back! + + +{{/if}} + +{{outlet}} +``` + +## Motivation + +The introduction of [strict mode](./0496-handlebars-strict-mode.md) brought +with it two problems: + +1. How do users opt-in to the strict mode semantics? + +2. If there are no globals or dynamic resolutions, how do users access things + like components and helpers in their templates? + +The HBML proposal solves both of these problems at once, by using the new file +extension as an explicit opt-in to strict mode semantics, and by introducing +a template import syntax for brining items into scope. + +A dedicated file extension for Ember templates also makes it easier to provide +good developer experience. As mentioned above, Handlebars is a general purpose +templating syntax designed to be embedded into many different context. While +`.hbs` files almost always refer to Handlebars-in-HTML templates in an Ember +app, this is not generally the case in the wider ecosystem. + +As a result, when using the `.hbs` extension, editors and tools are not always +able to reliably provide the best language support (e.g. auto-completion and +syntax highlight of HTML tags) for these files. By declaring a dedicated file +extension for Ember's use case, these tools can implement more targeted support +for HTML and Ember-specific extensions to the Handlebars syntax. While it may +take some time for it to gain traction in the tooling ecosystem, we believe +this would be an improvement in the long run. + +Finally, by adopting the JavaScript import syntax and its module semantics, it +also makes it much easier for tools to understand Ember templates. For example, +build tools can use the imports to build a dependency graph for the purpose of +tree-shaking, IDEs can more reliably provide features like "jump to definition" +and automated refactoring, TypeScript can use this information to locate type +definitions, etc. Of course, it also makes it easier for human readers to +follow where different variables come from. + +## Detailed Design + +### `.hbml` File Extension + +* Editors +* GitHub, etc +* Compiler +* Build Tools +* Linters, Recast, etc +* TypeScript +* Inline `hbml` function +* `{{! use hbml }}` opt-in for `.hbs` files? + +### Strict Mode Semantics + +* See "Strict Mode" RFC +* No opt-out + +### Import Declaration + +* `---` must be beginning of file (no comments, whitespaces) + * Only JS import statements, JS whitespace and JS comments + * Optional newline after "closing" `---`, not part of content +* Interactions with components co-location (link to RFC) + * Reminder: Tempalte still not separately importable (whatever langauge is used in the other RFC) + * To state the obvious: Imports in JS and HBS are separately scoped and will not collide/pollute/hygene something something +* Give example of how to import from the component file + * `import ... from './foo';` + * Note: `import ... from '.';` only works the same way as node for "folder" version, so probably don't do it +* Keywords + * Is not a "value", cannot be imported, cannot be passed around + * Needed for things currently implemented by AST transforms ("macros") + * List of keywords: + * action: implicit this magic (too bad this is "easier" to use than `on`) + * debugger + * each, each-in: the latter AST transforms to each + * hasBlock + * has-block + * has-block-params + * if + * unless + * let + * log + * mount: but may be nice to be able to tree-shake this? + * partial: but it doesn't matter, strict mode doesn't allow it + * readonly / mut: probably should figure out the proper path forward here... + * outlet + * query-params: probably should be deprecated, has ast transform (when passed directly to {{link-to}} we transform to @queryParams=, etc) + * unbound + * with + * yield + * -in-element/in-element + * List of non-keywords: + * array (`import { array } from '@ember/helper`): in future we may just have literals and not need this + * component: should be fine?? does it give up any early optimization opportunities? + * concat (`import { concat } from '@ember/helper`) + * fn (`import { fn } from '@ember/helper`) + * get (`import { get } from '@ember/helper`): in future may have a syntax short hand?? + * hash (`import { hash } from '@ember/helper`): in future we may just have literals and not need this + * Input (`import { Input } from '@ember/component`): just a component (check if this is an AST macro somehow?) + * LinkTo: just a component + * currently importable from `import LinkTo from '@ember/routing/link-component` + * proposing "index reexport" from `import { LinkTo } from '@ember/routing` + * link-to: that takes p-args has to be a keyword and deprecated + * loc: deprecate if not already deprecated + * on (`import { on } from '@ember/modifier'`) + * textarea + * currently importable from `import TextArea from '@ember/component/text-area';` + * proposing "index reexport" from `import { TextArea } from '@ember/component';` +* Say what happens when you look at a built-in non-keyword helper/modifier in JS space + * Opaque unique JS value (e.g Symbol) +* Do we have globals? (on, eq, etc) + * Both RJ and GC thinks no + * CG wants eq, etc to be easy to use +* Any issues with other non-core "magic globals" that doesn't have import paths? +* Suggest packages reexport public API components/helpers/modifiers from their main entry-point + * somewhat in conflict with original rfcs#176 stance of "classes are default exports", does this matter? +* How to use legacy non-co-located components? Does it matter? + * RJ thinks "no" + * either try to migrate to colocation or import and call `setComponentTemplate` + +* `hbml`: no explicit access to import, optional first arg is POJO of imports + * Having to type the imports avoid the editor saying undefined reference + * In the short term, it makes "Jump to definition" easier + * Should `imports` just be inside `options` (so, at the end) + +``` +hbml({ + Foo, + Bar, + Baz: BazComponent +}, ` + + + +`, { + moduleName: '...' +}); + +hbml(template: string); +hbml(template: string, options: PrecompileOptions); +hbml(imports: Imports, template: string); +hbml(imports: Imports, template: string, options: PrecompileOptions); +``` + +* `camelCase` for helpers, modifiers, "control-flow" components is the new normal + * New idiom + * Docs, etc should follow this + * Should sloppy mode somehow support camelCase everything? + * Does that already work? No + +* `has-block` vs `hasBlock`? + * Always `(hasBlock)` + + +## TODO + +* import * from https://github.com/ef4/rfcs/blob/js-compatible-resolving/text/0000-js-compatible-resolution.md; + +* import * from https://github.com/emberjs/rfcs/blob/template-import/text/0000-template-imports.md; + +## Summary + +> One paragraph explanation of the feature. + +## Motivation + +> Why are we doing this? What use cases does it support? What is the expected +outcome? + +## Detailed design + +> This is the bulk of the RFC. + +> Explain the design in enough detail for somebody +familiar with the framework to understand, and for somebody familiar with the +implementation to implement. This should get into specifics and corner-cases, +and include examples of how the feature is used. Any new terminology should be +defined here. + +## How we teach this + +> What names and terminology work best for these concepts and why? How is this +idea best presented? As a continuation of existing Ember patterns, or as a +wholly new one? + +> Would the acceptance of this proposal mean the Ember guides must be +re-organized or altered? Does it change how Ember is taught to new users +at any level? + +> How should this feature be introduced and taught to existing Ember +users? + +## 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 ab94aa63132f977a3942ee0fc6ab92a106f08372 Mon Sep 17 00:00:00 2001 From: Godfrey Chan Date: Fri, 10 Apr 2020 11:05:58 -0700 Subject: [PATCH 7/9] Finalize strict mode RFC --- text/0496-handlebars-strict-mode.md | 225 +++++++++++++++++++++------- 1 file changed, 175 insertions(+), 50 deletions(-) diff --git a/text/0496-handlebars-strict-mode.md b/text/0496-handlebars-strict-mode.md index e0ae6b383e..ddaaeb6b60 100644 --- a/text/0496-handlebars-strict-mode.md +++ b/text/0496-handlebars-strict-mode.md @@ -13,6 +13,16 @@ language. Together, these changes are bundled into a "strict mode" that Ember developers can opt-into. In contrast, the non-strict mode (i.e. what developers are using today) will be referred to as "sloppy mode" in this RFC. +This RFC aims to introduce and define the semantics of the Handlebars strict +mode, as well as the low-level primitive APIs to enable it. However, it does +not introduce any new user-facing syntax or conveniences for opting into this +mode. + +The intention is to unlock experimentation of features such as template +imports and single-file components, but those features will require further +design and iterations before they can be proposed and recommended to Ember +users. + ## Motivation Ember has been using Handlebars since it was released 7 years ago (!). Over @@ -28,8 +38,9 @@ We propose the following changes: 1. No implicit globals 2. No implicit `this` fallback -3. No dynamic resolution -4. No evals (no partials) +3. No implicit invocation of argument-less helpers +4. No dynamic resolution +5. No evals (no partials) ### 1. No implicit globals @@ -89,7 +100,47 @@ All of these problems are all related to implicit globals and/or implicit `this` fallback. Since neither of these features are supported in strict mode, they are no longer a concern for us. -### 3. No dynamic resolution +### 3. No implicit invocation of argument-less helpers + +In the contextual helpers RFC, we discussed [an issue](https://github.com/emberjs/rfcs/blob/master/text/0432-contextual-helpers.md#relationship-with-globals) +regarding the invocation of arguments-less helpers in invocation argument +positions. + +In today's semantics, if there is a global helper named `pi`, the following +template will result it the helper being invoked, and the result of the helper +invocation will be passed into the component. + +```hbs + +``` + +This is not desirable behavior in the value-based semantics proposed in that +RFC, because it makes it impossible to pass helpers around as values, just as +it is possible to pass around contextual components today. + +The contextaul helper RFC proposed to deprecate this behavior and require +mandatory parentheses to invoke the helper. + + +```hbs +{{!-- passing the helper pi to the component --}} + + +{{!-- passing the result of invoking the helper pi to the component --}} + +``` + +In strict mode, the current, soon-to-be-deprecated behavior will be removed, +and the parentheses syntax will be mandatory. + +Note that this only affects arguments-less helpers, which are exceedingly rare, +as most helpers perform self-contained computations based on the provided +arguments. It also only affect argument positions. In content and attribute +positions, the intent is clear as it does not make sense to "pass a helper into +the DOM". The parentheses-less form will continue to work in those positions, +although the explicit parentheses are also permitted. + +### 4. No dynamic resolution Today, Ember supports passing strings to the `component` helper (as well as the `helper` and `modifier` helpers proposed in [RFC #432](https://github.com/emberjs/rfcs/pull/432)). @@ -112,34 +163,34 @@ result in an error. In practice, it is almost always the case that these dynamic resolutions are switching between a small and bounded number of known components. For this -purpose, they can be replaced by patterns similar to this (using syntax from -the [template imports RFC](./0000-template-imports.md)): +purpose, they can be replaced by patterns similar to this. -```hbs ---- -import { eq, get, hash } from '@ember/template/helpers'; +```js +import Component from '@glimmer/component'; import { First, Second, Third } from './contextual-components'; ---- - -{{#if (eq this.selected "first")}} - {{yield First}} -{{else if (eq this.selected "second")}} - {{yield Second}} -{{else if (eq this.selected "third")}} - {{yield Third}} -{{/if}} -{{!-- alternatively... --}} +class ProviderComponent extends Component { + get selectedComponent() { + switch(this.args.selection) { + case "first": + return First; + case "second": + return Second; + case "third": + return Third; + } + } +} +``` -{{#let (hash first=First second=Second third=Third) as |options|}} - {{yield (get options this.selected)}} -{{/let}} +```hbs +{{yield this.selectedComponent}} ``` This will make it clear to the human reader and enable tools to perform optimizations, such as tree-shaking, by following the explict dependency graph. -### 4. No evals (no partials) +### 5. No evals (no partials) Ember currently supports the `partial` feature. It takes a template name, which can either be passed as a literal `{{partial "foo-bar"}}` or passed as a @@ -164,9 +215,10 @@ variables warning. Whenever the Glimmer VM encounter partials, it has to emit a large amount of extra metadata just so they can be "wired up" correctly at runtime. -We propose to remove support for partials completely in strict mode. Invoking -the `{{partial ...}}` keyword in strict mode will be a static (build time) -error. +This feature has already been deprecated, and we propose to remove support for +partials completely in strict mode. Until the feature has been removed from +Ember itself, invoking the `{{partial ...}}` keyword in strict mode will be a +static (build time) error. The use case of extracting pieces of a template into smaller chunks can be replaced by [template-only components](https://github.com/emberjs/rfcs/pull/278). @@ -188,13 +240,17 @@ mode, as well as the low-level primitive APIs to enable it. However, it does not introduce any new user-facing syntax or conveniences for opting into this mode. -The intention is that the primary way for users to opt-in to strict mode will be -defined by the [template imports](./0000-template-imports.md) proposal using -the APIs proposed in this RFC. However, these low-level APIs will also enable -other build tools (such as [ember-cli-htmlbars-inline-precompile](https://github.com/ember-cli/ember-cli-htmlbars-inline-precompile)) -to provide their own API for users to opt-in. In addition, these APIs will -allow other experiments to be built in userland, such as explorations for a -"single-file component" format. +The intention is to unlock experimentation of features such as template +imports and single-file components, but those features will require further +design and iterations before they can be proposed and recommended to Ember +users. + +During this phase of experimentation, these low-level APIs will also enable +other build tools (such as [ember-cli-htmlbars](https://github.com/ember-cli/ember-cli-htmlbars)) +to provide their own API for users to opt-in. In addition, once this RFC is +approved and implemented in Ember, these low-level APIs will have the same +semver guarentees as any other APIs in Ember, allow these experimentations to +be built on stable grounds. ### Low-level APIs @@ -319,9 +375,10 @@ references will either cause a static (build-time) error from the linter, transpiler (e.g. babel) or packager (e.g. rollup or webpack), or a runtime `ReferenceError` when the code is evaluated by a JavaScript engine. -This low-level, primitive feature is mainly useful for building the user-facing -[template imports](./0000-template-imports.md) feature. The user would likely -write a template like this: +This low-level, primitive feature is mainly useful for building user-facing +template import and single-file component features. While this RFC does not +propose a user-facing syntax for these features, here is a hypothetical +template import syntax for the illustrative purposes only: ```hbs --- @@ -334,7 +391,7 @@ import BlogPost from './components/blog-post'; {{/let}} ``` -The build tool can then compile this into a JavaScript module like this: +The build tool can compile this into a JavaScript module like this: ```js import { createTemplateFactory } from '@ember/template-factory'; @@ -377,9 +434,71 @@ allowed list, it will throw an error with the appropiate location info. It also follows that build tools can choose to disable this feature completely by passing an empty array. +### Keywords + +While most items should be imported into scope explicitly, some of the existing +constructs in the language are unimportable will be made available as keywords +instead: + +* `action` +* `debugger` +* `each-in` +* `each` +* `has-block-params` +* `has-block` +* `hasBlock` +* `if` +* `in-element` +* `let` +* `link-to` (non-block form curly invocations) +* `loc` +* `log` +* `mount` +* `mut` +* `outlet` +* `query-params` +* `readonly` +* `unbound` +* `unless` +* `with` +* `yield` + +These keywords do _not_ have to be imported into scope and will always be +ambiently available. + +On the other hand, the following built-in constructs will need to be imported +(the current or proposed import paths in parentheses): + +* `array` (`import { array } from '@ember/helper`) +* `concat` (`import { concat } from '@ember/helper`) +* `fn` (`import { fn } from '@ember/helper`) +* `get` (`import { get } from '@ember/helper`) +* `hash` (`import { hash } from '@ember/helper`) +* `on` (`import { on } from '@ember/modifier'`) +* `Input` (`import { Input } from '@ember/component`) +* `LinkTo` (`import { LinkTo } from '@ember/routing`) +* `TextArea` (`import { TextArea } from '@ember/component'`) + +In general, built-ins that can be made importable should be imported. The main +difference are that some of the keywords uses internal language features (e.g. +implemented via AST transforms) that requires them to be keywords. + +Some of the keywords included in the list are considered legacy, and may be +deprecated in the future via future RFCs. If that happens before strict mode +becomes available in a stable release of Ember, those RFC may propose to drop +support for the legacy keywords in strict mode altogether. + ### Deprecations -Implicit `this` fallback and partials should be deprecated in sloppy mode. +The following features should be deprecated and removed in sloopy mode: + +* Implicit `this` fallback, proposed in [RFC #308](https://github.com/emberjs/rfcs/blob/master/text/0308-deprecate-property-lookup-fallback.md) +* Implicit invocation of argument-less helpers, proposed in [RFC #432](https://github.com/emberjs/rfcs/blob/master/text/0432-contextual-helpers.md) +* Partials, proposed in [RFC #449](https://github.com/emberjs/rfcs/blob/master/text/0449-deprecate-partials.md) + +When all of these features are removed, the main difference between sloopy mode +and strict mode will be the precense of globals and the ability to perform +dynamic runtime resolutions. ## How we teach this @@ -388,22 +507,28 @@ templates going forward. We anticipate this is going to be a slow transition, but once the majority of Ember developers have migrated, we expect them to find it clearer, more intuitive and more productive. -Two of the strict mode restrictions—no implicit `this` fallback and no eval—can -be incrementally adopted in sloppy mode templates. The former is already -covered by [RFC #432](https://github.com/emberjs/rfcs/pull/432). We should -continue implementing the transition path laid out by that RFC and encourage -developers to start adopting the explicit `this` style. The latter (partials) -should also phased out and deprecated as soon as possible, as suggested in -[Pre-RFC #390](https://github.com/emberjs/rfcs/issues/390). We should encourage -developers to transition to components for these use cases. +Three of the strict mode restrictions—no implicit `this` fallback, no implicit +invocation of argument-less helpers and no eval—were already proposed in their +respective RFCs. These features will be deprecated in sloppy mode templates and +can be fixed incrementally. We should continue implementing these deprecatios +as already proposed and encouraged adoption. It is quite likely that by the +time strict mode becomes widely available, these deprecations will have already +been implemented and fixed in most Ember applications. On the other hand, implicit globals and dynamic resolutions are not going away anytime soon in sloppy mode. These features are intrinsically tied to sloppy -mode, and we expect developers to migrate to template imports over time (which -would also opt them into strict mode). - -The guides should be updated to feature template imports and therefore strict -mode. This will be discussed in more details in its own RFC. +mode, and we expect developers to migrate to template imports or single-file +components when those over time, which would also opt them into strict mode, +when those features become available. + +That being said, this RFC does not propose any user-facing changes and there +will be no official, application-level opt-in to strict mode until either +template imports or single-file components exit the experimental phase and +become adopted as official feature in Ember via a future RFC. + +Therefore, we do not recommend making any changes to the guides to document the +strict mode semantics at this stage. Instead, the guides should be updated to +feature template imports or single-file components when they become available. As for the low-level APIs, we should update the API documentation to cover the new flags (`strict` and `scope`). The documentation should cover the details of From cff9a7e38169e16028953bd3280277bc01b9105f Mon Sep 17 00:00:00 2001 From: Godfrey Chan Date: Fri, 10 Apr 2020 11:06:40 -0700 Subject: [PATCH 8/9] Remove unused files --- text/0000-hbml-templates.md | 269 ---------------------------------- text/0000-template-imports.md | 87 ----------- 2 files changed, 356 deletions(-) delete mode 100644 text/0000-hbml-templates.md delete mode 100644 text/0000-template-imports.md diff --git a/text/0000-hbml-templates.md b/text/0000-hbml-templates.md deleted file mode 100644 index 9eedbb849d..0000000000 --- a/text/0000-hbml-templates.md +++ /dev/null @@ -1,269 +0,0 @@ -- Start Date: 2020-01-09 -- Relevant Team(s): Ember.js -- RFC PR: (after opening the RFC PR, update this with a link to it and update the file name) -- Tracking: (leave this empty) - -# HBML Templates - -## Summary - -In this RFC, we propose to introduce support for a new kind of templates into -Ember.js – the Handlebars Markup Language, or HBML, with an `.hbml` extension -to distinguish them from regular `.hbs` tempaltes. - -HBML templates are a variant of the current `.hbs` templates with the following -differences: - -1. It specifically refers to the Ember's dialect of the Handlebars-in-HTML - templating language, unlike regular `.hbs` template which generically refers - to any applications of Handlebars, such as in Markdown or plain text files. - -2. HBML templates always has [strict mode](./0496-handlebars-strict-mode.md) - semantics, whereas `.hbs` which has "sloppy mode" semantics by default. - -3. HBML templates supports template imports via a frontmatter syntax. - -Today, a `.hbs` template in Ember may look like this: - -```hbs -{{#if isNewUser}} - -{{else}} - - Hello {{user.name}}, welcome back! - - -{{/if}} - -{{outlet}} -``` - -The equivilant HBML template would look like this: - -```hbs ---- -import { on } from '@ember/modifier'; -import Icon from 'ember-icon-pack'; -import Notification from 'my-app/components/notification'; -import ProductTour from 'my-app/components/product-tour'; ---- - -{{#if this.isNewUser}} - -{{else}} - - Hello {{this.user.name}}, welcome back! - - -{{/if}} - -{{outlet}} -``` - -## Motivation - -The introduction of [strict mode](./0496-handlebars-strict-mode.md) brought -with it two problems: - -1. How do users opt-in to the strict mode semantics? - -2. If there are no globals or dynamic resolutions, how do users access things - like components and helpers in their templates? - -The HBML proposal solves both of these problems at once, by using the new file -extension as an explicit opt-in to strict mode semantics, and by introducing -a template import syntax for brining items into scope. - -A dedicated file extension for Ember templates also makes it easier to provide -good developer experience. As mentioned above, Handlebars is a general purpose -templating syntax designed to be embedded into many different context. While -`.hbs` files almost always refer to Handlebars-in-HTML templates in an Ember -app, this is not generally the case in the wider ecosystem. - -As a result, when using the `.hbs` extension, editors and tools are not always -able to reliably provide the best language support (e.g. auto-completion and -syntax highlight of HTML tags) for these files. By declaring a dedicated file -extension for Ember's use case, these tools can implement more targeted support -for HTML and Ember-specific extensions to the Handlebars syntax. While it may -take some time for it to gain traction in the tooling ecosystem, we believe -this would be an improvement in the long run. - -Finally, by adopting the JavaScript import syntax and its module semantics, it -also makes it much easier for tools to understand Ember templates. For example, -build tools can use the imports to build a dependency graph for the purpose of -tree-shaking, IDEs can more reliably provide features like "jump to definition" -and automated refactoring, TypeScript can use this information to locate type -definitions, etc. Of course, it also makes it easier for human readers to -follow where different variables come from. - -## Detailed Design - -### `.hbml` File Extension - -* Editors -* GitHub, etc -* Compiler -* Build Tools -* Linters, Recast, etc -* TypeScript -* Inline `hbml` function -* `{{! use hbml }}` opt-in for `.hbs` files? - -### Strict Mode Semantics - -* See "Strict Mode" RFC -* No opt-out - -### Import Declaration - -* `---` must be beginning of file (no comments, whitespaces) - * Only JS import statements, JS whitespace and JS comments - * Optional newline after "closing" `---`, not part of content -* Interactions with components co-location (link to RFC) - * Reminder: Tempalte still not separately importable (whatever langauge is used in the other RFC) - * To state the obvious: Imports in JS and HBS are separately scoped and will not collide/pollute/hygene something something -* Give example of how to import from the component file - * `import ... from './foo';` - * Note: `import ... from '.';` only works the same way as node for "folder" version, so probably don't do it -* Keywords - * Is not a "value", cannot be imported, cannot be passed around - * Needed for things currently implemented by AST transforms ("macros") - * List of keywords: - * action: implicit this magic (too bad this is "easier" to use than `on`) - * debugger - * each, each-in: the latter AST transforms to each - * hasBlock - * has-block - * has-block-params - * if - * unless - * let - * log - * mount: but may be nice to be able to tree-shake this? - * partial: but it doesn't matter, strict mode doesn't allow it - * readonly / mut: probably should figure out the proper path forward here... - * outlet - * query-params: probably should be deprecated, has ast transform (when passed directly to {{link-to}} we transform to @queryParams=, etc) - * unbound - * with - * yield - * -in-element/in-element - * List of non-keywords: - * array (`import { array } from '@ember/helper`): in future we may just have literals and not need this - * component: should be fine?? does it give up any early optimization opportunities? - * concat (`import { concat } from '@ember/helper`) - * fn (`import { fn } from '@ember/helper`) - * get (`import { get } from '@ember/helper`): in future may have a syntax short hand?? - * hash (`import { hash } from '@ember/helper`): in future we may just have literals and not need this - * Input (`import { Input } from '@ember/component`): just a component (check if this is an AST macro somehow?) - * LinkTo: just a component - * currently importable from `import LinkTo from '@ember/routing/link-component` - * proposing "index reexport" from `import { LinkTo } from '@ember/routing` - * link-to: that takes p-args has to be a keyword and deprecated - * loc: deprecate if not already deprecated - * on (`import { on } from '@ember/modifier'`) - * textarea - * currently importable from `import TextArea from '@ember/component/text-area';` - * proposing "index reexport" from `import { TextArea } from '@ember/component';` -* Say what happens when you look at a built-in non-keyword helper/modifier in JS space - * Opaque unique JS value (e.g Symbol) -* Do we have globals? (on, eq, etc) - * Both RJ and GC thinks no - * CG wants eq, etc to be easy to use -* Any issues with other non-core "magic globals" that doesn't have import paths? -* Suggest packages reexport public API components/helpers/modifiers from their main entry-point - * somewhat in conflict with original rfcs#176 stance of "classes are default exports", does this matter? -* How to use legacy non-co-located components? Does it matter? - * RJ thinks "no" - * either try to migrate to colocation or import and call `setComponentTemplate` - -* `hbml`: no explicit access to import, optional first arg is POJO of imports - * Having to type the imports avoid the editor saying undefined reference - * In the short term, it makes "Jump to definition" easier - * Should `imports` just be inside `options` (so, at the end) - -``` -hbml({ - Foo, - Bar, - Baz: BazComponent -}, ` - - - -`, { - moduleName: '...' -}); - -hbml(template: string); -hbml(template: string, options: PrecompileOptions); -hbml(imports: Imports, template: string); -hbml(imports: Imports, template: string, options: PrecompileOptions); -``` - -* `camelCase` for helpers, modifiers, "control-flow" components is the new normal - * New idiom - * Docs, etc should follow this - * Should sloppy mode somehow support camelCase everything? - * Does that already work? No - -* `has-block` vs `hasBlock`? - * Always `(hasBlock)` - - -## TODO - -* import * from https://github.com/ef4/rfcs/blob/js-compatible-resolving/text/0000-js-compatible-resolution.md; - -* import * from https://github.com/emberjs/rfcs/blob/template-import/text/0000-template-imports.md; - -## Summary - -> One paragraph explanation of the feature. - -## Motivation - -> Why are we doing this? What use cases does it support? What is the expected -outcome? - -## Detailed design - -> This is the bulk of the RFC. - -> Explain the design in enough detail for somebody -familiar with the framework to understand, and for somebody familiar with the -implementation to implement. This should get into specifics and corner-cases, -and include examples of how the feature is used. Any new terminology should be -defined here. - -## How we teach this - -> What names and terminology work best for these concepts and why? How is this -idea best presented? As a continuation of existing Ember patterns, or as a -wholly new one? - -> Would the acceptance of this proposal mean the Ember guides must be -re-organized or altered? Does it change how Ember is taught to new users -at any level? - -> How should this feature be introduced and taught to existing Ember -users? - -## 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? diff --git a/text/0000-template-imports.md b/text/0000-template-imports.md deleted file mode 100644 index dded2f93c0..0000000000 --- a/text/0000-template-imports.md +++ /dev/null @@ -1,87 +0,0 @@ -- Start Date: 2019-02-14 -- Relevant Team(s): Ember.js -- RFC PR: (after opening the RFC PR, update this with a link to it and update the file name) -- Tracking: (leave this empty) - -# TODO Template Imports - -TODO: - -* import * from https://github.com/ef4/rfcs/blob/js-compatible-resolving/text/0000-js-compatible-resolution.md; - -* import * from https://github.com/emberjs/rfcs/blob/template-import/text/0000-template-imports.md; - -* define this as a preprocessor directive, not an extension to core handlebars - -* scroll down and see unresolved questions - -## Summary - -> One paragraph explanation of the feature. - -## Motivation - -> Why are we doing this? What use cases does it support? What is the expected -outcome? - -## Detailed design - -> This is the bulk of the RFC. - -> Explain the design in enough detail for somebody -familiar with the framework to understand, and for somebody familiar with the -implementation to implement. This should get into specifics and corner-cases, -and include examples of how the feature is used. Any new terminology should be -defined here. - -## How we teach this - -> What names and terminology work best for these concepts and why? How is this -idea best presented? As a continuation of existing Ember patterns, or as a -wholly new one? - -> Would the acceptance of this proposal mean the Ember guides must be -re-organized or altered? Does it change how Ember is taught to new users -at any level? - -> How should this feature be introduced and taught to existing Ember -users? - -## 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? - -TODO: - -* how to use/import things from new world in sloppy templates? -* how to use/import things from addons/node_modules? -* how to avoid importing `../../../../../../..`? `$src`? (does it work in JS too?) -* how to "import" resolver things from strict mode? `$app`? (does it work in JS too?) - * how does "association" work for old things? -* { `foo-bar/component.js`, `foo-bar/template.hbs` } or { `foo-bar.hbs`, `foo-bar.js` }? - * if the latter, how does the template import things from the js file? `import { someHelper } from '.'`? -* what is the recommended file/tree layout? -* how do you opt-in to strict mode without any imports? (does it matter?) -* keywords/syntax vs imports list -* something something DI injections -* do we allow JS comments inside `--- ... ---` (to temporarily comment out imports, or docs??) - * do we allow an optional new line after "closing" ---? -* to answer Robert's question: `--- ... ---` is a "preprocessor directive" and not a handlebars thing (?) - * implies that hbs` doesn't automatically get imports (which is good/correct/important) -* linting/errors - * imports and comments only From 508ac9b95bf2a4b397bf72b8e51d981c14c42535 Mon Sep 17 00:00:00 2001 From: Godfrey Chan Date: Sat, 18 Apr 2020 12:00:23 -0700 Subject: [PATCH 9/9] sloppy -> non-strict --- text/0496-handlebars-strict-mode.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/text/0496-handlebars-strict-mode.md b/text/0496-handlebars-strict-mode.md index ddaaeb6b60..66a81a24a5 100644 --- a/text/0496-handlebars-strict-mode.md +++ b/text/0496-handlebars-strict-mode.md @@ -11,7 +11,7 @@ In this RFC, we propose a set of changes to Ember's variant of Handlebars that are aimed at codifying best practices, improving clarity and simplifying the language. Together, these changes are bundled into a "strict mode" that Ember developers can opt-into. In contrast, the non-strict mode (i.e. what developers -are using today) will be referred to as "sloppy mode" in this RFC. +are using today) will be referred to as "non-strict mode" in this RFC. This RFC aims to introduce and define the semantics of the Handlebars strict mode, as well as the low-level primitive APIs to enable it. However, it does @@ -490,13 +490,13 @@ support for the legacy keywords in strict mode altogether. ### Deprecations -The following features should be deprecated and removed in sloopy mode: +The following features should be deprecated and removed in non-strict mode: * Implicit `this` fallback, proposed in [RFC #308](https://github.com/emberjs/rfcs/blob/master/text/0308-deprecate-property-lookup-fallback.md) * Implicit invocation of argument-less helpers, proposed in [RFC #432](https://github.com/emberjs/rfcs/blob/master/text/0432-contextual-helpers.md) * Partials, proposed in [RFC #449](https://github.com/emberjs/rfcs/blob/master/text/0449-deprecate-partials.md) -When all of these features are removed, the main difference between sloopy mode +When all of these features are removed, the main difference between non-strict mode and strict mode will be the precense of globals and the ability to perform dynamic runtime resolutions. @@ -509,14 +509,14 @@ it clearer, more intuitive and more productive. Three of the strict mode restrictions—no implicit `this` fallback, no implicit invocation of argument-less helpers and no eval—were already proposed in their -respective RFCs. These features will be deprecated in sloppy mode templates and +respective RFCs. These features will be deprecated in non-strict mode templates and can be fixed incrementally. We should continue implementing these deprecatios as already proposed and encouraged adoption. It is quite likely that by the time strict mode becomes widely available, these deprecations will have already been implemented and fixed in most Ember applications. On the other hand, implicit globals and dynamic resolutions are not going away -anytime soon in sloppy mode. These features are intrinsically tied to sloppy +anytime soon in non-strict mode. These features are intrinsically tied to non-strict mode, and we expect developers to migrate to template imports or single-file components when those over time, which would also opt them into strict mode, when those features become available.