Skip to content

Conversation

@NullVoxPopuli
Copy link
Contributor

@NullVoxPopuli NullVoxPopuli commented May 1, 2025

Propose renderComponent()

Rendered

Summary

This pull request is proposing a new RFC.

To succeed, it will need to pass into the Exploring Stage, followed by the Accepted Stage.

A Proposed or Exploring RFC may also move to the Closed Stage if it is withdrawn by the author or if it is rejected by the Ember team. This requires an "FCP to Close" period.

An FCP is required before merging this PR to advance to Accepted.

Upon merging this PR, automation will open a draft PR for this RFC to move to the Ready for Released Stage.

Exploring Stage Description

This stage is entered when the Ember team believes the concept described in the RFC should be pursued, but the RFC may still need some more work, discussion, answers to open questions, and/or a champion before it can move to the next stage.

An RFC is moved into Exploring with consensus of the relevant teams. The relevant team expects to spend time helping to refine the proposal. The RFC remains a PR and will have an Exploring label applied.

An Exploring RFC that is successfully completed can move to Accepted with an FCP is required as in the existing process. It may also be moved to Closed with an FCP.

Accepted Stage Description

To move into the "accepted stage" the RFC must have complete prose and have successfully passed through an "FCP to Accept" period in which the community has weighed in and consensus has been achieved on the direction. The relevant teams believe that the proposal is well-specified and ready for implementation. The RFC has a champion within one of the relevant teams.

If there are unanswered questions, we have outlined them and expect that they will be answered before Ready for Release.

When the RFC is accepted, the PR will be merged, and automation will open a new PR to move the RFC to the Ready for Release stage. That PR should be used to track implementation progress and gain consensus to move to the next stage.

Checklist to move to Exploring

  • The team believes the concepts described in the RFC should be pursued.
  • The label S-Proposed is removed from the PR and the label S-Exploring is added.
  • The Ember team is willing to work on the proposal to get it to Accepted

Checklist to move to Accepted

  • This PR has had the Final Comment Period label has been added to start the FCP
  • The RFC is announced in #news-and-announcements in the Ember Discord.
  • The RFC has complete prose, is well-specified and ready for implementation.
    • All sections of the RFC are filled out.
    • Any unanswered questions are outlined and expected to be answered before Ready for Release.
    • "How we teach this?" is sufficiently filled out.
  • The RFC has a champion within one of the relevant teams.
  • The RFC has consensus after the FCP period.

@github-actions github-actions bot added the S-Proposed In the Proposed Stage label May 1, 2025
@NullVoxPopuli NullVoxPopuli changed the title import { renderComponent } from '@ember/renderer'; renderComponent() May 1, 2025

When isInteractive is false, modifiers don't run, when true, modifiers do run.

#### `into`
Copy link

Choose a reason for hiding this comment

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

we could add after to allow app rendering after some node, without creating extra element wrapper

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've never heard of folks doing this -- is there a compelling reason / use case?

(maybe a bunch of render components in a list?)

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 kinda want to push back on this, as I don't know if the current render can do this.
It can always be added later if we need it

Copy link

Choose a reason for hiding this comment

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

@NullVoxPopuli, I agree.
Main use-case is render ember app in the middle of smf

Copy link
Contributor Author

Choose a reason for hiding this comment

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

smf?

Copy link

Choose a reason for hiding this comment

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

@NullVoxPopuli in between nodes, for example, we have:

<html>
  <body>
    <header> ama header </header>
    { HERE_I_WANT_TO_RENDER_WHOLE_APP without creating extra node }
    <footer> ama footer </footer>
  </body>
</html>

Copy link
Contributor

Choose a reason for hiding this comment

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

@gossi
Copy link

gossi commented May 2, 2025

