diff --git a/text/1133-template-only-class-component.md b/text/1133-template-only-class-component.md new file mode 100644 index 0000000000..b039f74529 --- /dev/null +++ b/text/1133-template-only-class-component.md @@ -0,0 +1,262 @@ +--- +stage: accepted +start-date: 2025-08-14T00:00:00.000Z +release-date: # In format YYYY-MM-DDT00:00:00.000Z +release-versions: +teams: + - framework + - typescript +prs: + accepted: https://github.com/emberjs/rfcs/pull/1133 +project-link: +suite: +--- + +# Template-Only Class Components + +## Summary + +Add a new `TemplateOnly` base class to `@glimmer/component` that enables template-only components to be defined as classes, allowing them to support TypeScript generics for better type safety. + +## Motivation + +Currently, template-only components in Ember are defined using `const` declarations like this: + +```typescript +export const Demo = +``` + +However, this approach has a significant limitation: **it's not possible to specify generic type parameters** with `const` declarations in TypeScript. This prevents template-only components from having proper type safety when they need to work with generic data types. + +For example, consider a template-only component that should work with different types of values: + +```typescript +import type { TOC } from '@ember/component/template-only'; + +// This doesn't work - can't add generics to const declarations +export const Demo: TOC<{ // ❌ Syntax error + Args: { + value: Value + } +}> = +``` + +This limitation forces developers to either: +1. Give up type safety and use `any` or `unknown` for component arguments +2. Create unnecessary `@glimmer/component` Component classes to add TypeScript generics -- [these are slower][^template-only-faster] +3. Create multiple duplicate components for different types + +Class-based components don't have the syntax limitation: + +```glimmer-ts +interface ComponentSignature { + Args: { + value: Value; + }; +} + +class Demo extends Component> { + +} +``` + +Creating a full component class when you only need a template feels like overkill and goes against the principle of template-only components [being lightweight][^template-only-faster]. + +[^template-only-faster]: template-only components are 7 to 26% faster than the `Component` from `@glimmer/component` -- https://nullvoxpopuli.com/2023-12-20-template-only-vs-class-components + +## Detailed design + +### New `TemplateOnly` Export + +This RFC proposes adding a new `TemplateOnly` export to `@glimmer/component` that can be used as a base class for template-only components: + +```glimmer-ts +import { TemplateOnly } from '@glimmer/component'; + +class Demo extends TemplateOnly<{ + Args: { + value: Value; + }; +}> { + +} + +export default Demo; +``` + +At runtime, this is _functionally_ the same as the `const` template-only components today. +In that the `TemplateOnly` class we re-export from `@glimmer/component` would be associated with a light-weight component manager, as template-only components are today. + +There is no `this`, so there will need to be a lint that forbids use of `this` in `TemplateOnly` classes. + +### Compilation + +Classes have a feature similar to functions, which is useful for defining more than one in the same file: hoisting. + +If we compiled to the `const` representation that we have today, we'd have a disagreement between editor expectations and runtime behavior. + +So compilation of these components should use `var` instead of `const`. + +Otherwise, all other aspects can be the same as existing compilation today: + +```glimmer-ts +// Source +class Demo extends TemplateOnly<{ + Args: { value: Value }; +}> { + +} + +// Compiled output (same as current template-only components) +import { precompileTemplate } from '@ember/template-compilation'; +import { setComponentTemplate } from '@ember/component'; +import templateOnlyComponent from '@ember/component/template-only'; + +export default setComponentTemplate( + precompileTemplate(`{{@value}}`, { + strictMode: true, + scope: () => ({}), + }), + templateOnlyComponent() +); +``` + +Existing babel-based compnoent compilation occurs in [`babel-plugin-ember-template-compilation`](https://github.com/emberjs/babel-plugin-ember-template-compilation). +But the `