Comments (5)
Extending upstream types is the name of the game—it's how pretty much all of Glint works 🙂
from rfcs.
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
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
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.
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.
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.
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)
- Standardize the use of yarn and npm scripts in the Ember experience, for test and start HOT 11
- V2 addons' build-time integration HOT 4
- Deprecate all of Ember Classic HOT 16
- Build-time configuration of index.html HOT 3
- Deprecate support for Travis CI HOT 6
- Deprecate `ember-mocha`? HOT 2
- Deprecate `ember-export-application-global` addon? HOT 4
- Run Prettier separately in `app` blueprint HOT 9
- Deprecate `app.import`
- Thoughts on this more ergonomic way to wire up the owner + destroyable association? HOT 2
- Explore "official" pod deprecation HOT 19
- {{else}} should render a value rather than be a control-flow keyword. HOT 5
- new primitive: transition, similar to modifiers, except they block certain render events HOT 2
- Numbers in PR titles affect automation
- Asset import spec RFC HOT 2
- Implement import spec RFC HOT 1
- Replacing `moduleName` in template meta HOT 11
- Simplified imports for common built-in modules HOT 2
- Pre-RFC: Add `aria-current` attribute to Ember's `LinkTo` component HOT 1
- Whitespace handling for `<template>` tag HOT 4
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
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.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from rfcs.