I'm a big fan of this, as this is the baseline to write a storybook adapter for gts files. But I have some questions around this, as I think the narrative is not clear (yet):

  1. Why is it renderComponent() ... when it renders a template? (which I very much prefer). We can use that to render html that uses a helper or modifier. Vue and svelte have similar concepts for helper and modifier, but their testing lacks support for them, because they constrained themselves to components only (see my Inline Svelte components for tests sveltejs/svelte#14791 (comment), alse see API for vitest-browser-svelte and vitest-browser-vue) and I like us not to run into that constrained. I see it similar to the render() function we use in tests and would even expect the same behavior.

  2. There is args but no backing class? That means a template-only-component? Is that by-design?

  3. I wonder why there is an into parameter and later a parentElement() on the result type? If into becomes the parentElement then please name it like that (or is it the render replacing the into). Is the parentElement() on the result then needed? We can assume that is a given?

  4. There is plenty API that adressess templates, but this is also very much fragmented. Can we all the API that is about templates in @ember/template? In the end it doesn't really matter if these are compilers or renderers or parsers or whatever. In the end everything that you need to operate on templates is coming from one package.

@NullVoxPopuli
Copy link
Contributor Author

Why is it renderComponent() ... when it renders a template?
There is plenty API that adressess templates, but this is also very much fragmented. Can we all the API that is about templates in @ember/template

template() only returns a component 🎉
when not used with a class, it returns a template-only component.

I see it similar to the render() function we use in tests and would even expect the same behavior.

aye! the render() utility from @ember/test-helpers is quite involved, and after this RFC we could change its implementation to use renderComponent to

  • simplify implementation of @ember/test-helpers / ember-qunit
  • encourage exploration of other render-utilities, such as what would be needed to make browser-mode vitest work

There is args but no backing class? That means a template-only-component? Is that by-design?

what do you mean no backing class?

This is covered in RFC#931

import { template } from "@ember/template-compiler";

class Example extends Component {
  static {
    template(
      "Hello {{message}}",
      {
        component: this,
        scope: () => ({ message }),
      },
    );
  }
}

s the parentElement() on the result then needed? We can assume that is a given?

I only included it because the sample implementation from @wycats included it -- I'm more than happy to remove it, as it does feel redundant, since you need a handle to that same element (it is the into) to render anything anyway.

/**
* Renders a component into an element, given a valid component definition.
*/
export function renderComponent(
Copy link

Choose a reason for hiding this comment

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

wondering if it make sense to give function name just "render", because we can't render anything except component (or template-only component) in ember (app has it's own render logic)


#### `into`

The element to render the compnoent into.
Copy link

@lifeart lifeart May 2, 2025

Choose a reason for hiding this comment

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

should we have body as default target node?

Copy link

@gossi gossi May 2, 2025

Choose a reason for hiding this comment

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

can we rename it to parentElement to make it match what the DOM has already defined?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

we removed parentElement 🙈

Copy link
Contributor Author

Choose a reason for hiding this comment

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

should we have body as default target node?

idk, any downsides?

Copy link

Choose a reason for hiding this comment

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

you removed the getter from the result. I see into is ambivalent. It can mean, that this is a parent or replaced with the contents you provide. parentElement is a concept, that is already known.

const render = modifier((element) => {
let result = renderComponent(Demo, {
owner,
env: { document: document, isInteractive: true },
Copy link

@lifeart lifeart May 2, 2025

Choose a reason for hiding this comment

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

for non-interactive envs we may have different function like renderSSR, so, wondering if we really need isInteractive flag, any use-cases for it?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

great question -- I'd need to dig in to glimmer-vm to see what all isInteractive is used for.

I don't know how much benefit there is to having renderComponent do multiple things via this flag vs your suggestion of renderSSR (which I like)

* Destroys the render tree and removes all rendered content from the element rendered into.
*/
destroy(): void

Copy link

Choose a reason for hiding this comment

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

I think render result should contain component instance itself, to be able to update it's properties if needed (and to be able to debug it)

@ef4 ef4 added S-Exploring In the Exploring RFC Stage and removed S-Proposed In the Proposed Stage labels May 2, 2025
@ef4
Copy link
Contributor

ef4 commented Aug 1, 2025

A good question brought up by @void-mAlex at RFC review: what happens if you call renderComponent multiple times with the same Element? Is that allowed?

@NullVoxPopuli
Copy link
Contributor Author

A good question brought up by @void-mAlex at RFC review: what happens if you call renderComponent multiple times with the same Element? Is that allowed?

update pushed!

@NullVoxPopuli
Copy link
Contributor Author

Clarification has been added around the calling-multiple-times-on-the-same-element behavior, and during the RFC review meeting, since FCP is passed, we decided to merge -- I'll now head over to implementing -- yay!

@NullVoxPopuli NullVoxPopuli merged commit 643bb63 into master Aug 8, 2025
8 checks passed
@NullVoxPopuli NullVoxPopuli deleted the nvp/renderComponent branch August 8, 2025 18:14
@NullVoxPopuli
Copy link
Contributor Author

finished the implementation here: emberjs/ember.js#20962

NullVoxPopuli added a commit that referenced this pull request Oct 17, 2025
Advance RFC #1099 `"renderComponent"` to Stage Released
}
```

`RenderResult` is a subset of the currently a pre-existing `interface` from `@glimmer/interfaces`, but would be exposed as public API via the only the return type of `renderComponent`.
Copy link
Contributor

@MrChocolatine MrChocolatine Nov 15, 2025

Choose a reason for hiding this comment

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

Suggested change
`RenderResult` is a subset of the currently a pre-existing `interface` from `@glimmer/interfaces`, but would be exposed as public API via the only the return type of `renderComponent`.
`RenderResult` is a subset of the currently pre-existing `interface` from `@glimmer/interfaces`, but would be exposed as public API via the only return type of `renderComponent`.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Can you pr this change? As this rfc is already merged (and implemented!) i can't apply this suggestion

Copy link
Contributor

@MrChocolatine MrChocolatine Nov 15, 2025

Choose a reason for hiding this comment

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

I actually didn't see the big bright Merged label at the top 🫠 ...
#1153

/**
* When false, modifiers will not run.
*/
isInteractive?: boolean;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This will apply to other post-initial render things in the future


`RenderResult` is a subset of the currently a pre-existing `interface` from `@glimmer/interfaces`, but would be exposed as public API via the only the return type of `renderComponent`.

It's shape is:
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
It's shape is:
Its shape is:


#### `env.isInteractive` (defaults to true)

When isInteractive is false, modifiers don't run, when true, modifiers do run.
Copy link
Contributor

Choose a reason for hiding this comment

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

"when this is true, modifiers will run. When this is false, modifiers will not run"
But why is it named isInteractive? Can't we name it runModifiers?


## How we teach this

`renderComponent` _is_ a low-level API, but it's use cases are powerful:
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
`renderComponent` _is_ a low-level API, but it's use cases are powerful:
`renderComponent` _is_ a low-level API, but its use cases are powerful:

```

> [!NOTE]
> Depending on your application, you may want to consider sanitizing user input, so users don't inject their own script / sytle tags into your app.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
> Depending on your application, you may want to consider sanitizing user input, so users don't inject their own script / sytle tags into your app.
> Depending on your application, you may want to consider sanitizing user input, so users don't inject their own script / style tags into your app.

NullVoxPopuli added a commit that referenced this pull request Nov 15, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Final Comment Period S-Exploring In the Exploring RFC Stage

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants