Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
270 changes: 13 additions & 257 deletions packages/@ember/-internals/glimmer/lib/renderer.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { privatize as P } from '@ember/-internals/container';

Check failure on line 1 in packages/@ember/-internals/glimmer/lib/renderer.ts

View workflow job for this annotation

GitHub Actions / tests / Linting

'P' is defined but never used. Allowed unused vars must match /^_/u
import { ENV } from '@ember/-internals/environment';
import type { InternalOwner } from '@ember/-internals/owner';
import { getOwner } from '@ember/-internals/owner';

Check failure on line 4 in packages/@ember/-internals/glimmer/lib/renderer.ts

View workflow job for this annotation

GitHub Actions / tests / Linting

'getOwner' is defined but never used. Allowed unused vars must match /^_/u
import { guidFor } from '@ember/-internals/utils';
import { getViewElement, getViewId } from '@ember/-internals/views';

Check failure on line 6 in packages/@ember/-internals/glimmer/lib/renderer.ts

View workflow job for this annotation

GitHub Actions / tests / Linting

'getViewElement' is defined but never used. Allowed unused vars must match /^_/u
import { assert } from '@ember/debug';
import { _backburner, _getCurrentRunLoop } from '@ember/runloop';
import {
Expand All @@ -15,24 +15,24 @@
} from '@glimmer/destroyable';
import { DEBUG } from '@glimmer/env';
import type {
Bounds,

Check failure on line 18 in packages/@ember/-internals/glimmer/lib/renderer.ts

View workflow job for this annotation

GitHub Actions / tests / Linting

'Bounds' is defined but never used. Allowed unused vars must match /^_/u
Cursor,
DebugRenderTree,
Environment,
DynamicScope as GlimmerDynamicScope,

Check failure on line 22 in packages/@ember/-internals/glimmer/lib/renderer.ts

View workflow job for this annotation

GitHub Actions / tests / Linting

'GlimmerDynamicScope' is defined but never used. Allowed unused vars must match /^_/u
RenderResult as GlimmerRenderResult,
Template,
TemplateFactory,

Check failure on line 25 in packages/@ember/-internals/glimmer/lib/renderer.ts

View workflow job for this annotation

GitHub Actions / tests / Linting

'TemplateFactory' is defined but never used. Allowed unused vars must match /^_/u
EvaluationContext,
CurriedComponent,

Check failure on line 27 in packages/@ember/-internals/glimmer/lib/renderer.ts

View workflow job for this annotation

GitHub Actions / tests / Linting

'CurriedComponent' is defined but never used. Allowed unused vars must match /^_/u
TreeBuilder,
ClassicResolver,
} from '@glimmer/interfaces';

import type { Nullable } from '@ember/-internals/utility-types';

Check failure on line 32 in packages/@ember/-internals/glimmer/lib/renderer.ts

View workflow job for this annotation

GitHub Actions / tests / Linting

'Nullable' is defined but never used. Allowed unused vars must match /^_/u
import { artifacts, RuntimeOpImpl } from '@glimmer/program';
import type { Reference } from '@glimmer/reference';
import { createConstRef, UNDEFINED_REFERENCE, valueForRef } from '@glimmer/reference';

Check failure on line 35 in packages/@ember/-internals/glimmer/lib/renderer.ts

View workflow job for this annotation

GitHub Actions / tests / Linting

'UNDEFINED_REFERENCE' is defined but never used. Allowed unused vars must match /^_/u

Check failure on line 35 in packages/@ember/-internals/glimmer/lib/renderer.ts

View workflow job for this annotation

GitHub Actions / tests / Linting

