Skip to content

Commit

Permalink
Break out README contents into GitBook docs
Browse files Browse the repository at this point in the history
  • Loading branch information
dfreeman committed Apr 7, 2022
1 parent 76c3936 commit 1c04bf4
Show file tree
Hide file tree
Showing 18 changed files with 696 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .gitbook.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
root: docs

structure:
readme: overview.md
summary: contents.md
23 changes: 23 additions & 0 deletions docs/contents.md
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)
102 changes: 102 additions & 0 deletions docs/ember/component-signatures.md
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.
55 changes: 55 additions & 0 deletions docs/ember/contextual-components.md
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"}}` */
```
9 changes: 9 additions & 0 deletions docs/ember/imports.md
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` |
32 changes: 32 additions & 0 deletions docs/ember/installation.md
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.
35 changes: 35 additions & 0 deletions docs/ember/rendering-tests.md
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('...');
});
```
34 changes: 34 additions & 0 deletions docs/ember/routes-and-controllers.md
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 }}
```
27 changes: 27 additions & 0 deletions docs/ember/template-only-components.md
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.
Loading

0 comments on commit 1c04bf4

Please sign in to comment.