-
-
Notifications
You must be signed in to change notification settings - Fork 408
Context #1200
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
Open
NullVoxPopuli
wants to merge
10
commits into
main
Choose a base branch
from
nvp/context
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Context #1200
Changes from 2 commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
2eff8b0
Context
NullVoxPopuli bf6471a
update meta
NullVoxPopuli db6b79b
[fn] ctx.consume() -> [get] ctx.consume
NullVoxPopuli 7368d6b
Remove note about modifier goals
NullVoxPopuli c8f3c61
Tighten language
NullVoxPopuli f8302db
makeContext -> createContext
NullVoxPopuli dd1620f
Rename the consume getter to value
NullVoxPopuli a6d06b9
Drop the let block since we have getters now
NullVoxPopuli fdca0af
Add clarification about in-element / render-tree / dom-tree
NullVoxPopuli 5b450c7
Add appendix note for capturing context at constructor time
NullVoxPopuli File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,309 @@ | ||
| --- | ||
| stage: accepted | ||
| start-date: 2026-06-10T00:00:00.000Z | ||
| release-date: # In format YYYY-MM-DDT00:00:00.000Z | ||
| release-versions: | ||
| teams: # delete teams that aren't relevant | ||
| - framework | ||
| - learning | ||
| prs: | ||
| accepted: https://github.com/emberjs/rfcs/pull/1200 | ||
| project-link: | ||
| suite: | ||
| --- | ||
|
|
||
| <!--- | ||
| Directions for above: | ||
|
|
||
| stage: Leave as is | ||
| start-date: Fill in with today's date, 2032-12-01T00:00:00.000Z | ||
| release-date: Leave as is | ||
| release-versions: Leave as is | ||
| teams: Include only the [team(s)](README.md#relevant-teams) for which this RFC applies | ||
| prs: | ||
| accepted: Fill this in with the URL for the Proposal RFC PR | ||
| project-link: Leave as is | ||
| suite: Leave as is | ||
| --> | ||
|
|
||
| # `makeContext`: Provide, consume | ||
|
|
||
| ## Summary | ||
|
|
||
| This RFC proposes one new export from `@ember/helper`: `makeContext`, which creates a render-tree-scoped _context_ -- a `Provide` component for binding a value into a region of the render tree, and a `consume` function for reading the nearest provided value from anywhere below it. This is the Context feature the community has been asking for since (at least) [RFC #975][rfc-975], scoped down to the smallest API that delivers it. | ||
|
|
||
| This RFC supersedes [RFC #1154][rfc-1154], incorporating what was learned while implementing it. An implementation of this proposal exists at [emberjs/ember.js#21450][impl-pr]. | ||
|
|
||
| [rfc-975]: https://github.com/emberjs/rfcs/pull/975 | ||
| [rfc-1154]: https://github.com/emberjs/rfcs/pull/1154 | ||
| [rfc-1155]: https://github.com/emberjs/rfcs/pull/1155 | ||
| [impl-pr]: https://github.com/emberjs/ember.js/pull/21450 | ||
| [epcc]: https://github.com/customerio/ember-provide-consume-context | ||
|
|
||
| ## Motivation | ||
|
|
||
| Folks want to share state with a _subtree_ of their app without prop-drilling, and without making that state global the way a service is. Today the only way to get real render-tree-scoped state is [ember-provide-consume-context][epcc], which works by [overriding private Glimmer VM classes](https://github.com/customerio/ember-provide-consume-context/blob/def6d34f639d56ebec1c7c8c888f86ec524b8688/ember-provide-consume-context/src/-private/override-glimmer-runtime-classes.ts#L1) -- which creates upgrade risk and a [maintenance burden](https://github.com/customerio/ember-provide-consume-context/pull/49) for both the addon and for ember-source. | ||
|
|
||
| [RFC #975][rfc-975] established the motivation and demand for Context in detail (design systems, chart/graph component families, form state, side-by-side trees that each need their own "ambient" state -- go read it, it holds up). Its last open action item was to propose a concrete public API. [RFC #1154][rfc-1154] attempted that with a low-level scope-exploration API (`getScope` / `addToScope`) intended for userland experimentation. Implementing it revealed that the experimentation layer was the wrong thing to make public: exposing render-tree _iteration_ commits us to far more API surface (and far more implementation detail) than exposing the one feature everyone actually wanted to build with it. | ||
|
|
||
| So this RFC proposes the feature itself, and nothing else: | ||
|
|
||
| - a way to provide a value to a subtree of the render tree, | ||
| - a way to consume the nearest provided value from within that subtree, | ||
|
|
||
| The expected outcome is that [ember-provide-consume-context][epcc] users (and everyone who wanted Context but wouldn't take on a private-API dependency to get it) have a built-in, supported, typed primitive. | ||
|
|
||
| > [!IMPORTANT] | ||
| > The APIs proposed here are different from [ember-provide-consume-context][epcc] -- with the goal of having the clearest block-semantics. This RFC does not propose any decerators, and use of Provide and consume are thus only available in synchronous calls from the `<template>` | ||
|
|
||
| ## Detailed design | ||
|
|
||
| ### The API | ||
|
|
||
| One new export from `@ember/helper` (plus its type): | ||
|
|
||
| ```ts | ||
| import { makeContext, type Context } from '@ember/helper'; | ||
| // ComponentLike is used for exposition (it's the @glint/template notion of | ||
| // "anything invokable as a component") -- the implementation returns an | ||
| // internal component class that satisfies this signature. | ||
|
|
||
| export function makeContext<T>(): Context<T>; | ||
|
|
||
| export interface Context<T> { | ||
| /** | ||
| * A component that makes `@value` available to everything | ||
| * rendered within its block. | ||
| * | ||
| * Renders no DOM of its own -- it only yields. | ||
| */ | ||
| Provide: ComponentLike<{ | ||
| Args: { value: T }; | ||
| Blocks: { default: [] }; | ||
| }>; | ||
|
|
||
| /** | ||
| * Returns the value from the nearest enclosing `<Provide>` | ||
| * for this context. | ||
| * | ||
| * Usable as a template helper -- `(theme.consume)` -- or called | ||
| * directly from JS that runs while rendering (e.g. a component | ||
| * constructor or a function helper). | ||
| * | ||
| * Throws if there is no enclosing `<Provide>` for this context, | ||
| * or if called outside of rendering. | ||
| */ | ||
| consume: () => T; | ||
| } | ||
| ``` | ||
|
|
||
| `makeContext` takes **no arguments**. It does not take a default value. The optional type parameter declares the shape of the value; the value itself is supplied at render time via `<Provide @value={{...}}>`. | ||
|
|
||
| ### Usage | ||
|
|
||
| ```gts | ||
| import { makeContext } from '@ember/helper'; | ||
|
|
||
| class Theme { | ||
| color = 'dark'; | ||
| } | ||
|
|
||
| const theme = makeContext<Theme>(); | ||
| const defaultTheme = new Theme(); | ||
|
|
||
| <template> | ||
| <theme.Provide @value={{defaultTheme}}> | ||
|
|
||
| {{! with let }} | ||
| {{#let (theme.consume) as |t|}} | ||
| {{t.color}} {{! "dark" }} | ||
| {{/let}} | ||
|
|
||
| {{! passing elsewhere }} | ||
| <SomeCompnoent @foo={{ (theme.consume) }} /> | ||
| </theme.Provide> | ||
| </template> | ||
| ``` | ||
|
|
||
| The context object is shared the same way any other value is shared in strict mode: export it from a module, and import it wherever you provide or consume. | ||
|
|
||
| ```gjs | ||
| // app/theme.js | ||
| import { makeContext } from '@ember/helper'; | ||
|
|
||
| export const theme = makeContext(); | ||
| ``` | ||
|
|
||
| ```gjs | ||
| // some deeply nested component | ||
| import Component from '@glimmer/component'; | ||
| import { theme } from 'my-app/theme'; | ||
|
|
||
| export default class FancyButton extends Component { | ||
| get color() { | ||
| return theme.consume().color; | ||
| } | ||
|
|
||
| <template> | ||
| <button style="color: {{this.color}}">{{yield}}</button> | ||
| </template> | ||
| } | ||
| ``` | ||
|
|
||
| ### Semantics | ||
|
|
||
| **Identity.** Every call to `makeContext()` creates a distinct context. A `consume()` only matches a `<Provide>` from the _same_ `makeContext()` call. There are no string keys, so there are no naming collisions: two addons can each have a context for "theme" and they cannot interfere with each other. | ||
|
|
||
| **Nearest provider wins.** `consume()` walks up the render tree and returns the value from the closest enclosing `<Provide>` for its context: | ||
|
|
||
| ```gjs | ||
| <template> | ||
| <ctx.Provide @value="outer"> | ||
| {{(ctx.consume)}} {{! "outer" }} | ||
| <ctx.Provide @value="inner"> | ||
| {{(ctx.consume)}} {{! "inner" }} | ||
| </ctx.Provide> | ||
| </ctx.Provide> | ||
| </template> | ||
| ``` | ||
|
|
||
| **Scoping follows the render tree.** A `<Provide>` is only visible to its descendants. Sibling subtrees do not see each other's providers, even for the same context. Separate `renderComponent` trees are fully isolated from each other. Because resolution follows the _render_ tree (not module structure, not the owner), a context provided in an app is visible inside a mounted engine's components, and a context can cross any component/helper boundary without anyone in the middle participating. | ||
|
|
||
| **Missing provider is an error.** `consume()` throws if there is no enclosing `<Provide>` for its context, and throws if called outside of rendering entirely. This is intentional harm-reduction: a missing provider is almost always a bug, and `undefined` would let that bug travel. There is no "default value" feature -- if you want an always-available value, render a `<Provide>` at your application root. (Note that this is a place where this proposal is deliberately stricter than [ember-provide-consume-context][epcc], whose `getContext` returns `undefined`.) | ||
|
|
||
| **An explicit `undefined` is still provided.** `<ctx.Provide @value={{undefined}}>` (or omitting `@value`) means the provider _is_ in the tree and provides `undefined` -- `consume()` returns `undefined` rather than throwing. "You provided nothing" and "there is no provider" are different situations, and only the latter is an error. | ||
|
|
||
| **Reactivity is just autotracking.** The `@value` binding is reactive: when the argument passed to `<Provide>` changes, consumers update. If `@value` is a stable object, mutating its `@tracked` fields updates consumers, and the object's identity is preserved across re-renders. There is no subscription API and no special invalidation mechanism -- a provided value behaves exactly like the same value passed as an argument. | ||
|
|
||
| **Where `consume()` may be called.** Anywhere that runs synchronously during rendering, below a matching `<Provide>`: | ||
|
|
||
| - in a template, as a helper: `(ctx.consume)` | ||
| - in a plain function helper's body | ||
| - in a component's constructor or getters | ||
|
|
||
| Because this is a synchronous, render-scoped API, `consume()` does not work after an `await` (there is no render tree position to resolve against anymore -- it throws the "outside of rendering" error). Read the context first, then go async. | ||
|
|
||
| > [!NOTE] | ||
| > In the current implementation, `consume()` inside a *modifier* also throws "outside of rendering", because modifiers run during the commit phase, after the render frame has unwound. Supporting modifiers is a desirable, purely-additive follow-up (it relaxes an error into working behavior, so it would not need a new RFC), but this RFC does not promise it. | ||
|
|
||
| ### Testing | ||
|
|
||
| No test-support API is needed. Providing a context in a rendering test is the same as providing it anywhere else: | ||
|
|
||
| ```gjs | ||
| test('FancyButton uses the provided theme', async function (assert) { | ||
| const testTheme = new Theme(); | ||
| testTheme.color = 'hotpink'; | ||
|
|
||
| await render( | ||
| <template> | ||
| <theme.Provide @value={{testTheme}}> | ||
| <FancyButton>hi</FancyButton> | ||
| </theme.Provide> | ||
| </template> | ||
| ); | ||
|
|
||
| assert.dom('button').hasStyle({ color: 'rgb(255, 105, 180)' }); | ||
| }); | ||
| ``` | ||
|
|
||
| ### How it works (non-normative) | ||
|
|
||
| The renderer already tracks the render-tree hierarchy (this is how the debug render tree behind Ember Inspector works). The implementation maintains a similar, always-on tracker of "scope nodes" as components are created and re-rendered. `<Provide>` records its (lazily-read, autotracked) `@value` on the current node under its context's private identity; `consume()` walks the current node's parent chain looking for that identity. | ||
|
|
||
| None of this is public API. The tracker, its module, and its shape may change freely as the renderer evolves -- the only stable surface is `makeContext` and the behavior specified above. (This is the most important difference from [RFC #1154][rfc-1154], which would have made the tree-walking itself public.) | ||
|
|
||
| ### Ecosystem implications | ||
|
|
||
| - a rule in `eslint-plugin-ember` flagging `consume()` (and other render-scoped calls) after an `await` would be a nice addition, but is not required for this feature to ship. | ||
| - [ember-provide-consume-context][epcc] can migrate its internals onto this (or document a migration path for its users) and stop overriding VM internals. | ||
|
|
||
| ### What this RFC does _not_ propose | ||
|
|
||
| To be explicit, because the surrounding discussion has covered a lot of ground over the years -- none of the following is proposed here: | ||
|
|
||
| - no `getScope` / `addToScope` or any other render-tree exploration API (that was [RFC #1154][rfc-1154]; this supersedes it) | ||
| - no `@provide` / `@consume` decorators and no string-keyed contexts (sketched in [RFC #975][rfc-975]) | ||
| - no changes to component manager APIs (the approach in [RFC #1155][rfc-1155]) | ||
| - no changes to `getOwner` or any owner-related API (see the appendix for why this is worth mentioning) | ||
| - no default values for contexts | ||
| - no test-support module | ||
|
|
||
| If any of these turn out to be wanted later, they can be their own (small) RFCs on top of this one. | ||
|
|
||
| ## How we teach this | ||
|
|
||
| The terminology is "context", "provider", and "consumer" -- the same words React, Vue (`provide`/`inject`), and Svelte (`setContext`/`getContext`) users already have for this concept. This is a case where matching the rest of the ecosystem is a feature: many developers will arrive already knowing what a context is, and the ones who don't will find a decade of general material about the pattern. | ||
|
|
||
| In the guides, context belongs after services, as "like a service, but scoped to part of the render tree instead of the whole application, with its lifetime tied to the provider's place in the tree." That comparison does most of the teaching: | ||
|
|
||
| | | service | context | | ||
| |---|---|---| | ||
| | visibility | whole application | descendants of a `<Provide>` | | ||
| | key | string (module name) | object identity (`makeContext()` result) | | ||
| | lifetime | application | the provider's block | | ||
| | can have many simultaneous values | no | yes (one per `<Provide>`) | | ||
|
|
||
| Guidance on _when_ to reach for it matters as much as the mechanics: app-wide state should remain a service; passing data one or two levels down should remain plain arguments; context is for when a component family or a region of the app needs shared ambient state and threading arguments through every intermediate component is the thing being avoided. | ||
|
|
||
| The API docs come from the JSDoc on `makeContext` (already written in the implementation PR) and should lead with a complete provide-then-consume example, plus the two error cases, since the throwing behavior is the part most likely to surprise someone coming from [ember-provide-consume-context][epcc]. | ||
|
|
||
| ## Drawbacks | ||
|
|
||
| - It is one more state-sharing tool to choose between (arguments, yields/contextual components, services, and now context), and it can be misused -- overly-broad context values cause the same problems in every framework that has the feature. This is a teaching problem, and the guides section above is most of the mitigation. | ||
| - `consume()` throwing on a missing provider is stricter than what existing [ember-provide-consume-context][epcc] users are used to, so migrating code may surface latent "consumed but never provided" bugs. (We think surfacing those is the point, but it is a real migration bump.) | ||
| - The always-on scope tracker adds a small amount of bookkeeping to every component create/update, whether or not an app uses context. The implementation keeps this to a stack push/pop with lazy allocation of everything else, and the implementation PR's smoke tests exist to keep it honest. | ||
|
|
||
| ## Alternatives | ||
|
|
||
| **Do nothing.** The community keeps depending on a VM-internals override that ember-source has to tiptoe around. | ||
|
|
||
| **[RFC #975][rfc-975]: `@provide` / `@consume` decorators with string keys.** This was the original proposal and it shaped this one. Its API has three problems this design avoids: string keys collide across addons; decorators only serve class components (template-only components, function helpers, and strict-mode templates are left out); and it coupled the design to the services API ("if services change form, context changes with it"). `makeContext` works in every component form, keys by identity, and stands alone. The decorators could still be built _on top of_ this primitive by anyone who wants them. | ||
|
|
||
| **[RFC #1154][rfc-1154]: public `getScope` / `addToScope`.** The previous attempt, by this RFC's author. It proposed the general capability (walk the render tree's userland metadata) so that Context could be explored in userland. Implementation showed that the general capability is the expensive thing to stabilize -- iteration order, entry shapes, owner access, and reactivity caveats all become public commitments -- while the thing people want to build with it took an API one-tenth the size. This RFC keeps the same underlying machinery private and ships the feature instead. | ||
|
|
||
| **[RFC #1155][rfc-1155]: expose the render tree to component managers.** This only serves components (helpers and other invokables can't participate), requires addon authors to interact with manager APIs to use it, and exposes internal manager values that aren't designed for extension. | ||
|
|
||
| **Prior art.** [ember-provide-consume-context][epcc] (whose test suite the implementation PR ports, so its production-proven behaviors -- sibling isolation, conditional providers, reactivity to value changes -- are pinned down as this feature's behavior), [React Context](https://react.dev/learn/passing-data-deeply-with-context), [Vue provide/inject](https://vuejs.org/guide/components/provide-inject.html), [Svelte setContext/getContext](https://svelte.dev/docs/svelte#setcontext), and [ember-context](https://github.com/alexlafroscia/ember-context) (which doesn't follow the render tree, and documents the resulting caveats). | ||
|
|
||
| ## Unresolved questions | ||
|
|
||
| - **Module home.** The implementation exports from `@ember/helper`, since `consume` is helper-shaped and that module already exports the template utilities (`fn`, `hash`, `array`, ...). A dedicated `@ember/context` module is the plausible alternative. The behavior in this RFC is unaffected either way. | ||
| - **Naming.** `makeContext` vs `createContext` (React's name), and `Provide` vs `Provider`. The implementation deliberately does not reuse React's exact names, since the shapes differ (`createContext(defaultValue)` vs `makeContext()` with no default), but bikeshedding is welcome. | ||
|
|
||
| ## Appendix: what this primitive makes cheap (not proposed here) | ||
|
|
||
| > [!IMPORTANT] | ||
| > Nothing in this appendix is proposed by this RFC. It exists to show that the machinery behind `makeContext` is the right altitude for future work -- and to record where that exploration lives -- so that the proposal above can stay minimal. | ||
|
|
||
| A long-wanted capability is `getOwner()` with **no arguments** working inside plain function helpers, which have no `this` to read an owner from: | ||
|
|
||
| ```gjs | ||
| import { getOwner } from '@ember/owner'; | ||
|
|
||
| function currentLocale() { | ||
| return getOwner()?.lookup('service:intl').locale; | ||
| } | ||
|
|
||
| <template> | ||
| {{ (currentLocale) }} | ||
| </template> | ||
| ``` | ||
|
|
||
| It turns out the owner _is_ a context: a value that everything below a point in the render tree should be able to read, where the nearest provider wins (which is exactly what `renderComponent`'s `owner` option and engine mounts need). Built on this RFC's machinery, the whole feature is essentially three lines -- a well-known internal context key, the helper opcodes providing the active owner under it, and `getOwner()` reading it back: | ||
|
|
||
| ```ts | ||
| // a well-known (internal) context key | ||
| export const OWNER: object = {}; | ||
|
|
||
| // at helper invocation, in the renderer | ||
| provideRenderContext(OWNER, () => owner); | ||
|
|
||
| // in getOwner(), when called with no argument | ||
| let read = lookupRenderContext(OWNER); | ||
| return read ? read() : undefined; | ||
| ``` | ||
|
|
||
| A working implementation of this, stacked on the implementation of this RFC, is at [NullVoxPopuli/ember.js#16](https://github.com/NullVoxPopuli/ember.js/pull/16) -- and because the owner lookup walks the render tree like any other context, it resolves correctly through `renderComponent` owner overrides and re-renders, for free. | ||
|
|
||
| That a years-old request becomes a three-line patch is the strongest evidence we have that this is the right primitive. If/when no-arg `getOwner` is wanted, it will be its own RFC; it would change the public signature of `getOwner`, and deserves its own discussion. | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The read and write API are mechanically totally out of sync!
not better, but you get the point I hope.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
consume is a function because it can be used within the component, Provide cannot
provide is not a function because invoking it without a block breaks the meaning of tree-based-context
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
a component in disguise.
If it is a component, make it a component
If it is a helper, make it a helpe.
Here is a component:
that's pleasing to the eyes. That's using ember's teached mechanics.
I'm pretty sure I'm ignoring important implementation details to make it work :D
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that requires two imports to work
as does what is proposed
see: this out of place doc (lol): https://guides.emberjs.com/release/in-depth-topics/rendering-values/
(in depth topics is probs the wrong place for it)
how do you mean? that's what is proposed -- except all functions are helpers now
theme.Provide- componenttheme.consume- function / helperThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would much rather have more imports than an overloaded object. Feels like we're going backwards to the
Ember.everythingdays...There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Provide as an output of the
createContextfunctionHaving
Providebe the output of context aligns with other frameworks (React, Svelte, Preact)Using other frameworks a reasoning for this can be better/faster types compared to a framework level component ala.
<Provide @context={{myContext}} @value={{someValue}} />. Along side of this the API for higher order state providers/headless managers remains the same from a simple context provider to more and more complex topics.For instance consider session in an app
provideas a helper/functionFunction invocation could be technically possible, but would have so many gotchas that it would be REALLY hard to teach and put meaningful errors/warnings for. JS functions and resource lifecycles can be hard to explain and at times to understand when/why they are being invoked and keeping them in sync with the render tree is hard to reason about.
This really doesn't give an indication of what gets the provided scope. We could make an assumption that this should populate to the top of the current component, but that opens a question of how you'd resolve
Or bike shedding of "should provide go to the nearest element"?
Having a component boundary for provide makes this a clear/simplified distinction
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using
Provide/consumeOne note that I would point out is that if you prefer to use
<Provide @context={{someContext}} @value={{myValue}} />this is a very simple API to write as a user land function.We are learning from React/Svelte here.
React uses a
import { useContext } from 'react'because it needs to setup some hooks. Svelte does not have this limitation: Svelte does not have a FW leveluseContextfor the preferredcreateContextAPIThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sure. It's the first time we get an API, that does these things:
there is no visual symmetry between the component, the helper and the constructor - all are syntactically distinct concepts, that do not say very well, they live in harmony. Ie, compare this to the classic
get()andset()(think old java), which is not far away from this context sample.I tried to "translate" your API into pure procedural form (sorta):
that is kinda your proposed API.
Also it would be the first time to design an API like that, so education to it has a price and an error rate.
I agree, it needs access to the render tree. I challenge you it needs to get this as a component - I don't believe so. Getting access to the render tree is likely the hard part of this RFC. Saying it must be a component may be a way to skip the hard part of this RFC and evade the essence of this RFC. I don't know about internals here. Accessing the render tree can be internal API, but if it must go public it may not even be wrong (devtools likely needs this to? or already have it? I'm thinking ember inspector here).
Delcarative/Procedural mixed API
Inspired by svelte:
Declarative Usage
Procedural Usage
Discussion
The yielded helpers need access to the owner. For the declarative this can be done with their own registered helper manager.
This is also the weak point. I heavily assume: Through the owner, it will have access to the render tree. I consider this to be solved as part of this RFC (see above access to the render tree).
The differences in procedural vs. declarative helper ergonomics are based on the constraints of the current design. That's the best I know, after long research, frustrations and try-and-error building
ember-ability.Asking for a
getOwner()(wo/ params) function would solve this, but I know this is perhaps a harder problem than context.I'm fine postponing context in favor of levelling up the modifier/helper game at first.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that is why we have an API that collects all these things and does not have them as separate concepts.
getContext, andprovideas separately importable thing would make the very points you bring up worse.this RFC is similar, in that you create an object that you get returned to you, and then interact with properties/methods on it.
trackedto work outside of classes #1071nay, huge disagree. it's a matter of semantics. context is render-tree scoped, so we need a render-tree concept, to make it visually obvious where the providing boundary is
the devtools are not what we want to lean on -- I'm working on some refactoring in ember to allow folks to just not ship any of the devtools support in production. Context would still need to work, and is minimally much much different from the debugRenderTree
We do not want the owner to have any access to the render tree. rendering is completely independent of the owner.
this RFC already describes that, as designed, we could use the same mechanism for getOwner() -- but that's conceptually with one provided at the root of the render tree. (In reality though, I don't know if we'll end up using contexts to implement parameter-less getOwner(), because the renderer already knows what the current owner is (note that the owner does not know anything about what is happening in the renderer))
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not really where I was aiming at: The
<obj.Prop>way of syntax is new here.I had this idea:
then use it:
It's your API, but presented a little different, which I think makes it more familiar looking (
<ThemeProvider>instead of<obj.Prop>).I still also prefer an imperative API, too (both declarative + imperative).
Good to keep the debug code ejetable. Context + Debugging/Tooling is what I consider stakeholders to that API.
Yeah, the owner was what I used to describe it. It's not the owner, but I dunno what its been called either. If that can be made available as a stable API for debug tooling as well as for context (so addons can use it). That's what I consider to be the essence in here (Context is then only a high-level API to it).