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

First-Class Component Templates #779

Merged
merged 59 commits into from
Mar 4, 2022
Merged

First-Class Component Templates #779

merged 59 commits into from
Mar 4, 2022

Conversation

chriskrycho
Copy link
Contributor

@chriskrycho chriskrycho commented Dec 3, 2021

Rendered RFC

Summary

Adopt <template> tags as a format for making component templates first-class participants in JavaScript and TypeScript with strict mode template semantics. In support of the new syntax, adopt new custom JavaScript and TypeScript files with the extensions .gjs and .gts respectively.

First-class component templates address a number of pain points in today’s component authoring world, and provide a number of new capabilities to Ember and Glimmer users:

  • accessing local JavaScript values with no ceremony and no backing class, enabling much easier use of existing JavaScript ecosystem tools, including especially styling libraries—standard CSS Modules will “just work,” for example

  • authoring more than one component in a single file, where colocation makes sense—and thereby providing more control over a component’s public API

  • likewise authoring locally-scoped helpers, modifiers, and other JavaScript functionality

First-class component templates offer these new capabilities while not only maintaining but improving Ember’s long-standing commitment to integrated testing, in that it allows app and test code to share a single authoring paradigm—substantially simplifying our teaching story. Similarly, it preserves Ember’s long-standing commitment to treating JavaScript and HTML (and CSS!) as distinctive concerns which, however closely related, are not the same.

Full-fledged example showing how this might work in practice

Two notes:

  • For this and all the examples in the RFC, I assume RFC #0757: Default Modifier Manager for simplicity, but it does not meaningfully change this proposal.

  • The syntax highlighting here is a mess… but that's because GitHub still doesn't have good highlighting for decorators. Samples which have <template> but not @tracked actually already highlight decently well.

import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';

const Greet = <template>
  <p>Hello, {{@name}}!</p>
</template>

class SetUsername extends Component {
  @tracked name = '';

  updateName = ({ target: { value } }) => {
    this.name = value;
  }

  saveName = (submitEvent) => {
    submitEvent.preventDefault();
    this.args.onSaveName(this.name);
  };

  <template>
    <form {{on "submit" this.saveName}}>
      <label for='name'>Set username:</label>
      <input
        id='name'
        value={{this.value}}
        {{on "input" this.updateName}}
      />
      <button type='submit' disabled={{eq this.value.length 0}}>
        Generate
      </button>
    </form>
  </template>
}

function replaceLocation(el, { with: newUrl }) {
  el.contentWindow.location.replace(newUrl);
}

export default class GenerateAvatar extends Component {
  @tracked name = "";

  get previewUrl() {
    return `http://www.example.com/avatars/${name}`;
  }

  updateName = (newName) => {
    this.name = newName;
  };

  <template>
    <Greet @name={{this.name}} />
    <SetUsername
      @name={{this.name}}
      @onSaveName={{this.updateName}}
    />
    
    {{#if (gt 0 this.name.length)}}
      <iframe
        title='Preview'
        {{replaceLocation with=this.previewUrl}}
      >
    {{/if}}
  </template>
}

A note on the discussion

Before arguing for an alternative syntax, please take the time to read the ~16,000 words of careful, detailed analysis I provided about the tradeoffs of the alternatives designs in this space: Ember Template Imports. I spent an enormous amount of time working through that so that we can have a useful discussion here; please show me the respect of doing the same! If it seems to me from your comments that you have not read or understood those posts, I will direct you to them so that the conversation can be profitable!

Thanks

The words in this RFC are mine, and therefore so are any typos, infelicitous wording, or mistakes—but the best parts of this are the result of years of conversation (and occasional friendly argument!) with @pzuraq, @dfreeman, @jamescdavis, and @rwjblue. Special credit is due to @pzuraq for proving out the tooling considerations and building ember-template-imports, and for continually discussing this with me and many others over the past few years.

@chriskrycho chriskrycho changed the title [WIP] First-Class Component Templates First-Class Component Templates Dec 3, 2021

1. Create an ESLint processor which uses the same Babel transform as the core behavior itself (as provided currently by [ember-template-imports][eti]) to make normal ESLint rules (e.g. unused imports and values) work with the template scope.

2. Going the other direction, make it possible to use the existing `ember-template-lint` rules in `.gjs`/`.gts` files. This likely means integrating `ember-template-lint` directly into ESLint, in much the same way that other sub-language integration is done (in the same way that e.g. [eslint-plugin-svelte3][eslint-svelte] integrates Svelte's custom language).

Choose a reason for hiding this comment

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

+1 !!!!!

import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';

const Greet = <template>
Copy link
Sponsor Contributor

Choose a reason for hiding this comment

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

This is tangential to the RFC, but the lack of symmetry in the starting and ending template tags have been bothering me.

I've started writing components like this:

const Greet =
  <template>
    <p>Hello, {{@name}}!</p>
  </template>

Thoughts?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I couldn’t possibly care less: I share the preference for symmetry but dislike the extra indentation and have many other such small tastes. Whatever we standardize on will be fine, and I will leave those arguments to bikeshedding in the Prettier repo. 😂

Choose a reason for hiding this comment

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

Both options feel readable to me ✌🏻

Copy link
Sponsor Contributor

@NullVoxPopuli NullVoxPopuli left a comment

Choose a reason for hiding this comment

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

Nice work 🎉

I'm in full agreement with the direction proposed here, and do not believe the unresolved questions should be blocking (imo)

@Alonski
Copy link
Member

Alonski commented Dec 5, 2021

Just read the whole thing. I don't have much to add other than the fact that I am excited for this! Good job!

Copy link
Member

@bertdeblock bertdeblock left a comment

Choose a reason for hiding this comment

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

Super excited for this! Thanks for all the hard work!

text/0779-first-class-component-templates.md Outdated Show resolved Hide resolved
text/0779-first-class-component-templates.md Outdated Show resolved Hide resolved
text/0779-first-class-component-templates.md Outdated Show resolved Hide resolved
Copy link
Contributor

@acorncom acorncom left a comment

Choose a reason for hiding this comment

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

Overall, this looks great. Thanks very much for your hard work on putting this together Chris

text/0779-first-class-component-templates.md Outdated Show resolved Hide resolved
chriskrycho and others added 4 commits December 5, 2021 09:05
Thanks to the reviewers who spotted these!

Co-authored-by: Alon Bukai <alonzorz@gmail.com>
Co-authored-by: Bert De Block <bert.deblock@bagaar.be>
Co-authored-by: David Baker <acorncom@users.noreply.github.com>
- remove a needless use of `modifier()`
- remove extraneous imports
@knownasilya
Copy link
Contributor

Guessing this is getting merged any minute now.

@chriskrycho
Copy link
Contributor Author

FCP is two weeks long. Next week. 😉

@chriskrycho
Copy link
Contributor Author

@locks told me I was thinking of some other FCP process. Per the RFCs README:

RFCs that are candidates for inclusion in Ember will enter a "final comment period" lasting 7 days. The beginning of this period will be signaled with a comment and tag on the RFC's pull request. Furthermore, Ember's official Twitter account will post a tweet about the RFC to attract the community's attention.

So yeah, this is ready to merge… but I haven't been through this before so I’ll defer to other folks on Core about when we actually mash the merge button. 😂

@rwjblue rwjblue merged commit c776dca into emberjs:master Mar 4, 2022
@chriskrycho chriskrycho deleted the fcct branch March 4, 2022 19:11
chriskrycho added a commit that referenced this pull request Mar 24, 2022
RFC #724 originally intentionally excluded template type checking via
Glint from its designation of "official support" for Ember. However, in
the time since the RFC was authored, there have been two significant changes:

1.  Glint itself has matured significantly, with no known major issues
    at this time (though plenty of polish still to be done).

2.  The *major* design issues in Ember needed to unblock Glint have
    been resolved:

    - resolution of template identifiers (components, helpers, and
      modifiers): RFC #779
    - a Glimmer Component type signature which exposes information
      about the blocks and the target for `...attributes`: RFC #748

Although there remain a number of smaller design questions to resolve,
this is sufficient for us to treat type-checked templates as a viable
part of our story, and given *viability*, we think this is *necessary*.
ef4 added a commit to ember-cli/ember-cli-htmlbars that referenced this pull request Jun 30, 2022
Classically, standalone templates got a dedicated preprocessor that had nothing to do with Javascript transpilation.

Later came inline templates that appear inside Javascript. Those were handled as a totally separate code path from the standalone templates. To this day, there are two different code paths for handling these two cases.

But at this point, templates-inside-javascript are the foundational primitive that is aligned with [where Ember is heading](emberjs/rfcs#779), because they have access to Javascript scope and this solves a lot of problems.

We can treat standalone HBS as just a degenerate kind of JS that is easily up-compiled via a pure function, allowing them to go down the Javascript code path and allowing us to drop all the old code path.

This also makes it easier to support new features like [AST transforms that can manipulate Javascript scope](emberjs/babel-plugin-ember-template-compilation#5).

Embroider already [implemented this same pattern](embroider-build/embroider#1010).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Final Comment Period T-framework RFCs that impact the ember.js library T-templates
Projects
None yet
Development

Successfully merging this pull request may close these issues.