Skip to content
Open
Show file tree
Hide file tree
Changes from 12 commits
Commits
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
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -192,10 +192,12 @@
"@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.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
25 changes: 13 additions & 12 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,7 +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 { BootEnvironment } from '@ember/engine/instance';
export type { OutletState, RenderState } from './lib/utils/outlet';
export {
componentCapabilities,
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.

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)


/**
* 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
Expand Up @@ -3,14 +3,14 @@ import type { Nullable } from '@ember/-internals/utility-types';
import { assert } from '@ember/debug';
import type EngineInstance from '@ember/engine/instance';

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

move to the route-managers/classic/ directory - this is where its only consumers are

import { _instrumentStart } from '@ember/instrumentation';
import { precompileTemplate } from '@ember/template-compilation';
import type {
CompilableProgram,
ComponentDefinition,
CustomRenderNode,
Destroyable,
Environment,
InternalComponentCapabilities,
TemplateFactory,
VMArguments,
WithCreateInstance,
WithCustomDebugRenderTree,
Expand All @@ -23,7 +23,6 @@ import { unwrapTemplate } from './unwrap-template';

import type { DynamicScope } from '../renderer';
import type { OutletState } from '../utils/outlet';
import type OutletView from '../views/outlet';

function instrumentationPayload(def: OutletDefinitionState) {
// "main" used to be the outlet name, keeping it around for compatibility
Expand All @@ -43,10 +42,9 @@ export interface OutletDefinitionState {
name: string;

/**
* 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.
* What this outlet renders. 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;
Expand Down Expand Up @@ -180,16 +178,6 @@ class OutletComponentManager

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

// 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,
OutletInstanceState,
Expand All @@ -204,12 +192,9 @@ export class OutletComponent implements ComponentDefinition<

constructor(
owner: InternalOwner,
public state: OutletDefinitionState
public state: OutletDefinitionState,
template: TemplateFactory
) {
this.compilable = unwrapTemplate(OUTLET_COMPONENT_TEMPLATE(owner)).asLayout();
this.compilable = unwrapTemplate(template(owner)).asLayout();
}
}

export function createRootOutlet(outletView: OutletView): OutletComponent {
return new OutletComponent(outletView.owner, outletView.state);
}
46 changes: 4 additions & 42 deletions packages/@ember/-internals/glimmer/lib/renderer.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { privatize as P } from '@ember/-internals/container/lib/registry';
import type { InternalOwner } from '@ember/-internals/owner';
import { getOwner } from '@ember/-internals/owner';
import { guidFor } from '@ember/-internals/utils/lib/guid';
import { getViewElement, getViewId } from '@ember/-internals/views/lib/system/utils';
import { assert } from '@ember/debug';
import {
Expand All @@ -26,21 +25,17 @@ import type { Reference } from '@glimmer/reference/lib/reference';
import { createConstRef, UNDEFINED_REFERENCE, valueForRef } from '@glimmer/reference/lib/reference';
import type { CurriedValue } from '@glimmer/runtime/lib/curried-value';
import { clientBuilder } from '@glimmer/runtime/lib/vm/element-builder';
import { createCapturedArgs, EMPTY_POSITIONAL } from '@glimmer/runtime/lib/vm/arguments';
import { curry } from '@glimmer/runtime/lib/curried-value';
import { inTransaction } from '@glimmer/runtime/lib/environment';
import { renderMain } from '@glimmer/runtime/lib/render';
import { dict } from '@glimmer/util/lib/collections';
import { unwrapTemplate } from './component-managers/unwrap-template';
import type { SimpleDocument, SimpleElement, SimpleNode } from '@simple-dom/interface';
import type Component from './component';
import type ClassicComponent from './component';
import { BOUNDS } from './component-managers/curly';
import { createRootOutlet } from './component-managers/outlet';
import { RootComponentDefinition } from './component-managers/root';
import RouterResolver from './router-resolver';
import type { OutletState } from './utils/outlet';
import OutletView from './views/outlet';
import type { IBuilder, RendererRoot } from './base-renderer';
import { BaseRenderer, errorLoopTransaction } from './base-renderer';

Expand All @@ -53,6 +48,7 @@ export {
errorLoopTransaction,
renderComponent,
renderSettled,
setRenderer,
_resetRenderers,
} from './base-renderer';

Expand Down Expand Up @@ -103,7 +99,7 @@ class ClassicRootState implements RendererRoot {
readonly env: Environment;

constructor(
public root: Component | OutletView,
public root: Component,
context: EvaluationContext,
owner: object,
template: Template,
Expand All @@ -117,7 +113,7 @@ class ClassicRootState implements RendererRoot {
template !== undefined
);

this.id = root instanceof OutletView ? guidFor(root) : getViewId(root);
this.id = getViewId(root);
this.result = undefined;
this.destroyed = false;
this.env = context.env;
Expand Down Expand Up @@ -216,36 +212,6 @@ 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 { invokable } = view.state;
assert('[BUG] OutletView state is unexpectedly missing its root invokable', invokable);

let named = dict<Reference>();
named['Component'] = createConstRef(invokable, '@Component');

let args = createCapturedArgs(named, EMPTY_POSITIONAL);

this._appendDefinition(
view,
curry(0 as CurriedComponent, outlet, view.owner, args, true),
target
);
}

appendTo(view: ClassicComponent, target: SimpleElement): void {
let definition = new RootComponentDefinition(view);
this._appendDefinition(
Expand All @@ -255,11 +221,7 @@ export class Renderer extends BaseRenderer {
);
}

_appendDefinition(
root: OutletView | ClassicComponent,
definition: CurriedValue,
target: SimpleElement
): void {
_appendDefinition(root: ClassicComponent, definition: CurriedValue, target: SimpleElement): void {
let self = createConstRef(definition, 'this');
let dynamicScope = new DynamicScope(null, UNDEFINED_REFERENCE);
let rootState = new ClassicRootState(
Expand Down
3 changes: 0 additions & 3 deletions packages/@ember/-internals/glimmer/lib/router-resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,8 @@ import type { InternalOwner } from '@ember/-internals/owner';
import type { Nullable } from '@ember/-internals/utility-types';
import ResolverImpl from './resolver';
import { mountHelper } from './syntax/mount';
import { outletHelper } from './syntax/outlet';

const ROUTER_KEYWORD_HELPERS: Record<string, object> = {
'-mount': mountHelper,
'-outlet': outletHelper,
};

/**
Expand Down
5 changes: 0 additions & 5 deletions packages/@ember/-internals/glimmer/lib/setup-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@ import LinkTo from './components/link-to';
import Textarea from './components/textarea';
import { clientBuilder, rehydrationBuilder, serializeBuilder } from './dom';
import { Renderer } from './renderer';
import OutletTemplate from './templates/outlet';
import RootTemplate from './templates/root';
import OutletView from './views/outlet';

export function setupApplicationRegistry(registry: Registry): void {
// because we are using injections we can't use instantiate false
Expand Down Expand Up @@ -42,9 +40,6 @@ export function setupApplicationRegistry(registry: Registry): void {
export function setupEngineRegistry(registry: Registry): void {
registry.optionsForType('template', { instantiate: false });

registry.register('view:-outlet', OutletView);
registry.register('template:-outlet', OutletTemplate as any);

registry.optionsForType('helper', { instantiate: false });

registry.register('component:input', Input);
Expand Down
2 changes: 1 addition & 1 deletion packages/@ember/-internals/glimmer/lib/templates/outlet.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { precompileTemplate } from '@ember/template-compilation';
import { outletHelper } from '../syntax/outlet';

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

as above

import { outletHelper } from '../../../routing/route-managers/classic/outlet';

export default precompileTemplate(`{{component (outletHelper)}}`, {
moduleName: 'packages/@ember/-internals/glimmer/lib/templates/outlet.hbs',
Expand Down
Loading
Loading