Giter VIP home page Giter VIP logo

Comments (5)

dfreeman avatar dfreeman commented on May 28, 2024 3

Extending upstream types is the name of the game—it's how pretty much all of Glint works 🙂

from rfcs.

NullVoxPopuli avatar NullVoxPopuli commented on May 28, 2024

Reserving this post for my hacks and tradeoffs as I explore how to use Glint with gts and managers.


While writing this, I had an idea -- what if library authors provided a way for certain constructs to tie in to Glint in strict mode?

so, for example, for Resources, I'd do something like this:

// ember-resources/glint.ts
import type { HelperLike } from "@glint/template";
// ArgsWrapper is the traditional { named: {}, positional: [] } (abbrv)
import type { Resource, type ArgsWrapper } from './core';

type PositionalArgsOf<T extends ArgsWrapper> = T['positional'];
type NamedArgsOf<T extends ArgsWrapper> = T['named'];

interface Resource<Args extends ArgsWrapper> {
  [InTemplate]: HelperLike<{ 
    Args: { 
      Positional: PositionalArgsOf<Args>;
      Named: NamedArgsOf<Args> 
    } 
  }>;
}

Then Glint could check for existence of the [InTemplate] symbol and not have to figure out how to statically implement manager-lookup logic.

Glint could do something like

ExpandSignature<S> = S extends HasInTemplate ? S[InTemplate] : S

TS playground here


Resources

resources are reactive utilities that can easily be used in both JS and templates, and are completely type safe (except in Glint, where the assumption for HelperLike exists).
resources are helpers that can be used in JS with little-to-no ceremony.
resources may or may not have their own internal state.

tl;dr:

what I expect to work
import { tracked } from '@glimmer/tracking';
import { Resource } from 'ember-resources/core';

import type { TemplateOnlyComponent as TOC } from '@ember/component/template-only';

class ShadowHost extends Resource {
  @tracked value: ShadowRoot | undefined;

  update = (nextValue: ShadowRoot) => (this.value = nextValue);
}

const attachShadow = ((element: Element, setShadow: ShadowRoot['update']) => {
  setShadow(element.attachShadow({ mode: 'open' }));
});

// index.html has the production-fingerprinted references to these links
// Ideally, we'd have some pre-processor scan everything for references to
// assets in public, but idk how to set that up
const getStyles = () => {
  return [...document.head.querySelectorAll('link')].map(link => link.href);;
}

type Shadowed = TOC<{ Blocks: { default: [] } }>

