-
-
Notifications
You must be signed in to change notification settings - Fork 4.2k
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
Allow GJS/GTS route templates #20768
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -46,6 +46,7 @@ export interface OutletDefinitionState { | |
template: Template; | ||
controller: unknown; | ||
model: unknown; | ||
component: object | undefined; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The choice of type here reflects that a component is anything with a component manager, and having a component manager requires being a weakmap key. |
||
} | ||
|
||
const CAPABILITIES: InternalComponentCapabilities = { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -89,6 +89,9 @@ export const outletHelper = internalHelper( | |
return model; | ||
}); | ||
|
||
let componentRef = childRefFromParts(outletRef, ['render', 'component']); | ||
named['component'] = componentRef; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unlike There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If it doesn't change then something like |
||
|
||
if (DEBUG) { | ||
named['model'] = createDebugAliasRef!('@model', named['model']); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also not really sure what's up with this, does this just predate |
||
|
@@ -160,6 +163,7 @@ function stateFor( | |
name: render.name, | ||
template, | ||
controller: render.controller, | ||
component: render.component, | ||
model: render.model, | ||
}; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,6 +8,7 @@ import { tracked } from '@ember/-internals/metal'; | |
import { set } from '@ember/object'; | ||
import { backtrackingMessageFor } from '../../utils/debug-stack'; | ||
import { runTask } from '../../../../../../internal-test-helpers/lib/run'; | ||
import { template } from '@ember/template-compiler'; | ||
|
||
moduleFor( | ||
'Application test: rendering', | ||
|
@@ -553,5 +554,88 @@ moduleFor( | |
this.assertText('first'); | ||
}); | ||
} | ||
|
||
async ['@test it can use a component as the route template'](assert) { | ||
this.router.map(function () { | ||
this.route('example'); | ||
}); | ||
|
||
this.add( | ||
'route:example', | ||
Route.extend({ | ||
model() { | ||
return { | ||
message: 'I am the model', | ||
}; | ||
}, | ||
}) | ||
); | ||
|
||
this.add( | ||
'controller:example', | ||
class extends Controller { | ||
message = 'I am the controller'; | ||
} | ||
); | ||
|
||
this.add( | ||
'template:example', | ||
class extends Component { | ||
message = 'I am the component'; | ||
|
||
static { | ||
template( | ||
`<div data-test="model">{{@model.message}}</div> | ||
<div data-test="controller">{{@controller.message}}</div> | ||
<div data-test="component">{{this.message}}</div>`, | ||
{ | ||
component: this, | ||
} | ||
); | ||
} | ||
} | ||
); | ||
|
||
await this.visit('/example'); | ||
|
||
assert.strictEqual(this.$('[data-test="model"]').nodes[0]?.innerText, 'I am the model'); | ||
assert.strictEqual( | ||
this.$('[data-test="controller"]').nodes[0]?.innerText, | ||
'I am the controller' | ||
); | ||
assert.strictEqual( | ||
this.$('[data-test="component"]').nodes[0]?.innerText, | ||
'I am the component' | ||
); | ||
} | ||
|
||
async ['@test can switch between component-defined routes']() { | ||
this.router.map(function () { | ||
this.route('first'); | ||
this.route('second'); | ||
}); | ||
this.add('template:first', template('First')); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This |
||
this.add('template:second', template('Second')); | ||
await this.visit('/first'); | ||
this.assertText('First'); | ||
await this.visit('/second'); | ||
this.assertText('Second'); | ||
} | ||
|
||
async ['@test can switch from component-defined route to template-defined route']() { | ||
this.router.map(function () { | ||
this.route('first'); | ||
this.route('second'); | ||
}); | ||
this.add('template:first', template('First')); | ||
this.addTemplate( | ||
'second', | ||
'Second sees {{#if @component}}A Component{{else}}No Component{{/if}}' | ||
); | ||
await this.visit('/first'); | ||
this.assertText('First'); | ||
await this.visit('/second'); | ||
this.assertText('Second sees No Component'); | ||
} | ||
} | ||
); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -37,6 +37,7 @@ import { | |
prefixRouteNameArg, | ||
stashParamNames, | ||
} from './lib/utils'; | ||
import { hasInternalComponentManager } from '@glimmer/manager'; | ||
|
||
export interface ExtendedInternalRouteInfo<R extends Route> extends InternalRouteInfo<R> { | ||
_names?: unknown[]; | ||
|
@@ -1825,6 +1826,15 @@ export function getRenderState(route: Route): RenderState | undefined { | |
return route[RENDER_STATE]; | ||
} | ||
|
||
import { precompileTemplate } from '@ember/template-compilation'; | ||
|
||
const RoutableComponent = precompileTemplate( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I had to name it this for the LOLs. |
||
`<@component @model={{@model}} @controller={{this}} />`, | ||
{ | ||
strictMode: true, | ||
} | ||
); | ||
|
||
function buildRenderState(route: Route): RenderState { | ||
let owner = getOwner(route); | ||
assert('Route is unexpectedly missing an owner', owner); | ||
|
@@ -1836,17 +1846,31 @@ function buildRenderState(route: Route): RenderState { | |
|
||
let model = route.currentModel; | ||
|
||
let template = owner.lookup(`template:${route.templateName || name}`) as | ||
let templateOrComponent = owner.lookup(`template:${route.templateName || name}`) as | ||
| TemplateFactory | ||
| object | ||
| undefined; | ||
|
||
let template: TemplateFactory | undefined; | ||
let component: object | undefined; | ||
|
||
if (templateOrComponent) { | ||
if (hasInternalComponentManager(templateOrComponent)) { | ||
template = RoutableComponent; | ||
component = templateOrComponent; | ||
} else { | ||
template = templateOrComponent as TemplateFactory; | ||
} | ||
} | ||
|
||
let render: RenderState = { | ||
owner, | ||
into: undefined, | ||
outlet: 'main', | ||
name, | ||
controller, | ||
model, | ||
component, | ||
template: template?.(owner) ?? route._topLevelViewTemplate(owner), | ||
}; | ||
|
||
|
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.
This is a build-time feature that we added in RFC 931 but haven't used internally in ember-source yet. The whole implementation of it lives in babel-plugin-ember-template-compilation.