TypeScript-powered tooling for Glimmer templates.
Glint is a set of tools to aid in developing code that uses the Glimmer VM for rendering, such as Ember.js and GlimmerX projects. Similar to Vetur for Vue projects, Glint consists of a CLI and a language server to provide feedback and enforce correctness both locally during editing and project-wide in CI.
You'll first need to add @glint/core
and an appropriate Glint environment to your project's devDependencies
. Currently there are two provided environments: @glint/environment-glimmerx
for GlimmerX projects and @glint/environment-ember-loose
for Ember.js projects.
# If you use Yarn for package management in an Ember project, for example:
yarn add --dev @glint/core @glint/environment-ember-loose
Next, you'll add a .glintrc.yml
file to the root of your project (typically alongside your tsconfig.json
file). This file tells Glint what environment you're working in and, optionally, which files it should include in its typechecking:
# .glintrc.yml
environment: ember-loose
include:
- app/**
Finally, you may choose to install an editor extension to display Glint's diagnostics inline in your templates and provide richer editor support, such as the VS Code Extension.
The @glint/core
package includes two executables: glint
and glint-language-server
.
The glint
CLI can be used to typecheck your project in a similar manner to tsc
, but with understanding of how values flow through templates.
You can use the glint
executable in CI to ensure you maintain type safety in your templates. For example, you might replace an ember-try
config's tsc
invocation with glint
like this:
- command: "tsc --noEmit",
+ command: "glint",
You can also use the glint
command locally with the --watch
flag to monitor your project as you work!
Similarly, glint-language-server
can be used by editor integrations to expose that same information inline as you type.
The language server can also enable your editor to provide other richer help, such as type information on hover, automated refactoring, and more. See the VS Code extension README for further examples.
In order for GlimmerX entities to be interpretable by Glint, you currently need to use Glint-specific import paths for @glimmerx/modifier
, @glimmerx/helper
and @glimmerx/component
. Note that this is not a long-term restriction, but a temporary workaround for the current state of the ecosystem.
Vanilla GlimmerX | GlimmerX + Glint |
---|---|
@glimmerx/component |
@glint/environment-glimmerx/component |
@glimmerx/modifier |
@glint/environment-glimmerx/modifier |
@glimmerx/helper |
@glint/environment-glimmerx/helper |
Note: because of the way @glimmerx/babel-plugin-component-templates
currently works, you must still import hbs
from @glimmerx/component
or your templates will not be compiled.
While GlimmerX components accept Args
as a type parameter, the Glint version accepts Signature
, which contains types for Element
, Args
and Yields
.
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.
import Component from '@glint/environment-glimmerx/component';
import { hbs } from '@glimmerx/component';
export interface ShoutSignature {
// We have a `<div>` as our root element
Element: HTMLDivElement;
// We accept one required argument, `message`
Args: {
message: string;
};
// We yield a single string to the default block, `shoutedMessage`
Yields: {
default?: [shoutedMessage: string];
};
}
export class Shout extends Component<ShoutSignature> {
private get louderPlease() {
return `${this.args.message.toUpperCase()}!`;
}
public static template = hbs`
<div ...attributes>
{{yield this.louderPlease}}
</div>
`;
}
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-modifier |
@glint/environment-ember-loose/ember-modifier |
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
. These three fields behave in the same way as they do for GlimmerX components, detailed above in that Component Signatures section.
// app/components/super-table.ts
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>> {}
Since Ember components don't have this.args
, it takes slightly more boilerplate to make them typesafe.
// app/components/greeting.ts
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';
}
}
Because Ember's template resolution occurs dynamically at runtime today, Glint needs a way of mapping the names used in your templates to the actual backing value they'll be resolved to. This takes the form of a "type registry" similar to the one that powers Ember Data's types.
The recommended approach is to include a declaration in each component, modifier or helper module that adds it to the registry, which is the default export of @glint/environment-ember-loose/registry
.
// app/components/greeting.ts
import Component from '@glint/environment-ember-loose/glimmer-component';
export default class Greeting extends Component {
// ...
}
declare module '@glint/environment-ember-loose/registry' {
export default interface Registry {
Greeting: typeof Greeting;
}
}
As mentioned above, Glint is not yet stable and is still under active development. As such, there are currently several known limitations to be aware of.
Currently, users must import Glint-aware versions of Component
and other values that are re-exported from their Glint environment rather than being able to directly use the "native" versions. The details of this requirement are explained in the environment-specific "Using Glint" sections above.
This is not a permanent limitation, but rather a result of the base types for those entities not (yet) being extensible in the ways necessary to capture their template behavior. As we're able to make adjustments to those upstream types across the ecosystem to ensure they're extensible, we should be able to shift to use declaration merging instead of completely re-exporting.
Once that's done, consumers should be able to import values the "normal" way directly from the sources packages like @glimmer/component
.
Glint is not currently integrated with ember-cli-typescript
, so typechecking performed during an ember-cli
build will not take templates into account.
In addition, only pod-based and colocated components with backing classes are currently supported. That is, the following are not yet checkable with Glint:
- template-only components
- classic layout components
- route templates
render(...)
calls in tests
Finally, the template registry described in the "With Ember.js" section above must currently be maintained by hand. A few possibilities for mitigating that pain have been discussed, but ultimately the best solution will be when strict mode comes to Ember and we no longer need to reckon with runtime resolution of template entities.
The CLI and language server currently have a few known limitations:
- The CLI does not yet support composite projects or
tsc
's--build
mode. - The language server will only operate on projects with a
.glintrc
at their root, not in a subdirectories of the workspace root. - In VS Code, you will see diagnostics from both TypeScript and Glint in many files, as well as false 'unused symbol' positives for things only referenced in templates. See the VS Code extension README for details on dealing with this.