Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 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
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
7 changes: 7 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,13 @@
"@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/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/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
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,17 @@ interface OutletInstanceState {
export interface OutletDefinitionState {
ref: Reference<OutletState | undefined>;
name: string;
template: object;
controller: unknown;

/**
* What this outlet renders. The root `OutletView` provides the upgraded
* root template as `invokable`; per-outlet states built by the `{{outlet}}`
* helper carry the manager's `wrapper` (when present) plus `invokable` and
* `controller`, which the helper's stability check keys on.
*/
controller?: unknown;
wrapper?: object;
invokable?: object;
bucket?: object;
}

const CAPABILITIES: InternalComponentCapabilities = {
Expand Down Expand Up @@ -171,10 +180,15 @@ class OutletComponentManager

const OUTLET_MANAGER = /*@__PURE__*/ new OutletComponentManager();

const OUTLET_COMPONENT_TEMPLATE = precompileTemplate(
'<@Component @controller={{@controller}} @model={{@model}} />',
{ strictMode: true }
);
// The one outlet layout. `@Component` is always a value that already
// carries everything it needs — the outlet helper curries the args (the
// invokable/bucket/live context for wrapped renders, the live context for
// wrapper-less ones) onto the render target before handing it over, and the
// root outlet's invokable takes no args at all. Keeping the layout arg-less
// also keeps stray named args out of the debug render tree.
const OUTLET_COMPONENT_TEMPLATE = precompileTemplate('<@Component />', {
strictMode: true,
});

export class OutletComponent implements ComponentDefinition<
OutletDefinitionState,
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);
}
31 changes: 12 additions & 19 deletions packages/@ember/-internals/glimmer/lib/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ import { RootComponentDefinition } from './component-managers/root';
import RouterResolver from './router-resolver';
import type { OutletState } from './utils/outlet';
import OutletView from './views/outlet';
import { makeRouteTemplate } from './component-managers/route-template';
import type { IBuilder, RendererRoot } from './base-renderer';
import { BaseRenderer, errorLoopTransaction } from './base-renderer';

Expand Down Expand Up @@ -218,31 +217,25 @@ export class Renderer extends BaseRenderer {
// renderer HOOKS

appendOutletView(view: OutletView, target: SimpleElement): void {
// The append is scheduled into the `render` queue, so a teardown that
// starts in the same run loop (e.g. a deferred incremental outlet pass
// racing test cleanup) can destroy this renderer before the queue
// flushes. Rendering into a destroying renderer would assert deep in
// the destroyable graph; skip instead.
if (isDestroying(this) || isDestroyed(this)) {
return;
}

// TODO: This bypasses the {{outlet}} syntax so logically duplicates
// some of the set up code. Since this is all internal (or is it?),
// we can refactor this to do something more direct/less convoluted
// and with less setup, but get it working first
let outlet = createRootOutlet(view);
let { name, /* controller, */ template } = view.state;
let { invokable } = view.state;
assert('[BUG] OutletView state is unexpectedly missing its root invokable', invokable);

let named = dict<Reference>();

named['Component'] = createConstRef(
makeRouteTemplate(view.owner, name, template as Template),
'@Component'
);

// TODO: is this guaranteed to be undefined? It seems to be the
// case in the `OutletView` class. Investigate how much that class
// exists as an internal implementation detail only, or if it was
// used outside of core. As far as I can tell, test-helpers uses
// it but only for `setOutletState`.
// named['controller'] = createConstRef(controller, '@controller');
// Update: at least according to the debug render tree tests, we
// appear to always expect this to be undefined. Not a definitive
// source by any means, but is useful evidence
named['controller'] = UNDEFINED_REFERENCE;
named['model'] = UNDEFINED_REFERENCE;
named['Component'] = createConstRef(invokable, '@Component');

let args = createCapturedArgs(named, EMPTY_POSITIONAL);

Expand Down
Loading
Loading