Polaris: What I'm Working On
I've been spending my time doing a lot of long-overdue work on Ember, Glimmer and the Handlebars parser. I figured I'd post a quick list of the work.
Simplified Runtime Component Renderer
In broad strokes, I'm working on making it straight-forward to take a .gjs
or .gts
component and run it in the browser.
A renderComponent
API
Status: Initial PR (with passing tests). Needs a feature flag and RFC.
This PR creates a new top-level API to render a single component rather than an entire Ember app.
const Hello = <template>Hello {{@name}}</template>
renderComponent(<template><Hello @name="Person" /></template>, {
into: someElement
});
Components rendered this way have access to all of Ember's keywords, interact properly with Ember's scheduler, and can freely use any other Ember component, modifier or helper.
They are also expected to be able to share services with running Ember apps by passing an owner
as an option to renderComponent
. Once this lands (🤞) and gets used in the real world, we'll work on finding the right patterns to make owner-sharing ergonomic.
Runtime Template Compiler
Status: Complete PR that implements an existing RFC (#931). Pending final review.
This PR implements the runtime implementation of RFC #931 ("JS Representation of Template Tag").
In short, it means that that you can preprocess this code into something with a runtime implementation:
const name = "world";
class Hello extends Component {
<template>Hello {{name}}</template>
}
The RFC specifies an explicit form, which explicitly enumerates all of the JavaScript variables used in the template:
import { template } from "@ember/template-compiler";
const name = "world";
class Example extends Component {
static {
template(
"Hello {{message}}",
{
component: this,
scope: () => ({ message }),
},
);
}
}
and an implicit form, which is the simplest and fastest way to preprocess the GJS format that has a viable runtime implementation:
import { template } from "@ember/template-compiler";
class Example extends Component {
static {
template(
"Hello {{message}}",
{
component: this,
eval() { return eval(arguments[0]) },
},
);
}
}
This is simpler and more efficient to produce because it can be produced directly from the original <template>
syntax without the need for additional scope traversal.
Why is this helpful? Among other things, it's a good fit for the Vite development environment, which uses native browser modules to keep the feedback loop between module changes and runtime updates small. In this environment, the simpler you can make the transformation from a custom syntax like GJS to a runnable syntax like the one I outlined below, the better the feedback loop will be.
This is also nice for in-browser REPLs, which can use a simple preprocessor to convert<template>
to the impliciteval
form and not need to ship a more complicated toolchain that can analyze the scope of the file, and keep that analysis up to date as JavaScript syntax evolves.
TL;DR I implemented the runtime version of the "JS Representation of Template Tag" RFC, and it's now pending review.
Component Inspector Fixes
Status: Landed in glimmer-vm
.
If you're using <template>
syntax, you might have noticed that the inspector has gotten a lot less useful. Rather than showing you the name of the component you invoked, it's full of a lot of (unnamedTemplateOnlyComponent)
😱.
The root cause of this regression is complicated, and the fix is also a little involved.
At the root, though, is an update to the internals of the DebugRenderTree
so that it attempts to follow a simple rule:
The inspector should represent<ComponentInvocations
and<p {{modifierNames}}
as you wrote them in your source file.
There's more work to be done to make this true in all cases, but it should be true in the vast majority of cases now. Please report bugs if you see something in the inspector that doesn't match what you wrote in the source code. We know of some situations already, but the more cases you report, the more cases we can turn into tests and fix.
This change has landed in Glimmer VM and will make its way into Ember soon.
New Glimmer Syntax
Another category of work I've been doing is adding a handful of new syntaxes to the Handlebars parser:
this.#private
field syntax(hash=literal)
syntax[array literal]
syntax
Private Fields Syntax
Status: Landed in the parser. Implements part of an existing RFC (#931). Needs additional implementation in Glimmer VM and Ember to make it work in Ember.
This change makes it possible to access private fields in GJS component classes:
export default class extends Component {
#target = "world";
<template>
Hello {{this.#target}}
</template>
}
There are a lot of reasons to want this, but here are the two that are on my mind:
- It's a gap in our template syntax if you can store something in a field in a JavaScript class but can't access it in a template.
- This will become more pressing once we fully adopt Stage 3 decorators in Ember. Stage 3 decorators will allow you to say
@tracked accessor #target
, and we don't want people to be forced to create public fields simply because private ones aren't accessible in templates.
For fun, check out this speculative gist that I wrote in 2015 about how we could use private fields and decorators to improve Ember's data flow model. Given all the time that has gone by and everything that has happened to decorators and private fields since then, I'm surprised at how much of this lines up with my current thinking on the topic.
Hash and Array Literals
Status: Landed in the parser. Needs a feature flag and RFC. Needs additional implementation in Glimmer VM and Ember to make it work in Ember.
Ember currently has two special keywords (array
and hash
) that serve as array literals and object literals in Ember's templates.
I recently landed a syntax change to the Handlebars parser that creates literal syntaxes for arrays and hashes.
Array Literals:
<template>
<Hello @people={{["Tom" "Katie" @person]}} />
</template>
Object Literals:
<template>
<Greet @greeting={{hello="Hello" target="world"}} />
</template>
Object literals work inside of mustaches like above, but also in ()
like this:
<template>
{{greet
[
(greeting="Good night" target="sky")
(greeting="Good night" target="air")
(greeting="Good night" target="noises everywhere")
]
}}
</template>
function greet(greetings: { greeting: string, target: string }[]) {
return greetings.map(g => `${g.hello} ${g.target}`).join(". ");
}
Array literals derive their syntax from positional argument lists in Handlebars. Object literals derive their syntax from named argument lists in Handlebars.
In my opinion, this is the obvious "follow-your-nose" answer to array and object literals in Handlebars.
Because Handlebars already has positional and named parameters, and because lists and records are almost universally present in programming languages, I think this also makes sense in language-agnostic Handlebars.
These new syntaxes have already landed in handlebars-parser
and need additional implementation work to make their way into Ember. That said, since there are existing, approved RFCs to make array
and hash
built-in keywords, the work to bring the syntactic forms to Ember should be a small delta on top of that implementation. I plan to work on those keywords next.