Skip to content

Commit 7364d6f

Browse files
authored
Merge pull request #108 from typed-ember/generate-package-typings
Generate package typings
2 parents 33a1dcf + d14e5a5 commit 7364d6f

File tree

5 files changed

+80
-7
lines changed

5 files changed

+80
-7
lines changed

README.md

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ Use TypeScript in your Ember 2.x and 3.x apps!
1111
* [Using TypeScript with Ember effectively](#using-typescript-with-ember-effectively)
1212
* [Incremental adoption](#incremental-adoption)
1313
* [Install other types!](#install-other-types)
14-
* [Environment configuration typings](#environment-configuration-typings)
14+
* [The `types` directory](#the-types-directory)
15+
* [Global types for your package](#global-types-for-your-package)
16+
* [Environment configuration typings](#environment-configuration-typings)
1517
* [Service and controller injections](#service-and-controller-injections)
1618
* [Ember Data lookups](#ember-data-lookups)
1719
* [Opt-in unsafety](#opt-in-unsafety)
@@ -30,15 +32,15 @@ Use TypeScript in your Ember 2.x and 3.x apps!
3032

3133
## Setup and Configuration
3234

33-
To install the addon, just run:
35+
To install or upgrade the addon, just run:
3436

3537
```
3638
ember install ember-cli-typescript@latest
3739
```
3840

3941
All dependencies will be added to your `package.json`, and you're ready to roll! If you're upgrading from a previous release, you should check to merge any tweaks you've made to `tsconfig.json`.
4042

41-
In addition to ember-cli-typescript, the following are installed—all at their current "latest" value:
43+
In addition to ember-cli-typescript, the following are installed—all at their current "latest" value—or generated:
4244

4345
* Packages:
4446
* [`typescript`](https://github.com/Microsoft/TypeScript)
@@ -47,6 +49,8 @@ In addition to ember-cli-typescript, the following are installed—all at their
4749
* [`@types/ember-testing-helpers`](https://www.npmjs.com/package/@types/ember-testing-helpers)
4850
* Files:
4951
* [`tsconfig.json`](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html)
52+
* `types/<app name>/index.d.ts` – the location for any global type declarations you need to write for you own application; see [Global types for your package](#global-types-for-your-package) for information on its default contents and how to use it effectively
53+
* `types/<app name>/config/environment.d.ts` – a basic set of types defined for the contents of the `config/environment.js` file in your app; see [Environment and configuration typings](#environment-and-configuration-typings) for details
5054

5155
### Ember support
5256

@@ -113,10 +117,30 @@ To make this easier, we're maintaining [a list of addons with known type definit
113117
[known-typings]: ./known-typings.md
114118
[definitely typed]: https://github.com/DefinitelyTyped/DefinitelyTyped
115119

116-
### Environment configuration typings
120+
### The `types` directory
121+
122+
During installation, we create a `types` directory in the root of your application and add a `"paths"` mapping that includes that directory in any type lookups TypeScript tries to do. This is convenient for a few things:
123+
124+
* global types for your package (see the next section)
125+
* writing types for third-party/`vendor` packages which do not have any types
126+
* developing types for an addon which you intend to upstream later
127+
128+
These are all fallbacks, of course, you should use the types supplied directly with a package
129+
130+
#### Global types for your package
131+
132+
At the root of your application or addon, we include a `types/<your app>` directory with an `index.d.ts` file in it. Anything which is part of your application but which must be declared globally can go in this file. For example, if you have data attached to the `Window` object when the page is loaded (for bootstrapping or whatever other reason), this is a good place to declare it.
133+
134+
In the case of applications (but not for addons), we also automatically include declarations for Ember's prototype extensions in this `index.d.ts` file, with the `Array` prototype extensions enabled and the `Function` prototype extensions commented out. You should configure them to match your own config (which we cannot check during installation). If you are [disabling Ember's prototype extensions][disabling], you can remove these declarations entirely; we include them because they're enabled in most Ember applications today.
135+
136+
[disabling]: https://guides.emberjs.com/v2.18.0/configuring-ember/disabling-prototype-extensions/
137+
138+
#### Environment configuration typings
117139

118140
Along with the @types/ files mentioned above, ember-cli-typescript adds a starter interface for `config/environment.js` in `config/environment.d.ts`. This interface will likely require some changes to match your app.
119141

142+
We install this file because the actual `config/environment.js` is (a) not actually identical with the types as you inherit them in the content of an application, but rather a superset of what an application has access to, and (b) not in a the same location as the path at which you look it up. We map it to the lookup path within your `types` directory, and TypeScript resolves it correctly.
143+
120144
### Service and controller injections
121145

122146
Ember does service and controller lookups with the `inject` helpers at runtime, using the name of the service or controller being injected up as the default value—a clever bit of metaprogramming that makes for a nice developer experience. TypeScript cannot do this, because the name of the service or controller to inject isn't available at compile time in the same way. This means that if you do things the normal Ember way, you will have to specify the type of your service or controller explicitly everywhere you use it.

blueprints/ember-cli-typescript/files/__root__/config/environment.d.ts renamed to blueprints/ember-cli-typescript/files/types/__app_name__/config/environment.d.ts

File renamed without changes.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<%= baseDeclarations(dasherizedPackageName) %>

blueprints/ember-cli-typescript/index.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,16 @@
33
const fs = require('fs');
44
const path = require('path');
55

6+
const APP_DECLARATIONS = `
7+
declare global {
8+
interface Array<T> extends Ember.ArrayPrototypeExtensions<T> {}
9+
// interface Function extends Ember.FunctionPrototypeExtensions {}
10+
}
11+
`;
12+
613
module.exports = {
14+
APP_DECLARATIONS,
15+
716
description: 'Initialize files needed for typescript compilation',
817

918
install(options) {
@@ -45,8 +54,24 @@ module.exports = {
4554
paths[`${appName}/*`].push(`${addon}/app/*`);
4655
}
4756

57+
paths['*'] = ['types/*'];
58+
4859
return JSON.stringify(paths, null, 2).replace(/\n/g, '\n ');
4960
},
61+
baseDeclarations: dasherizedName => {
62+
const isDummyApp = dasherizedName === 'dummy';
63+
const useAppDeclarations = !(isAddon || isDummyApp);
64+
return useAppDeclarations ? APP_DECLARATIONS : '';
65+
},
66+
};
67+
},
68+
69+
fileMapTokens(/*options*/) {
70+
// Return custom tokens to be replaced in your files.
71+
return {
72+
__app_name__(options) {
73+
return options.inAddon ? 'dummy' : options.dasherizedModuleName;
74+
},
5075
};
5176
},
5277

node-tests/blueprints/ember-cli-typescript-test.js

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ const path = require('path');
55
const helpers = require('ember-cli-blueprint-test-helpers/helpers');
66
const chaiHelpers = require('ember-cli-blueprint-test-helpers/chai');
77

8+
const ects = require('../../blueprints/ember-cli-typescript');
9+
810
const expect = chaiHelpers.expect;
911
const file = chaiHelpers.file;
1012

@@ -14,7 +16,8 @@ describe('Acceptance: ember-cli-typescript generator', function() {
1416
it('basic app', function() {
1517
const args = ['ember-cli-typescript'];
1618

17-
return helpers.emberNew()
19+
return helpers
20+
.emberNew()
1821
.then(() => helpers.emberGenerate(args))
1922
.then(() => {
2023
const pkg = file('package.json');
@@ -31,16 +34,25 @@ describe('Acceptance: ember-cli-typescript generator', function() {
3134
expect(tsconfigJson.compilerOptions.paths).to.deep.equal({
3235
'my-app/tests/*': ['tests/*'],
3336
'my-app/*': ['app/*'],
37+
'*': ['types/*'],
3438
});
3539

3640
expect(tsconfigJson.include).to.deep.equal(['app', 'tests']);
41+
42+
const projectTypes = file('types/my-app/index.d.ts');
43+
expect(projectTypes).to.exist;
44+
expect(projectTypes).to.include(ects.APP_DECLARATIONS);
45+
46+
const environmentTypes = file('types/my-app/config/environment.d.ts');
47+
expect(environmentTypes).to.exist;
3748
});
3849
});
3950

4051
it('basic addon', function() {
4152
const args = ['ember-cli-typescript'];
4253

43-
return helpers.emberNew({ target: 'addon' })
54+
return helpers
55+
.emberNew({ target: 'addon' })
4456
.then(() => helpers.emberGenerate(args))
4557
.then(() => {
4658
const pkg = file('package.json');
@@ -59,16 +71,22 @@ describe('Acceptance: ember-cli-typescript generator', function() {
5971
'dummy/*': ['tests/dummy/app/*'],
6072
'my-addon': ['addon'],
6173
'my-addon/*': ['addon/*'],
74+
'*': ['types/*'],
6275
});
6376

6477
expect(tsconfigJson.include).to.deep.equal(['addon', 'tests']);
78+
79+
const projectTypes = file('types/dummy/index.d.ts');
80+
expect(projectTypes).to.exist;
81+
expect(projectTypes).not.to.include(ects.APP_DECLARATIONS);
6582
});
6683
});
6784

6885
it('in-repo addons', function() {
6986
const args = ['ember-cli-typescript'];
7087

71-
return helpers.emberNew()
88+
return helpers
89+
.emberNew()
7290
.then(() => {
7391
const packagePath = path.resolve(process.cwd(), 'package.json');
7492
const contents = JSON.parse(fs.readFileSync(packagePath, { encoding: 'utf8' }));
@@ -90,9 +108,14 @@ describe('Acceptance: ember-cli-typescript generator', function() {
90108
'my-addon-1/*': ['lib/my-addon-1/addon/*'],
91109
'my-addon-2': ['lib/my-addon-2/addon'],
92110
'my-addon-2/*': ['lib/my-addon-2/addon/*'],
111+
'*': ['types/*'],
93112
});
94113

95114
expect(json.include).to.deep.equal(['app', 'tests', 'lib/my-addon-1', 'lib/my-addon-2']);
115+
116+
const projectTypes = file('types/my-app/index.d.ts');
117+
expect(projectTypes).to.exist;
118+
expect(projectTypes).to.include(ects.APP_DECLARATIONS);
96119
});
97120
});
98121
});

0 commit comments

Comments
 (0)