<template signature:Shadowed>
  {{#let (ShadowHost) as |shadow|}}
    <div data-shadow {{attachShadow shadow.update}}></div>

    {{#if shadow.value}}
      {{#in-element shadow.value}}
        {{#let (getStyles) as |styles|}}
          {{#each styles as |styleHref|}}
            <link rel="stylesheet" href={{styleHref}}>
          {{/each}}
        {{/let}}

        {{yield}}
      {{/in-element}}
    {{/if}}
  {{/let}}
</template>

Note that I'm perfectly aware I could use modifier from ember-modifier, but that's not the point of this 😅

what I have to do today
import { tracked } from '@glimmer/tracking';
import { Resource } from 'ember-resources/core';

import type { TemplateOnlyComponent as TOC } from '@ember/component/template-only';
import type { ModifierLike, HelperLike } from "@glint/template";


/**
 * Every custom-manager using object needs to have two types.
 * - one for templates / glint
 * - one for JS/TS
 *
 *
 * Because Glint doesn't have an integration with the managers,
 * this complexity is pushed into user space.
 *
 * See issue report:
 *   https://github.com/emberjs/rfcs/issues/822#issuecomment-1140541910
 */
export class ShadowHost extends Resource {
  @tracked value: ShadowRoot | undefined;

  update = (nextValue: ShadowRoot) => (this.value = nextValue);
}

/**
 * Glint does not tie into any of the managers.
 * See issue report:
 *   https://github.com/emberjs/rfcs/issues/822
 */
const state = ShadowHost as unknown as HelperLike<{ Args: {}; Return: ShadowHost }>;


type UpdateFn = ShadowHost['update'];

const attachShadow = ((element: Element, setShadow: UpdateFn) => {
  setShadow(element.attachShadow({ mode: 'open' }));
/**
 * Because Glint doesn't have an integration with the managers,
 * this complexity is pushed into user space.
 *
 * See issue report:
 *   https://github.com/emberjs/rfcs/issues/822
 */
}) as unknown as ModifierLike<{ Args: { Positional: [UpdateFn] }}> ;

// index.html has the production-fingerprinted references to these links
// Ideally, we'd have some pre-processor scan everything for references to
// assets in public, but idk how to set that up
const getStyles = () => {
  return [...document.head.querySelectorAll('link')].map(link => link.href);;
}

const Shadowed: TOC<{
  Blocks: { default: [] }
}> =
<template>
  {{#let (state) as |shadow|}}
    <div data-shadow {{attachShadow shadow.update}}></div>

    {{#if shadow.value}}
      {{#in-element shadow.value}}
        {{#let (getStyles) as |styles|}}
          {{#each styles as |styleHref|}}
            <link rel="stylesheet" href={{styleHref}}>
          {{/each}}
        {{/let}}

        {{yield}}
      {{/in-element}}
    {{/if}}
  {{/let}}
</template>

export default Shadowed;
related issues

typed-ember/glint#340
typed-ember/glint#339


Normally, for templates, you'd add an entry into this structure:

import State from 'wherevere';

declare module "@glint/environment-ember-loose/registry" {
  export default interface Registry {
    state: HelperLike<{ Args: {}, Return: State }>;
  }
}

However, this does not work in strict mode, because your usage code looks like this:

import state from 'limber/helpers/state';

import type { TemplateOnlyComponent as TOC } from '@ember/component/template-only';

const Shadowed: TOC<{
  Blocks: { default: [] }
}> =
<template>
  {{#let (state) as |shadow|}}
      .... 
  {{/let}}
</template>

the type of state is used instead of whatever is in the registry -- which makes sense!

To remedy this _for template usage only, and break usage in regular TS files, I have to do:

const State = (this part actually doesn't matter);

export default State as unknown as HelperLike<{ Args: {}, Return: State}>;

As a per-app convention, one could employ a brittle (because human-enforced) convention of exporting two different things like this:

export const State = (this part actually doesn't matter);
export default State as unknown as HelperLike<{ Args: {}, Return: State}>;

So js/ts users would import { State } ... and Glint would read the default export.

however since in strict-mode, we can have things defined anywhere, defined locally, etc, there is no way to have a locally defined Resource (or any utility implemented with their own managers) that allow usage in JS and TS -- you'd have to do this hack every time you wanted to use something in a template:

const MyThing = '...'
const MyThingButForTemplates = '...' as unknown as HelperLike

from rfcs.

dfreeman avatar dfreeman commented on May 28, 2024

Ok there's a lot to cover here, so I'm going to take it in order.

For folks wanting to use glint, and have their custom-manager implementations support TS, there is no path here.

Glint was designed from the ground up to support this; the entire point of the @glint/environment-* packages is to provide bindings between Glint's notion of how template types work and arbitrary implementations of components/helpers/modifiers. Those packages aren't privileged in any way, though, and the core doesn't have special knowledge of them—that's how we were able to prototype glint-environment-ember-template-imports in a standalone library before incorporating it into the monorepo.

I think we should figure out a path for managers to provide the types to Glint,

Managers are kind of a red herring for providing types to Glint, as the TS type system has no simple way of modeling "a value that has had this function called on it". We explored approaches to making that work early on, but ultimately it also turns out the manager type isn't 100% useful to use for working out how something is going to behave in a template even if we have it.

all of these use custom managers and have no way to utilize typed templates

Have you checked Glint's issue tracker? We've had conversations about how to do this 🙂

Atm, @glimmer/component, specifically has ExpandSignature: typed-ember/glint@0e31e49/packages/template/-private/index.d.ts
RFC here: #748
So (atm), I'm kinda thinking we need some sort of "ExpandSignature" type thing per syntax, rather than per implementation.

I think you've misunderstood what ExpandSignature is. It doesn't actually have anything to do with making Glint work—it is just about, well, "expanding a signature" from its shorthand form to the full longhand one as specified in the RFC you linked. The reason there's no helper or modifier equivalent is that there is no shorthand for @ember/component/helper or ember-modifier signatures. Other base component/helper/modifier implementations can use any structure they like to capture the relevant type information, as long as the key pieces are there somewhere (which you can see in the example below).

what if library authors provided a way for certain constructs to tie in to Glint in strict mode?

It's not specific to strict mode, but you're slowly working your way toward reinventing how Glint already works 😉

I'm not super familiar with the shape of the resource base class, but you should be able to write something along these lines to make Glint aware of how Resource subclasses behave in templates:

interface Resource<T extends ArgsWrapper> extends InstanceType<
  HelperLike<{
    Args: { Named: T['named']; Positional: T['positional'] };
    Return: IDontKnowHowToGetTheValueTypeOfAResourceButItGoesHere
  }>
> {}

This is exactly how we integrate Ember's own base classes into Glint (for example, here's Helper).

from rfcs.

wagenet avatar wagenet commented on May 28, 2024

It sounds like @dfreeman is saying that the issues @NullVoxPopuli identified already have solutions. Is this a correct understanding? Is there any path forward for this ticket?

from rfcs.

NullVoxPopuli avatar NullVoxPopuli commented on May 28, 2024

Sort of, we have my specific issue solved by extending upstream / third-party types like here: https://github.com/NullVoxPopuli/ember-statechart-component/blob/main/ember-statechart-component/src/glint.ts#L39

I can't speak for the typed-ember folks, but if this is the path forward for all situations like this, I'm happy with closing this

from rfcs.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.