Skip to content

Rework Element/...attributes/modifier checking #138

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

Merged
merged 12 commits into from
Apr 8, 2021
Merged

Conversation

dfreeman
Copy link
Member

@dfreeman dfreeman commented Apr 7, 2021

Background

Support for checking ...attributes and modifiers against a component's Element was something that landed at the last minute before we went into beta, and it ended up being somewhat bolted-on (as passed attribute values were from the start) in the emitted code. This meant that we resolved component elements in a different way than we resolved their block params, and also that we were explicitly passing type parameters in certain places in emitted output, which opened the door to bugs like #133 and complicates the JS interop story.

This PR reworks how we emit TS code for elements and components in a template. Examples of the transformation before and after this PR follow.

(funny symbol details for the unfamiliar)
  • 𝚪 is a template-wide context value responsible for representing the available @args and this value as well as the declared Element type and block parameters for the component the template belongs to
  • χ is the environment-provided library of primitives used to express template constructs as TypeScript
  • 𝛄 is newly introduced in this PR and is a local context value for the element or component we're in the process of emitting, containing the type of its DOM element and, for components, the available block parameters

Elements

<div class="hello" ...attributes {{on "click" this.clicked}}>{{message}}</div>

Before

χ.applyAttributes<import("@glint/template").ElementForTagName<"div">>({ class: "hello" });
χ.applySplattributes<typeof 𝚪.element, import("@glint/template").ElementForTagName<"div">>();
χ.applyModifier<import("@glint/template").ElementForTagName<"div">>(
  χ.resolve(on)({}, "click", 𝚪.this.clicked)
);

χ.invokeEmit(χ.resolveOrReturn(𝚪.args.message)({}));

After

χ.emitElement("div", (𝛄) => {
  χ.applyAttributes(𝛄.element, { class: "hello" });
  χ.applySplattributes(𝚪.element, 𝛄.element);
  χ.applyModifier(𝛄.element, χ.resolve(on)({}, "click", 𝚪.this.clicked));

  χ.emitValue(χ.resolveOrReturn(𝚪.args.message)({}));
});

Components

<Greeting @message="hello" class="my-greeting" ...attributes as |greeting|>
  {{greeting.formattedMessage}}
</Greeting>

Before

χ.applySplattributes<typeof 𝚪.element, import("@glint/template").ElementForComponent<typeof Greeting>>();
χ.applyAttributes<import("@glint/template").ElementForComponent<typeof Greeting>>({
  class: "my-greeting"
});

χ.invokeBlock(χ.resolve(Greeting)({ message: "hello" }), {
  default(greeting) {
    χ.invokeEmit(χ.resolveOrReturn(greeting?.formattedMessage)({}));
  }
});

After

χ.emitComponent(χ.resolve(Greeting)({ message: "hello" }), (𝛄) => {
  χ.applySplattributes(𝚪.element, 𝛄.element);
  χ.applyAttributes(𝛄.element, { class: "my-greeting" });

  χ.bindBlocks(𝛄.blockParams, {
    default(greeting) {
      χ.emitValue(χ.resolveOrReturn(greeting?.formattedMessage)({}));
    }
  });
});

Notes

This change fixes #133, and eliminates the possibility of us running into similar issues in the future, as it gets us out of the "emitting paths in type-space" game entirely. You may also have noticed that the code we emit for elements and components is now 100% valid JS, which should make integration with JS backing classes for editor support more tenable in the future (though the top-level template boilerplate still includes type annotations).

This change also means that the type of Element a component has could depend on the types of its args. While that's not a pattern I love (and in many cases it would probably still require {{! @glint-ignore }} for ...attributes in the implementing template), there are some potential use cases that could be compelling, and either way it seems like a nice property for resolution of Yields and Element types to work the same way.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Can't apply attributes or modifiers to yielded components
1 participant