'createConstRef' is defined but never used. Allowed unused vars must match /^_/u
import type { CurriedValue } from '@glimmer/runtime';
import {
clientBuilder,
Expand Down Expand Up @@ -62,47 +62,7 @@
import { makeRouteTemplate } from './component-managers/route-template';
import { EvaluationContextImpl } from '@glimmer/opcode-compiler';

export type IBuilder = (env: Environment, cursor: Cursor) => TreeBuilder;

export interface View {
parentView: Nullable<View>;
renderer: Renderer;
tagName: string | null;
elementId: string | null;
isDestroying: boolean;
isDestroyed: boolean;
[BOUNDS]: Bounds | null;
}

export class DynamicScope implements GlimmerDynamicScope {
constructor(
public view: View | null,
public outletState: Reference<OutletState | undefined>
) {}

child() {
return new DynamicScope(this.view, this.outletState);
}

get(key: 'outletState'): Reference<OutletState | undefined> {
assert(
`Using \`-get-dynamic-scope\` is only supported for \`outletState\` (you used \`${key}\`).`,
key === 'outletState'
);
return this.outletState;
}

set(key: 'outletState', value: Reference<OutletState | undefined>) {
assert(
`Using \`-with-dynamic-scope\` is only supported for \`outletState\` (you used \`${key}\`).`,
key === 'outletState'
);
this.outletState = value;
return value;
}
}

const NO_OP = () => {};
type IBuilder = (env: Environment, cursor: Cursor) => TreeBuilder;

// This wrapper logic prevents us from rerendering in case of a hard failure
// during render. This prevents infinite revalidation type loops from occuring,
Expand Down Expand Up @@ -205,7 +165,7 @@
template: Template,
self: Reference<unknown>,
parentElement: SimpleElement,
dynamicScope: DynamicScope,

Check failure on line 168 in packages/@ember/-internals/glimmer/lib/renderer.ts

View workflow job for this annotation

GitHub Actions / tests / Type Checking (current version)

Cannot find name 'DynamicScope'.
builder: IBuilder
) {
assert(
Expand Down Expand Up @@ -276,10 +236,6 @@

const renderers: BaseRenderer[] = [];

export function _resetRenderers() {
renderers.length = 0;
}

function register(renderer: BaseRenderer): void {
assert('Cannot register the same renderer twice', renderers.indexOf(renderer) === -1);
renderers.push(renderer);
Expand Down Expand Up @@ -363,7 +319,7 @@
builder: IBuilder;
}

export class RendererState {
class RendererState {
static create(data: RendererData, renderer: BaseRenderer): RendererState {
const state = new RendererState(data, renderer);
associateDestroyableChild(renderer, state);
Expand Down Expand Up @@ -613,6 +569,16 @@
* so passing additional things here is also considered private API)
*/
[rendererOption: string]: unknown;

// Proposed public API?
// this family of functions could render to anything
// (terminal, mobile, webgl, database, whatever)
emitText;
emitComponent;
emitElement;
applyAttributes;
applyModifier;
emitTemplate;
};

/**
Expand Down Expand Up @@ -685,13 +651,7 @@
document: SimpleDocument | Document,
options: { isInteractive: boolean; hasDOM?: boolean }
) {
return new BaseRenderer(
owner,
{ hasDOM: hasDOM, ...options },
document as SimpleDocument,
new ResolverImpl(),
clientBuilder
);
/* ... */
}

readonly state: RendererState;
Expand Down Expand Up @@ -763,208 +723,4 @@
rerender(): void {
this.state.scheduleRevalidate(this);
}

// render(component: Component, options: { into: Cursor; args?: Record<string, unknown> }): void {
// this.state.renderRoot(component);
// }
}

export class Renderer extends BaseRenderer {
static strict(
owner: object,
document: SimpleDocument | Document,
options: { isInteractive: boolean; hasDOM?: boolean }
): BaseRenderer {
return new BaseRenderer(
owner,
{ hasDOM: hasDOM, ...options },
document as SimpleDocument,
new ResolverImpl(),
clientBuilder
);
}

private _rootTemplate: Template;
private _viewRegistry: ViewRegistry;

static create(props: { _viewRegistry: any }): Renderer {
let { _viewRegistry } = props;
let owner = getOwner(props);
assert('Renderer is unexpectedly missing an owner', owner);
let document = owner.lookup('service:-document') as SimpleDocument;
let env = owner.lookup('-environment:main') as {
isInteractive: boolean;
hasDOM: boolean;
};
let rootTemplate = owner.lookup(P`template:-root`) as TemplateFactory;
let builder = owner.lookup('service:-dom-builder') as IBuilder;
return new this(owner, document, env, rootTemplate, _viewRegistry, builder);
}

constructor(
owner: InternalOwner,
document: SimpleDocument,
env: { isInteractive: boolean; hasDOM: boolean },
rootTemplate: TemplateFactory,
viewRegistry: ViewRegistry,
builder = clientBuilder,
resolver = new ResolverImpl()
) {
super(owner, env, document, resolver, builder);
this._rootTemplate = rootTemplate(owner);
this._viewRegistry = viewRegistry || owner.lookup('-view-registry:main');
}

// renderer HOOKS

appendOutletView(view: OutletView, target: SimpleElement): void {
// 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 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;

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(
view,
curry(0 as CurriedComponent, definition, this.state.owner, null, true),
target
);
}

_appendDefinition(
root: OutletView | ClassicComponent,
definition: CurriedValue,
target: SimpleElement
): void {
let self = createConstRef(definition, 'this');
let dynamicScope = new DynamicScope(null, UNDEFINED_REFERENCE);
let rootState = new ClassicRootState(
root,
this.state.context,
this.state.owner,
this._rootTemplate,
self,
target,
dynamicScope,
this.state.builder
);
this.state.renderRoot(rootState, this);
}

cleanupRootFor(component: ClassicComponent): void {
// no need to cleanup roots if we have already been destroyed
if (isDestroyed(this)) {
return;
}

let roots = this.state.roots;

// traverse in reverse so we can remove items
// without mucking up the index
let i = roots.length;
while (i--) {
let root = roots[i];
assert('has root', root);
if (root.type === 'classic' && root.isFor(component)) {
root.destroy();
roots.splice(i, 1);
}
}
}

remove(view: ClassicComponent): void {
view._transitionTo('destroying');

this.cleanupRootFor(view);

if (this.state.isInteractive) {
view.trigger('didDestroyElement');
}
}

get _roots() {
return this.state.debug.roots;
}

get _inRenderTransaction() {
return this.state.debug.inRenderTransaction;
}

get _isInteractive() {
return this.state.debug.isInteractive;
}

get _context() {
return this.state.context;
}

register(view: any): void {
let id = getViewId(view);
assert(
'Attempted to register a view with an id already in use: ' + id,
!this._viewRegistry[id]
);
this._viewRegistry[id] = view;
}

unregister(view: any): void {
delete this._viewRegistry[getViewId(view)];
}

getElement(component: View): Nullable<Element> {
if (this._isInteractive) {
return getViewElement(component);
} else {
throw new Error(
'Accessing `this.element` is not allowed in non-interactive environments (such as FastBoot).'
);
}
}

getBounds(component: View): {
parentElement: SimpleElement;
firstNode: SimpleNode;
lastNode: SimpleNode;
} {
let bounds: Bounds | null = component[BOUNDS];

assert('object passed to getBounds must have the BOUNDS symbol as a property', bounds);

let parentElement = bounds.parentElement();
let firstNode = bounds.firstNode();
let lastNode = bounds.lastNode();

return { parentElement, firstNode, lastNode };
}
}
31 changes: 31 additions & 0 deletions packages/@ember/runtime/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
*
* Can we design a runtime that is both fast and eliminates complexity from our language server tooling?
*
*/
export function emitText(text: string): void {}
export function emitComponent<Signature>(
component: ComponentLike<Signature>,
args: Args
): ElementFor<Signature> {}

export function emitElement<TagName extends string>(
tagName: TagName,
staticAttributes: AttributesFor<TagName>
): Element {}

/**
* to elements and components
*/
export function applyAttributes<Element>(
element: Element,
dynamicAttributes: Record<string, () => string>
) {}
export function applyModifier<Element>(element: Element, modifier: ModifierLike, args: Args) {}

/**
* e.g.: <template shadowrootmode="open">
*
* TODO: I don't know how this would work yet
*/
export function emitTemplate() {}
9 changes: 9 additions & 0 deletions packages/@ember/runtime/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"name": "@ember/runtime",
"private": true,
"type": "module",
"exports": {
".": "./index.ts"
},
"dependencies": {}
}
Loading