-
Notifications
You must be signed in to change notification settings - Fork 54
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Break out README contents into GitBook docs
- Loading branch information
Showing
18 changed files
with
696 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
root: docs | ||
|
||
structure: | ||
readme: overview.md | ||
summary: contents.md |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
# Table of contents | ||
|
||
- [Overview](overview.md) | ||
- [Getting Started](getting-started.md) | ||
|
||
## Using Glint | ||
|
||
- Ember | ||
- [Installation](ember/installation.md) | ||
- [Imports](ember/imports.md) | ||
- [Component Signatures](ember/component-signatures.md) | ||
- [Template Registry](ember/template-registry.md) | ||
- [Routes and Controllers](ember/routes-and-controllers.md) | ||
- [Template-Only Components](ember/template-only-components.md) | ||
- [Rendering Tests](ember/rendering-tests.md) | ||
- [Contextual Components](ember/contextual-components.md) | ||
- [Typing Your Dependencies](ember/typing-your-dependencies.md) | ||
- GlimmerX | ||
- [Installation](glimmerx/installation.md) | ||
- [Imports](glimmerx/imports.md) | ||
- [Component Signatures](glimmerx/signatures.md) | ||
- [Template Components](glimmerx/template-components.md) | ||
- [Known Limitations](known-limitations.md) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
While Glimmer components accept `Args` as a type parameter, and Ember components accept no type parameters at all, the Glint version of each accepts `Signature`, which contains types for `Element`, `Args` and `Yields`. | ||
|
||
The `Args` field functions the same way the `Args` type parameter does with normal `@glimmer/component` usage. | ||
|
||
The `Element` field declares what type of element(s), if any, the component applies its passed `...attributes` to. This is often the component's root element. Tracking this type ensures any modifiers used on your component will be compatible with the DOM element(s) they're ultimately attached to. If no `Element` is specified, it will be a type error to set any HTML attributes when invoking your component. | ||
|
||
The `Yields` field specifies the names of any blocks the component yields to, as well as the type of any parameter(s) they'll receive. See the [Yieldable Named Blocks RFC] for further details. | ||
(Note that the `inverse` block is an alias for `else`. These should be defined in `Yields` as `else`, though `{{yield to="inverse"}}` will continue to work.) | ||
|
||
## Glimmer Components | ||
|
||
{% code title="app/components/super-table.ts" %} | ||
|
||
```typescript | ||
import Component from '@glint/environment-ember-loose/glimmer-component'; | ||
|
||
export interface SuperTableSignature<T> { | ||
// We have a `<table>` as our root element | ||
Element: HTMLTableElement; | ||
// We accept an array of items, one per row | ||
Args: { | ||
items: Array<T>; | ||
}; | ||
// We accept two named blocks: an optional `header`, and a required | ||
// `row`, which will be invoked with each item and its index. | ||
Yields: { | ||
header?: []; | ||
row: [item: T, index: number]; | ||
}; | ||
} | ||
|
||
export default class SuperTable<T> extends Component<SuperTableSignature<T>> {} | ||
``` | ||
|
||
{% endcode %} | ||
|
||
{% code title="app/components/super-table.hbs" %} | ||
|
||
```handlebars | ||
{{! app/components/super-table.hbs }} | ||
<table ...attributes> | ||
{{#if (has-block 'header')}} | ||
<thead> | ||
<tr>{{yield to='header'}}</tr> | ||
</thead> | ||
{{/if}} | ||
<tbody> | ||
{{#each @items as |item index|}} | ||
<tr>{{yield item index to='row'}}</tr> | ||
{{/each}} | ||
</tbody> | ||
</table> | ||
``` | ||
|
||
{% endcode %} | ||
|
||
## Ember Components | ||
|
||
Since Ember components don't have `this.args`, it takes slightly more boilerplate to make them typesafe. | ||
|
||
{% code title="app/components/greeting.ts" %} | ||
|
||
```typescript | ||
import Component, { ArgsFor } from '@glint/environment-ember-loose/ember-component'; | ||
import { computed } from '@ember/object'; | ||
|
||
export interface GreetingSignature { | ||
Args: { | ||
message: string; | ||
target?: string; | ||
}; | ||
Yields: { | ||
default: [greeting: string]; | ||
}; | ||
} | ||
|
||
// This line declares that our component's args will be 'splatted' on to the instance: | ||
export default interface Greeting extends ArgsFor<GreetingSignature> {} | ||
export default class Greeting extends Component<GreetingSignature> { | ||
@computed('target') | ||
private get greetingTarget() { | ||
// Therefore making `this.target` a legal `string | undefined` property access: | ||
return this.target ?? 'World'; | ||
} | ||
} | ||
``` | ||
|
||
{% endcode %} | ||
|
||
{% code title="app/components/greeting.hbs" %} | ||
|
||
```handlebars | ||
{{yield (concat @message ', ' this.greetingTarget '!')}} | ||
``` | ||
|
||
{% endcode %} | ||
|
||
Ember components also support `PositionalArgs` in their signature. Such usage is relatively rare, but components such as [`{{animated-if}}`](https://github.com/ember-animation/ember-animated) do take advantage of it. `PositionalArgs` are specified using a tuple type in the same way that block parameters are. You can also include `PositionalArgs` in the signature passed to `ComponentLike` (see below) when declaring types for third-party components. | ||
|
||
Note that both `Element` and `PositionalArgs` are not fully integrated with the string-based APIs on the `@ember/component` base class. This means, for example that there's no enforcement that `tagName = 'table'` and `Element: HTMLTableElement` are actually correlated to one another. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
When you yield a contextual component, e.g. `{{yield (component "my-component" foo="bar")}}`, you need some way to declare the type of that value in your component signature. For this you can use the `ComponentLike` type, or the `ComponentWithBoundArgs` shorthand. | ||
|
||
```typescript | ||
interface MyComponentSignature { | ||
Element: HTMLInputElement; | ||
Args: { | ||
value: string; | ||
count: number; | ||
}; | ||
} | ||
|
||
class MyComponent extends Component<MyComponentSignature> { | ||
// ... | ||
} | ||
``` | ||
|
||
Given a component like the one declared above, if you wrote this in some other component's template: | ||
|
||
```handlebars | ||
{{yield (hash foo=(component 'my-component'))}} | ||
``` | ||
|
||
You could type your `Yields` as: | ||
|
||
```typescript | ||
Yields: { | ||
default: [{ foo: typeof MyComponent }] | ||
} | ||
``` | ||
|
||
However, if you pre-set the value of the `@value` arg, the consumer shouldn't _also_ need to set that arg. You need a way to declare that that argument is now optional: | ||
|
||
```typescript | ||
import { ComponentWithBoundArgs } from '@glint/environment-ember-loose'; | ||
|
||
// ... | ||
|
||
Yields: { | ||
default: [{ foo: ComponentWithBoundArgs<typeof MyComponent, 'value'> }] | ||
} | ||
``` | ||
If you had pre-bound multiple args, you could union them together with the `|` type operator, e.g. `'value' | 'count'`. | ||
Note that `ComponentWithBoundArgs` is effectively shorthand for writing out a `ComponentLike<Signature>` type, which is a generic type that any Glimmer component, Ember component or `{{component}}` return value is assignable to, assuming it has a matching signature. | ||
```typescript | ||
import { ComponentLike } from '@glint/environment-ember-loose'; | ||
|
||
type AcceptsAFooString = ComponentLike<{ Args: { foo: string } }>; | ||
|
||
const Ember: AcceptsAFooString = class extends EmberComponent<{ Args: { foo: string } }> {}; | ||
const Glimmer: AcceptsAFooString = class extends GlimmerComponent<{ Args: { foo: string } }> {}; | ||
const Bound: AcceptsAFooString = /* the result of `{{component "ember-component"}}` */ | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
In order for GlimmerX entities to be interpretable by Glint, you currently need to use Glint-specific import paths for `@glimmer/component`, `@ember/component` and `ember-modifier`. Note that this is not a long-term restriction, but a temporary workaround for the current state of the ecosystem. | ||
|
||
| Vanilla Ember | Ember + Glint | | ||
| -------------------------------- | -------------------------------------------------------------- | | ||
| `@glimmer/component` | `@glint/environment-ember-loose/glimmer-component` | | ||
| `@ember/component` | `@glint/environment-ember-loose/ember-component` | | ||
| `@ember/component/helper` | `@glint/environment-ember-loose/ember-component/helper` | | ||
| `@ember/component/template-only` | `@glint/environment-ember-loose/ember-component/template-only` | | ||
| `ember-modifier` | `@glint/environment-ember-loose/ember-modifier` | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
To use Glint with [Ember](https://github.com/emberjs/ember.js), you'll add the `@glint/core` and `@glint/environment-ember-loose` packages to your project's `devDependencies`, then create a `.glintrc.yml` in the root of your project. | ||
|
||
{% tabs %} | ||
{% tab title="Yarn" %} | ||
|
||
```sh | ||
yarn add --dev @glint/core @glint/environment-ember-loose | ||
``` | ||
|
||
{% endtab %} | ||
{% tab title="npm" %} | ||
|
||
```sh | ||
npm install -D @glint/core @glint/environment-ember-loose | ||
``` | ||
|
||
{% endtab %} | ||
{% endtabs %} | ||
|
||
{% code title=".glintrc.yml" %} | ||
|
||
```yaml | ||
environment: ember-loose | ||
include: | ||
- 'app/**' | ||
``` | ||
{% endcode %} | ||
Note that specifying `include` globs is optional, but may be a useful way to incrementally migrate your project to Glint over time. | ||
|
||
To minimize spurious errors when typechecking with vanilla `tsc` or your editor's TypeScript integration, you should add `import '@glint/environment-ember-loose';` somewhere in your project's source or type declarations. You may also choose to disable TypeScript's "unused symbol" warnings in your editor, since `tsserver` won't understand that templates actually are using them. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
Templates rendered in tests using `ember-cli-htmlbars`'s `hbs` tag will be checked the same way as standalone `hbs` files. | ||
|
||
```typescript | ||
import { render } from '@ember/test-helpers'; | ||
import { hbs } from 'ember-cli-htmlbars'; | ||
|
||
test('MyComponent works', async function (assert) { | ||
// If `@arg` is declared to be a string, you'll get a squiggle here | ||
await render(hbs`<MyComponent @arg={{123}} />`); | ||
|
||
assert.dom().hasText('...'); | ||
}); | ||
``` | ||
|
||
In some TypeScript codebases it's common practice to define per-module (or even per-test) context types that include additional properties. If you do this and need to access these properties in your template, you can include the context type as a parameter to `render`. | ||
|
||
```typescript | ||
import { render } from '@ember/test-helpers'; | ||
import { hbs } from 'ember-cli-htmlbars'; | ||
import type { TestContext } from '@ember/test-helpers'; | ||
|
||
interface MyContext extends TestContext { | ||
message: string; | ||
} | ||
|
||
test('MyComponent works', async function (this: MyContext, assert) { | ||
this.message = 'hello'; | ||
|
||
await render<MyContext>(hbs` | ||
<MyComponent @arg={{this.message}} /> | ||
`); | ||
|
||
assert.dom().hasText('...'); | ||
}); | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
Templates associated with Ember routes and/or controllers will be typechecked against those backing classes without needing to import from Glint-specific paths. | ||
|
||
If a controller class exists, then `@model` in the corresponding template will have the type of the controller's declared `model` property, and `{{this}}` will be the type of the controller itself. | ||
|
||
```typescript | ||
export default class MyController extends Controller { | ||
declare model: MyModelType; | ||
|
||
greeting = 'Hello, world!'; | ||
} | ||
``` | ||
|
||
```handlebars | ||
{{this}} {{! MyController }} | ||
{{this.greeting}} {{! string }} | ||
{{this.model}} {{! MyModelType }} | ||
{{@model}} {{! MyModelType }} | ||
``` | ||
|
||
If no controller exists but a route does, then `{{@model}}` will be the return type of the route's `model()` hook (unwrapping any promise if necessary), and `{{this}}` will be the type of an empty controller with a `model` property of the same type as `@model`. | ||
|
||
```typescript | ||
export default class MyRoute extends Route { | ||
async model(): Promise<MyModelType> { | ||
// ... | ||
} | ||
} | ||
``` | ||
|
||
```handlebars | ||
{{this}} {{! Controller & { model: MyModelType } }} | ||
{{this.model}} {{! MyModelType }} | ||
{{@model}} {{! MyModelType }} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
A template-only component is any template for which Ember (and Glint) can't locate a backing TS or JS module. In Glint, these are treated very similarly to a component with an empty signature: it has no args, and it can't yield to blocks or apply `...attributes` anywhere. Additionally, the value of `{{this}}` in such a template will be `void`. | ||
|
||
While it's possible to do some simple things like invoking other components from these templates, typically you'll want to create a backing module for your template so you can declare its signature, add it to the template registry, and so on. | ||
|
||
```typescript | ||
import templateOnlyComponent from '@glint/environment-ember-loose/ember-component/template-only'; | ||
|
||
interface ShoutSignature { | ||
Element: HTMLDivElement; | ||
Args: { message: string }; | ||
Yields: { | ||
default: [shoutedMessage: string]; | ||
}; | ||
} | ||
|
||
const Shout = templateOnlyComponent<ShoutSignature>(); | ||
|
||
export default Shout; | ||
|
||
declare module '@glint/environment-ember-loose/registry' { | ||
export default interface Registry { | ||
Shout: typeof Shout; | ||
} | ||
} | ||
``` | ||
|
||
Note that the runtime content of this module (effectively `export default templateOnlyComponent();`) is exactly what Ember generates at build time when creating a backing module for a template-only component. |
Oops, something went wrong.