typed-ember / ember-typings Goto Github PK
View Code? Open in Web Editor NEWTypescript type definitions for ember.js
License: MIT License
Typescript type definitions for ember.js
License: MIT License
The goal of this quest issue is to write TypeScript type definitions to cover the top 100 addons listed on Ember Observer as of the time the quest is launched. Our bonus goal is to type any other addons which want to participate, as well!
There are two overarching concerns that drive everything else in this quest:
Good type definitions are worth their weight in gold. Bad type definitions are worse than no type definitions at all. Accordingly, our goal is not just to get some type definitions written, but to write good type definitions that actually give us these benefits, and which don’t mislead us or give us a false sense of security.
We should be good citizens of the Ember.js community. We want to use TypeScript to be helpful; we do not want to be pushy jerks or typed-language fanatics. As part of the Ember.js ecosystem, we’re all in this together, and those of us working on the TypeScript side should never look down on people who prefer JavaScript – there are legitimate tradeoffs either direction. So, as always, let’s be kind!
By doing this, we can make the experience of developing Ember apps better for everyone – not just TypeScript users, but all the people using plain-old JavaScript as well. An increasing number of editors will take advantage of TypeScript type definitions if they’re available to help you in your development experience, even if you’re not using TypeScript at all. And of course, if you are using TypeScript, having these will make all the difference in your experience of using an addon. So let’s do it!
First, a couple notes to both add-on authors and add-on users, then the nitty gritty!
{>> TODO <<}
One other approach you can take, if you're interested in adding types to your addon but don't have the knowledge or bandwidth to do it yourself is to add a Help Wanted issue to your repository and link it here! If your repo isn't in the top-100-addons list, I'll add it anyway in its own section. If you're
{>> TODO <<}
If you take a look at one of the addons on this list and the author hasn’t yet expressed any interest in adding TypeScript or TypeScript definitions to their addon, the very first thing you should do is open an issue and see if they’re open to the possibility of converting their addon to TypeScript or hosting the type definitions in the addon. We strongly recommend you just use this template when opening the issue:
Suggested Title: Interested in TypeScript?
Suggested Body:
Hello! I use your addon and really appreciate it. I’m also participating in this quest issue to add TypeScript support throughout the Ember.js ecosystem. This will benefit to everyone who uses your addon, not just TypeScript users! (See here for an explanation.)
Are you interested in either converting the addon to use TypeScript itself, or in adding type definitions? I’d love to help out, if so! And if not, that’s just fine. Thanks!
To make it easy, you can just copy and paste this directly into the issue of a body:
Hello! I use your addon and really appreciate it. I’m also participating in [this quest issue to add TypeScript support throughout the Ember.js ecosystem](https://github.com/typed-ember/ember-typings/issues/14). This will benefit to *everyone* who uses your addon, not just TypeScript users! (See [here]( improving-developer-experience-for-all-emberjs-users) for an explanation.)
Are you interested in either converting the addon to use TypeScript itself, or in adding type definitions? I’d love to help out, if so! And if not, that’s just fine. Thanks!
Based on their answer, you can then help them convert their addon to use TypeScript, help them add type definitions to the repository, or – and this is very important – just leave them alone if they’re not interested! It’s perfectly fine if addon authors aren’t interested in using TypeScript themselves or if they don’t want to take on the extra maintenance burden of hosting type definitions. Do not badger an addon author about TypeScript! If they aren’t interested, we can fall back to using DefinitelyTyped to host type definitions!
Okay, let's dive in and talk about how to do this!
{>> TODO <<}
As noted above, the most important thing about type definitions is that they are correct. If they're incorrect, they'll actually lead users to write wrong code, and that's a deeply frustrating experience.
Some general tips:
Wherever there is documentation, read it very carefully. It's not always correct or up to date, but it still usually gives you a good starting point. If nothing else, it is often a good guide for what the public API is.
Read the source code for the public API. This is the only way to actually be sure of what the code does. Take a look at the function arguments, and look at what is done with them. Are there optional arguments? Are the arguments "polymorphic," i.e. can you pass in either a string or an array of strings or an object as the nth argument? Does it always return the same thing or not? Etc.
Pick something you can handle! Your type definitions should capture everything about the actual behavior of an addon's classes and functions. Sometimes, this will be easy. Other times (here's looking at you, Ember CLI Mirage!) this will involve a lot of arcane type mechanics. If you're just starting out, that's okay! Pick a simpler addon. If you've got mad TypeScript chops, awesome: pick something harder!
Don't go it alone. Get input along the way. The folks on the ember-cli-typescript team and many of the people hanging out in #topic-typescript on the Ember Community Slack are happy to help review your typings work and help you go from just wrote my first type definition ever! to mentoring other people! And getting feedback along the way will help us achieve our goal
{>> TODO: how to write a good type definition <<}
{>> TODO: .d.ts
files, modules, exports <<}
For more complicated types, you may need to lean on TypeScript's optionals, generics, union and intersection types, overloading, or the combination of several of those.
One of the most common scenarios you'll need to cover is handling optional arguments or properties
{>> TODO <<}
There are two ways you'll commonly need to write out optional properties. One is when some items on a type are required and others are optional. In that case, you can write the type with each argument specified explicitly as being optional or not:
type ATypicalWesternName = {
first: string;
middle?: string;
last: string;
}
The other case is where every property on an object is optional. (This tends to come up when you're passing in a configuration/options hash to a function.) In that case, you can use the build-in Partial<T>
type, which makes every property optional:
type Options = {
doSomething: boolean;
withSomeValue: number;
};
export declare function takesOptions(options: Partial<Options>);
If any of the arguments are required, don't use Partial
, spell it out explicitly. Alternatively, build an intersection type from the combination of required properties and optional properties.
type WesternRequiredName = {
first: string;
last: string;
}
type WesternOptionalName = {
middle?: string;
}
type WesternName = WesternRequiredName & WesternOptionalName;
(This is something of a last resort, but it's there when you need it!)
Union types (described here) let you describe the either-or scenario. If a function can take either a number or a string or a boolean as an argument, you can write it like this:
declare function takesAUnion(firstArg: number | string | boolean);
Likewise, if a function returns more than one kind of things, you can write it like this:
declare function returnsAUnion(): number | string | boolean;
This is most useful for times when there is no distinct mapping between the input and output type. For example, the helpers in the ember-native-dom-helpers
helpers (now living at @ember/test-helpers
) all take either a string
or an HTMLElement
as their argument, but they always return the same kind of thing. The function signature for click
, for example, is:
export function click(selector: string | Element): Promise<void>;
However, in some circumstances, a function might take more than one input type and have different output types – but each kind of input would always have the same kind of output. For that, we'll use overloading.
With overloading, we can tell TypeScript that a single function or method has different "signatures." For example, you may recall that {>> TODO: example <<}
The basic structure of the type definition should mirror the structure of the addon or library you're documenting. However, the layout of Ember addons imposes specific requirements that are somewhat unique. In general in TypeScript, it is a good idea to lay out more complicated definitions side-by-side with the modules they document:
src/
index.js
index.d.ts
some-other-module.js
some-other-module.d.ts
However, in the current file system layout for Ember addons, the source location and the "lookup location" where you import the files from are not the same. That is, you don't import Foo from 'an-addon/addon/services/foo';
, you import Foo from 'an-addon/services/foo';
. This essentially constrains us to writing all of our type definitions in a single file. (The Module Unification file layout will help with this, but it will be some time before most addons are using it.)
Aside: The Ember types, as they stand today, are not a good example for how to document most libraries: we have documented Ember as it exists, where you can import things both in the new modules API and via the old global. As Ember itself moves to using the module structure under the hood and those packages are actually installed on the file system, type definitions should move to mirror them.
For a simple addon you can just put the type definitions in the root at index.d.ts
, like this:
the-addon/
addon/
index.js
app/
index.d.ts
index.js
package.json
A prime example of this kind of setup is ember-test-friendly-error-handler.
{>> TODO: example addon for multiple modules in index.d.ts
– ember-qunit maybe? <<}
{>> TODO: structure – modules etc. <<}
[Reminder: only do this after you've checked with the author(s) of the addon that they're interested!]
Once you have a version of the types working solidly in your own application(s), you can open a pull request to the addon with those types. The pull request should include:
index.d.ts
file in the root of the repository"types"
key in package.json
pointing to the file – for the sake of forwards-compatibility, so that if the type definitions are expanded-and-moved at a later time, things don't surprisingly breakIt should also have a good writeup, describing any open questions, pain points, limitations, etc. – that will help the addon maintainer and the reviewers help you wrap everything up with a bow!
Your best bet is to solicit feedback from both the addon maintainer and from knowledgeable folks in #topic-typescript on the Ember Community Slack. The Typed Ember team, currently consisting of @dfreeman, @dwickern, @jamescdavis, and @chriskrycho, are happy to help review these requests, and over time others will be experienced enough to chip in as well!
If you get feedback that things need tweaking, don't worry! Type definitions are hard to get just right, and the definitions for Ember have been through a ton of revisions and we're constantly finding issues with and helping each other with those, too!
You should also make sure the type definition is not excluded by .npmignore
file and if the package.json
is manually specifying files via the "files"
key, that index.d.ts
is in the list. (This is unusual for Ember addons.) See the documentation for the files
key for details.
{>> TODO <<}
DefinitelyTyped is an officially supported, Microsoft-owned GitHub repository which publishes to the @types
npm namespace. Type definitions are automatically published from each folder in the types
directory in the repository.
The process when using DefinitelyTyped is mostly the same as when adding them directly to an addon. When opening a PR for a set of types on DefinitelyTyped, as when doing it directly to an addon, you should get reviews from other folks who are comfortable with both TypeScript and the addon. The Typed Ember team, currently consisting of @dfreeman, @dwickern, @jamescdavis, and @chriskrycho, are happy to help review these requests, and over time others will be experienced enough to chip in as well!
Once it's in place, if the addon author is up for it, you can then open a PR to add a note to their README indicating that types are available at @types/<the-addon-name>
. You should also open a PR to this repository to update the known-typings.md file.
I’ve broken this down into two categories: Top 100 Addons on Ember Observer and Other addons. Both of these are important, albeit for different reasons.
The top 100 addons on Ember Observer represent the most-installed addons in the community; their importance is probably obvious—getting typings in place for them immediately impacts the most people. Other addons or packages are those which may be downloaded less, but whose authors actively want to participate in the quest! We want to support both of these.
The top 100 add-ons in the ecosystem, by way of Ember Observer! Some of these may not need typings added; we’ll remove them as we evaluate that. There is also quite a range of variation in the complexity of these.
@types/lodash
!Ember
global; the type definition already exists there.If your addon isn't in the top 100 on Ember Observer but you volunteer it, we'll list it here!
Using the latest, I bumped into the following issues:
beforeModel
and afterModel
(a) should be generic and (b) are overly strict. Should probably be something like this:
beforeModel<T>(transition: Transition): Rsvp.Promise<T>;
beforeModel(transition: Transition): void;
Similarly, model
should probably be something like
model<T>(params: {}, transition: Transition): T | Rsvp.Promise<T>;
model(params: {}, transition: Transition): void;
I also ran into issues with the ActionsHash
bits combined with the user of Route.extend(SomeMixin)
, but I strongly suspect the mixin pattern is to blame there, rather than the Route
definition.
I have a Ember.Service definition below:
import Ember from 'ember';
const { Service } = Ember;
export default Service.extend({
init() {
this._super(...arguments);
}
});
However, TypeScript complains that this is incorrect and throw the exception below:
Error:(6, 3) TS2345:Argument of type '{ init(): void; }' is not assignable to parameter of type 'Mixin'. Object literal may only specify known properties, and 'init' does not exist in type 'Mixin'.
This is supported by the Ember lib and seems like a critical issue
Hey everyone!
I ran into an issue when using ES classes and trying to use async/await on the Route's model hook. Use case is below:
class MyRoute extends Route {
async model(): Promise<any> {
const prop = await this.get('store').findRecord('someRecord', 'someParam');
return { prop };
}
}
However, the above would through the following error
Class 'MyRoute' incorrectly extends base class 'Route'.
Types of property 'model' are incompatible.
Type '() => Promise<any>' is not assignable to type '<T>(params: {}, transition: Transition) => T | Promise<T>'.
Type 'Promise<any>' is not assignable to type 'T | Promise<T>'.
Type 'Promise<any>' is not assignable to type 'Promise<T>'.
Property 'new' is missing in type 'Promise<any>'.
I'm assuming because the definition of model() in Route is expected to return T | Promise<T>
However, if you try to change the return type for the above to
async model(): any | Promise<any>
Then you lose the above error for Route and get one for async instead:
[ts] The return type of an async function or method must be the global Promise<T> type.
Was wondering if this is an issue that should be fixed or if there is some insight that I'm missing. Thanks!
A brand new ember-cli 2.18 application with latest ember-cli-typescript installed, ember serve
will shows warning above:
WARNING: node_modules/@types/ember/index.d.ts(1614,19): error TS2430: Interface 'NativeArray<T>' incorrectly extends interface 'MutableArray<T>'.
Types of property 'filter' are incompatible.
Type '{ <S extends T>(callbackfn: (value: T, index: number, array: T[]) => value is S, thisArg?: any): ...' is not assignable to type '{ <S extends T>(callbackfn: (value: T, index: number, array: T[]) => value is S, thisArg?: any): ...'. Two different types with this name exist, but they are unrelated.
Type 'any[]' is not assignable to type 'NativeArray<T>'.
Property 'replace' is missing in type 'any[]'.
These're all types installed:
▾ @types/
▸ ember/
▸ ember-testing-helpers/
▸ handlebars/
▸ jquery/
▸ rsvp/
When using async/await on an ember Route model, awaiting the result of store.findRecord
throws the following typescript exception. (in the comment below)
async model(): Promise<any> {
const store = get(this, 'store');
await store.findRecord('someStoreRecord', 1); // Type is referenced directly or indirectly in the fulfillment callback of its own 'then' method
}
Using a .then
works as expected. This issue arises with as mentioned, using async/await
Could this be an issue with the return type definition for store.findRecord
which is PromiseObject<T> & T
?
Hi there. I am trying to get ember-cli-typescript
up and running but I'm encountering some rather fundamental issues. Something about the type declarations of get()
is funny.
I am encountering a few issues. The first of which is in a class-based pattern (which I'd like to use, ideally):
import Controller from '@ember/controller';
import { get, computed } from '@ember/object';
export default class ApplicationController extends Controller {
value: string;
init() {
console.log(get(this, 'value'));
}
}
I get a warning on the line get(this, 'value')
, specifically on the token this
:
WARNING: ember-typescript/controllers/application.ts(8,21): error TS2345: Argument of type 'this' is not assignable to parameter of type 'ComputedProperties<{ value: string; init: () => void; replaceRoute: (name: string, ...args: any[]...'.
It can't seem to assign type ApplicationController
to ComputedProperties<T>
. I can't quite grasp the intent with this type.
The second use-case is when using a more standard Controller.extend
pattern:
import Controller from '@ember/controller';
import { get, computed } from '@ember/object';
export default Controller.extend({
value: 2,
debug: computed('value', function() {
let value = get(this, 'value');
}),
});
I get a warning warning on the line get(this, 'value')
, again, specifically on the token this
:
WARNING: ember-typescript/controllers/application.ts(8,27): error TS2345: Argument of type '"value"' is not assignable to parameter of type 'never'.
This seems to be because this
is of type any
as defined here:
type ComputedPropertyGetterFunction<T> = (this: any, key: string) => T;
in @types/ember/index.d.ts
line 76.
It's worth mentioning that this app completely functions in either scenario but it seems kind of misconfigured somehow as this is a completely vanilla app trying to do something completely standard. Any ideas what the issue is here?
I played a little with trying to pass around the this
context for them, but 😬 that seems like not a lot of fun.
Is our best bet here to lean hard on @computed
? Can we bind the this
context there more easily, at least in a class method context?
Ember modifies the prototype for String, Function and Array based on EmberENV.EXTEND_PROTOTYPES
// function prototype is used for computed properties
function() {
return this.get('name')
}.property('name')
// string prototype is used for random stuff
'test'.capitalize()
// array prototype is used for various functionality
['test'].get('firstObject')
arr.findBy('id', 5)
I'd like to start a discussion about which typings we should provide for these
Just discovered: htmlSafe(): void
but instead should be htmlSafe(): SafeString
see: https://github.com/emberjs/ember.js/blob/ab975417c47d49cd183b1e0470aea3c77c162682/packages/ember-glimmer/lib/utils/string.ts#L78-L85
These modules are important since they're included by the default ember-cli blueprint.
import { test, module } from 'qunit';
import { setResolver } from 'ember-qunit';
import { start } from 'ember-cli-qunit';
qunit
typings are published from DT (@types/qunit
). It exports a global QUnit
and that's it. ember-qunit
adds the qunit
exports (source) in addition to its own exports.
typescript complains when trying to import the @ember/error
module.
I'm in the process of fixing every type error in the ~35k lines of TS in our app, and tracking things that need tweaking in the typings from that or that get flagged up in Slack here.
Mixin
needs to support multiple arguments to create
. Something roughly like this (and out to several):
static create<T1, T2, Base = Ember.Object>(
arg1: T1 & ThisType<Fix<T1 & Base>>,
arg2: T2 & ThisType<Fix<T2 & Base>>
): Mixin<T1 & T2, Base>;
Mixin
needs to have __ember_mixin__
not be private.Route#afterModel
and Route#beforeModel
should return any
since any | Promise<any>
just reduces to any
.Route#serialize
should return object
or
should return ComputedProperty<boolean>
and
should return ComputedProperty<boolean>
RSVP.Promise
not native Promise
Store#adapterFor
needs to be generic over A extends Adapter
Store#serializerFor
needs to be generic over S extends Serializer
Model#serialize
's options
param should be optionalQUnitModuleCallbacks
needs needs?: string[]
Here is a little snippet:
import Component from '@ember/component';
export default class MyComponent extends Component {
view: string = 'list';
constructor() {
super(...arguments);
this.addObserver('view', () => {
this.doSomething();
});
}
}
Now, typescript will report Expected 3 arguments, but got 2
for the addObserver
method. However, this code is absolutely fine. May be that ember itself isn't clear about their method signatures on that case.
Here is the code trace to this:
Component
extends CoreView
extends CoreObject
which includes the mixin Observable
Observable.addObserver
is defined here https://github.com/emberjs/ember.js/blob/3a4b38000b2cc3ce49d93f4df45415db545b3752/packages/ember-runtime/lib/mixins/observable.js#L353 and passes it down to addObserver
from ember-metal
addObserver
at ember-metal
https://github.com/emberjs/ember.js/blob/3a4b38000b2cc3ce49d93f4df45415db545b3752/packages/ember-metal/lib/observer.js#L23 which passes it down to addListener
addListener
with the code line that says, the third argument is optional: https://github.com/emberjs/ember.js/blob/3a4b38000b2cc3ce49d93f4df45415db545b3752/packages/ember-metal/lib/events.js#L60Here is the relevant typing to that (if I'm correct): https://github.com/DefinitelyTyped/DefinitelyTyped/blob/e22846ad77459b3ece25598db93c2013e8c76716/types/ember/index.d.ts#L1683
I would have fixed this on my own, but these typings look like perl to me (😂).
Basically this should throw a typing error:
function makefoo(f : Foo) : string {
return f.bar;
}
And this is how Foo
is defined:
const Foo = Ember.Object.extend({
name: "Hallo"
});
type Foo = typeof Foo.prototype;
export default Foo;
As workaround this can be done:
function makefoo2(f : typeof Foo) : string {
return f.bar;
}
This correctly outputs:
app/controllers/application.ts(10,12): error TS2339: Property 'bar' does not exist on type 'Readonly & (new () => { name: string; } & Object) & (new (...args: any[]) => { nam...'
This repo can be used as reproduction.
Ping @dwickern @chriskrycho since we discussed this in slack.
tsc throws an exception for references to Ember.RSVP.Promise:"TS2694: Namespace 'Ember' has no exported member 'RSVP'"
. This worked in a previous version of @types/ember.
An example of a typing that fails is:
(urn: string): Ember.RSVP.Promise<IMetric, any>
Thanks
From what I can tell, this repo is no longer active, and changes to @types/ember
are made directly in the DefinitelyTyped project.
To prevent newcomers from opening issues ( #77 ) here and thinking they'll get attention, we should indicate that this repo is no longer in use
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.