Skip to content
Open
Show file tree
Hide file tree
Changes from 61 commits
Commits
Show all changes
89 commits
Select commit Hold shift + click to select a range
b7128a1
Implement the Route Manager API (RFC #1169)
evoactivity Jun 9, 2026
68781db
fix typescript problem
evoactivity Jun 10, 2026
7582a9f
moves the route manager types up a level to avoid the nonsense the ty…
evoactivity Jun 15, 2026
07c5731
update tree shakability snapshots
evoactivity Jun 15, 2026
67fbf1d
add a few missing public exports
evoactivity Jun 15, 2026
347cefd
ensure classic substate lookups only run for classic manager routes
evoactivity Jun 15, 2026
c3b21ec
remove wrapper from base RouteStateBucket interface
evoactivity Jun 15, 2026
20fcd21
remove name from classic route wrapper
evoactivity Jun 15, 2026
72c3303
remove lookup intercept
evoactivity Jun 24, 2026
d84de44
add a getRenderState to the manager contract
evoactivity Jun 24, 2026
da6320d
change classic route manager wrapper to use context instead of model
evoactivity Jun 24, 2026
a71f713
fix broken getAncestorContext
evoactivity Jun 25, 2026
02aea99
EmberRouter.getRoute now returns unknown
evoactivity Jun 25, 2026
edb1ebf
Merge branch 'main' into rfc-1169-route-manager
evoactivity Jun 25, 2026
2fc7e31
try to fix tree-shakability test
evoactivity Jun 25, 2026
c088ca8
update tree-shakability snapshot
evoactivity Jun 25, 2026
985d3c6
remove intercept
evoactivity Jun 25, 2026
1663f21
refactor: add manager.getRoute(bucket)
BobrImperator Jun 25, 2026
6be29bb
remove isBaseRoute
evoactivity Jun 25, 2026
f487008
Merge pull request #2 from mainmatter/bart/rfc-1169-route-manager
evoactivity Jun 29, 2026
2cd2a65
remove fallback path for legacy templates and upgrade templates passe…
evoactivity Jun 29, 2026
5cc6e62
keep `getRoute` as returning `unknown`
evoactivity Jun 29, 2026
dd3d59a
uses the bucket to pass in the controller to the wrapper component
evoactivity Jun 29, 2026
83ce52e
ensure RouteStateBucket is fully opaque
evoactivity Jun 29, 2026
ac6c449
remove knowledge of controller's from the route-template component ma…
evoactivity Jun 30, 2026
68f01b8
move const ref args from outlet helper to route manager
evoactivity Jun 30, 2026
3cc6787
add comment explaining why we need to await the enter promise to back…
evoactivity Jun 30, 2026
5a344c4
pass enterPromise directly to resolved route info
evoactivity Jun 30, 2026
ffa1b8e
undo route info passthrough
evoactivity Jul 1, 2026
b040925
add getRoute to route manager contract
evoactivity Jul 1, 2026
29d6bdc
- fable found dead code, remove it
evoactivity Jul 2, 2026
81f9dab
fable improved how manager and bucket are accessed in the framework: …
evoactivity Jul 2, 2026
ccb71e4
remove classic lifecycle duplication and route substate handling thro…
evoactivity Jul 2, 2026
6d32e47
simplify the outlet render path
evoactivity Jul 2, 2026
c815301
restore two classic behaviors dropped by the manager extraction, with…
evoactivity Jul 2, 2026
0377177
small cleanups across the route lookup and ancestor-context paths
evoactivity Jul 2, 2026
0b7fe86
keep the getRouteWrapper module-stable, make the wrapper optional, an…
evoactivity Jul 2, 2026
796dc96
deliver all outlet args via the curried render target
evoactivity Jul 2, 2026
d5da652
Merge remote-tracking branch 'upstream/main' into rfc-1169-route-manager
evoactivity Jul 2, 2026
166f902
rename managed route functions to route management
evoactivity Jul 2, 2026
945b6bf
fix lints
evoactivity Jul 3, 2026
a93e6cf
remove removed barrel file from renamed-modules
evoactivity Jul 3, 2026
0f6de0d
feat: add a produeContext callback to routeManager
BobrImperator Jul 6, 2026
e51fee2
Merge branch 'main' into rfc-1169-route-manager
BobrImperator Jul 16, 2026
68fc807
small experiment with a new @outlet
mansona Jul 8, 2026
c0522c5
fix: invoke outlet helper before passing it as named arg
BobrImperator Jul 8, 2026
4b0f767
feat: transform {{outlet}} to <@outlet />
BobrImperator Jul 9, 2026
b9663d8
WIP: fix debug render tree?
mansona Jul 9, 2026
eb25131
try to use renderComponent for toplevel
mansona Jul 13, 2026
4ce9867
fix: error while rendering outlet without dynamicScope
BobrImperator Jul 13, 2026
e942972
feat: creat a root-outlet.ts to replace the -outlet view
BobrImperator Jul 13, 2026
7b90594
refactor: cleanup OutletView and OutletTemplate
BobrImperator Jul 13, 2026
eb11aa1
fix: pass renderer to resolve {{mount}}. Fix rootElement on boot
BobrImperator Jul 14, 2026
9d626c1
wip: bulk AI test fixes
BobrImperator Jul 14, 2026
809cbc4
fix: avoid circular dependency cycle
BobrImperator Jul 15, 2026
6382317
refactor: uncomment debug-render-tree tests
BobrImperator Jul 16, 2026
523d52e
run format
BobrImperator Jul 16, 2026
91ba5f6
refactor: move outlet beside route-manager
BobrImperator Jul 20, 2026
2f5eac9
chore: use @ember-test-helpers patch emberjs/ember-test-helpers#1573
BobrImperator Jul 20, 2026
9c6f170
fix: change the node-tests harness to align with emberjs/ember-test-h…
BobrImperator Jul 20, 2026
e9541dc
Merge pull request #4 from mainmatter/test-new-outlet
BobrImperator Jul 20, 2026
9944b17
test: scenario funky route manager
BobrImperator Jul 21, 2026
89fad30
Merge pull request #5 from mainmatter/rfc-1169-scenario-funky-route-m…
BobrImperator Jul 22, 2026
6479e4b
feat: re-export makeRouteTemplate for pioneer-manager
BobrImperator Jul 27, 2026
4ce8cc8
wip: remove OutletComponent construction from outlet
BobrImperator Jul 28, 2026
cf6abee
wip: cache outlet renders again
BobrImperator Jul 28, 2026
5f6b811
wip: probes
BobrImperator Jul 27, 2026
4531356
fix: wrapper doesn't need to double call helper
BobrImperator Jul 28, 2026
e8d4faf
refactor: move outlet component caching into OutletComponent
BobrImperator Jul 28, 2026
a18167b
perf: build @outlet in prepareArgs instead of calling (outlet)
BobrImperator Jul 28, 2026
d72e0ac
perf: select the outlet layout via getDynamicLayout
BobrImperator Jul 29, 2026
1c7ddd8
refactor: move producing the context value to the OutletComponent
BobrImperator Jul 28, 2026
156c47c
refactor: drop unread controller from OutletDefinitionState
BobrImperator Jul 28, 2026
b4db280
refactor: add getRenderInvokable to manager
BobrImperator Jul 29, 2026
be50cad
refactor: remove outletState channel and outletHelper
BobrImperator Jul 29, 2026
f2ace86
feat: kill wrapper manager
BobrImperator Aug 4, 2026
7dbd25a
feat: encapsulate classic in OutletComponent
BobrImperator Aug 4, 2026
3e05dd1
refactor: remove manager specific fields from renderState
BobrImperator Aug 4, 2026
2d7c98a
feat: remove wrapper -> outlet is the wrapper now
BobrImperator Aug 4, 2026
8d77b14
cleanup: remove probes
BobrImperator Aug 5, 2026
4d64588
chore: cleanup comments
BobrImperator Aug 5, 2026
3cbdaa1
refactor: reactivity via BucketRenderState
BobrImperator Aug 5, 2026
7d4fb50
refactor: make renderstate tracked
BobrImperator Aug 5, 2026
1f8d011
refactor: remove unnecessary static method
BobrImperator Aug 5, 2026
582dda2
refactor: remove getRenderContext, cleanup unused owner
BobrImperator Aug 5, 2026
66cc2e7
refactor: merge outlet-template with manager
BobrImperator Aug 5, 2026
9cf90fc
Collapse route rendering layers into manager and outlet-component
BobrImperator Aug 5, 2026
2364c8e
Merge remote-tracking branch 'mm/main' into rfc-1169-route-manager
BobrImperator Aug 5, 2026
fcb7291
Merge remote-tracking branch 'mm/main' into rfc-1169-route-manager
BobrImperator Aug 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ test/version_tmp
test_*.html
/tests/ember-tests.js
/smoke-tests/scenarios/output/
/smoke-tests/scenarios/output-*/
tmp
tmp*.gem
tmp.bpm
Expand Down
17 changes: 15 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,8 @@
"ember-cli-htmlbars": "^7.0.0",
"babel-plugin-ember-template-compilation": "^4.0.0",
"ember-cli-babel": "^8.3.1",
"testem": "^3.20.1"
"testem": "^3.20.1",
"@ember/test-helpers": "5.4.3"
},
"onlyBuiltDependencies": [
"@swc/core",
Expand All @@ -164,7 +165,8 @@
],
"patchedDependencies": {
"@tracerbench/core@8.0.1": "patches/@tracerbench__core@8.0.1.patch",
"agadoo": "patches/agadoo.patch"
"agadoo": "patches/agadoo.patch",
"@ember/test-helpers@5.4.3": "patches/@ember__test-helpers@5.4.3.patch"
}
},
"peerDependencies": {
Expand All @@ -189,6 +191,17 @@
"@ember/-internals/metal/index.js": "ember-source/@ember/-internals/metal/index.js",
"@ember/-internals/owner/index.js": "ember-source/@ember/-internals/owner/index.js",
"@ember/-internals/routing/index.js": "ember-source/@ember/-internals/routing/index.js",
"@ember/-internals/routing/route-managers/api.js": "ember-source/@ember/-internals/routing/route-managers/api.js",
"@ember/-internals/routing/route-managers/classic/bucket.js": "ember-source/@ember/-internals/routing/route-managers/classic/bucket.js",
"@ember/-internals/routing/route-managers/classic/manager.js": "ember-source/@ember/-internals/routing/route-managers/classic/manager.js",
"@ember/-internals/routing/route-managers/classic/outlet-manager.js": "ember-source/@ember/-internals/routing/route-managers/classic/outlet-manager.js",
"@ember/-internals/routing/route-managers/classic/outlet-template.js": "ember-source/@ember/-internals/routing/route-managers/classic/outlet-template.js",
"@ember/-internals/routing/route-managers/classic/outlet.js": "ember-source/@ember/-internals/routing/route-managers/classic/outlet.js",
"@ember/-internals/routing/route-managers/classic/query-params.js": "ember-source/@ember/-internals/routing/route-managers/classic/query-params.js",
"@ember/-internals/routing/route-managers/classic/substates.js": "ember-source/@ember/-internals/routing/route-managers/classic/substates.js",
"@ember/-internals/routing/route-managers/classic/wrapper.js": "ember-source/@ember/-internals/routing/route-managers/classic/wrapper.js",
"@ember/-internals/routing/route-managers/registry.js": "ember-source/@ember/-internals/routing/route-managers/registry.js",
"@ember/-internals/routing/route-managers/root-outlet.js": "ember-source/@ember/-internals/routing/route-managers/root-outlet.js",
"@ember/-internals/runtime/index.js": "ember-source/@ember/-internals/runtime/index.js",
"@ember/-internals/runtime/lib/ext/rsvp.js": "ember-source/@ember/-internals/runtime/lib/ext/rsvp.js",
"@ember/-internals/runtime/lib/mixins/-proxy.js": "ember-source/@ember/-internals/runtime/lib/mixins/-proxy.js",
Expand Down
26 changes: 13 additions & 13 deletions packages/@ember/-internals/glimmer/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
The documentation for `@ember/component` still refers to the older authoring format. To read about
the new authoring format, see the
[Glimmer Component API documentation](/ember/release/modules/@glimmer%2Fcomponent).

### Showing a property

Templates manage the flow of an application's UI, and display state (through
Expand Down Expand Up @@ -117,7 +117,7 @@

```app/templates/application.gjs
import LabeledTextField from '../components/labeled-textfield';

<template>
<LabeledTextField @value={{@model.name}}>
First name:
Expand All @@ -127,7 +127,7 @@

```app/components/labeled-textfield.gjs
import { Input } from '@ember/component';

<template>
<label>
{{yield}} <Input @value={{@value}} />
Expand All @@ -148,12 +148,12 @@
```app/templates/application.gjs
import Component from '@glimmer/component';
import LabeledTextField from '../components/labeled-textfield';

export default class Application extends Component {
firstNameValidator = (value) => {
// validates
}

<template>
<LabeledTextField @value={{@model.validation}} @validator={{this.firstNameValidator}} as |validationError|>
{{#if validationError}}
Expand All @@ -167,7 +167,7 @@

```app/components/labeled-textfield.gjs
import { Input } from '@ember/component';

<template>
<label>
{{yield this.validationError}} <Input @value={{@value}} />
Expand All @@ -188,7 +188,7 @@

```app/templates/application.gjs
import DateRanges from '../components/date-ranges';

<template>
<DateRanges @value={{@model.date}} as |range|>
Start date: {{range.start}}
Expand All @@ -215,10 +215,10 @@
```

Multiple values can be yielded as block params:

```app/templates/application.gjs
import Banner from '../components/banner';

<template>
<Banner @value={{@model}} as |title subtitle body|>
<h1>{{title}}</h1>
Expand Down Expand Up @@ -271,7 +271,7 @@
Title = Title;
Subtitle = Subtitle;
Body = Body;

<template>
<div>
{{yield (hash
Expand Down Expand Up @@ -408,7 +408,7 @@
`{{(has-block-params)}}` indicates if the component was invoked with block params.

This component is invoked with block params:

```handlebars
<MyComponent as |favoriteFlavor|>
Hi Jen!
Expand Down Expand Up @@ -516,6 +516,7 @@ export {
_resetRenderers,
renderSettled,
renderComponent,
setRenderer,
type View,
} from './lib/renderer';
export {
Expand All @@ -533,8 +534,7 @@ export { DOMChanges, NodeDOMTreeConstruction, DOMTreeConstruction } from './lib/
// TODO just test these through public API
// a lot of these are testing how a problem was solved
// rather than the problem was solved
export { default as OutletView, type BootEnvironment } from './lib/views/outlet';
export type { OutletState, RenderState } from './lib/utils/outlet';
export type { BootEnvironment } from '@ember/engine/instance';
export {
componentCapabilities,
modifierCapabilities,
Expand Down
21 changes: 20 additions & 1 deletion packages/@ember/-internals/glimmer/lib/base-renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -462,12 +462,18 @@ export function renderComponent(
env,
into,
args,
appendIntoTarget = false,
}: {
/**
* The element to render the component in to.
*/
into: IntoTarget;

/**
* Appends `into` without clearing the target first. Mimics the `appendTo` behavior for classic components.
*/
appendIntoTarget?: boolean;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Porting over a comment from @NullVoxPopuli

this will need a bunch more tests <3
in particular:

renderCompnoent(A, { appendTo: IntoTarget })
renderComponent(B, { appendTo: IntoTarget })

and do the reactivity of A and B stay in the same location and not re-order themselves?
(This was the issue I ran in to during implementation, in that whatever was updated last would become last in DOM-order)
(also, I think this behavior will need its own RFC, describing the stability of render-order (and update-order), and probably appendTo rather than a booling, imo -- boolean configurable options are a bit of an ick for me)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

we should pull this out in to a separate PR and discuss test cases here -- I tried to implement this in an earlier iteration of renderComponent, and backed it out due to ordering-inconsistencies upon update of tracked state within the root region of the rendered area

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

potentially related: #21551

(rendering in to "Node"s rather than elements)


/**
* Optional owner. Defaults to `{}`, can be any object, but will need to implement the [Owner](https://api.emberjs.com/ember/release/classes/Owner) API for components within this render tree to access services.
*/
Expand Down Expand Up @@ -534,7 +540,7 @@ export function renderComponent(
* Because destruction is async, it won't be safe to
* do this again, and we'll have to rely on the above destroy.
*/
if (!existing && into instanceof Element) {
if (!(appendIntoTarget || existing) && into instanceof Element) {
into.innerHTML = '';
}

Expand Down Expand Up @@ -562,6 +568,11 @@ export function renderComponent(

if (innerResult) {
associateDestroyableChild(owner, innerResult);
registerDestructor(innerResult, () => {
if (RENDER_CACHE.get(into)?.glimmerResult === innerResult) {
RENDER_CACHE.delete(into);
}
});
}

let result: RenderResult = {
Expand All @@ -580,6 +591,14 @@ export function renderComponent(
const RENDER_CACHE = new WeakMap<IntoTarget, RenderCacheEntry>();
const RENDERER_CACHE = new WeakMap<object, BaseRenderer>();

/**
* The application seeds its `renderer:-dom` service which allows for
* router-aware resolver to resolve {{mount}}
*/
export function setRenderer(owner: object, renderer: BaseRenderer): void {
RENDERER_CACHE.set(owner, renderer);
}

export class BaseRenderer {
static strict(
owner: object,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import type { InternalOwner } from '@ember/-internals/owner';
import { _instrumentStart } from '@ember/instrumentation';
import type {
CapturedArguments,
CompilableProgram,
Expand All @@ -14,20 +13,19 @@ import type {
WithCustomDebugRenderTree,
} from '@glimmer/interfaces';
import type { Nullable } from '@ember/-internals/utility-types';
import { DEBUG } from '@glimmer/env';
import { capabilityFlagsFrom } from '@glimmer/manager/lib/util/capabilities';
import type { Reference } from '@glimmer/reference/lib/reference';
import { createDebugAliasRef, valueForRef } from '@glimmer/reference/lib/reference';
import { UNDEFINED_REFERENCE, valueForRef } from '@glimmer/reference/lib/reference';
import { curry, type CurriedValue } from '@glimmer/runtime/lib/curried-value';
import { unwrapTemplate } from './unwrap-template';

interface RouteTemplateInstanceState {
self: Reference;
controller: unknown;
}

export interface RouteTemplateDefinitionState {
name: string;
self: Reference;
}

const CAPABILITIES: InternalComponentCapabilities = {
Expand Down Expand Up @@ -55,18 +53,10 @@ class RouteTemplateManager
{
create(
_owner: InternalOwner,
_definition: RouteTemplateDefinitionState,
args: VMArguments
definition: RouteTemplateDefinitionState,
_args: VMArguments
): RouteTemplateInstanceState {
let self = args.named.get('controller');

if (DEBUG) {
self = createDebugAliasRef!('this', self);
}

let controller = valueForRef(self);

return { self, controller };
return { self: definition.self };
}

getSelf({ self }: RouteTemplateInstanceState): Reference {
Expand All @@ -88,7 +78,7 @@ class RouteTemplateManager
type: 'route-template',
name,
args,
instance: state.controller,
instance: valueForRef(state.self),
},
];
}
Expand All @@ -111,7 +101,7 @@ class RouteTemplateManager
const ROUTE_TEMPLATE_MANAGER = /*@__PURE__*/ new RouteTemplateManager();

/**
* This "upgrades" a route template into a invocable component. Conceptually
* This "upgrades" a route template into a invokable component. Conceptually
* it can be 1:1 for each unique `Template`, but it's also cheap to construct,
* so unless the stability is desirable for other reasons, it's probably not
* worth caching this.
Expand All @@ -129,13 +119,13 @@ export class RouteTemplate implements ComponentDefinition<
public capabilities = CAPABILITIES_MASK;
public compilable: CompilableProgram;

constructor(name: string, template: Template) {
constructor(name: string, template: Template, self: Reference) {
let unwrapped = unwrapTemplate(template);
// TODO This actually seems inaccurate – it ultimately came from the
// outlet's name. Also, setting this overrides `getDebugName()` in that
// message. Is that desirable?
this.resolvedName = name;
this.state = { name };
this.state = { name, self };
this.compilable = unwrapped.asLayout();
}
}
Expand All @@ -150,8 +140,9 @@ export class RouteTemplate implements ComponentDefinition<
export function makeRouteTemplate(
owner: InternalOwner,
name: string,
template: Template
template: Template,
self: Reference = UNDEFINED_REFERENCE
): CurriedValue {
let routeTemplate = new RouteTemplate(name, template);
let routeTemplate = new RouteTemplate(name, template, self);
return curry(0 as CurriedComponent, routeTemplate, owner, null, true);
}
Loading
Loading