55 |
56 | ### What are instructions we can follow to reproduce the issue?
57 | ```sh
58 | ember new sample; cd ./sample # Create a new ember app
59 | ember install ember-cli-typescript # Set up typescript support
60 |
61 | >> Your Instructions Go Here <<
62 |
63 | ```
64 |
65 | ##### Reproduction Case
66 | If you can, please try to fork [this codesandbox](https://codesandbox.io/s/github/mike-north/ember-new-output/tree/ts), and give us an example that demonstrates the problem. Paste the link below so that we can see what's going on
67 |
68 | **Link: **
69 |
70 |
71 |
72 | ### Now about that bug. What did you expect to see?
73 |
74 |
75 | ### What happened instead?
76 |
77 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/---build-enhancement.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: "\U0001F381 Build Enhancement"
3 | about: An enhancement relating to Ember.js typescript support & infrastructure
4 |
5 | ---
6 |
7 |
9 |
10 | ### Please write a user story for this feature
11 |
12 | Follow the form
13 |
14 | > *As a **role**, I want **feature** so that **reason**.*
15 |
16 | > Example:
17 | >
18 | > As an **addon publisher**, I want tobe able to **precompile my addon's typescript into js**, so that **my consumers can use my code, regardless of whether their app is written in typescript**
19 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/---types-bug.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: "\U0001F41B Types Bug"
3 | about: A problem with the type information for Ember & Ember-Data
4 |
5 | ---
6 |
7 |
9 |
10 | ### Which package(s) does this problem pertain to?
11 | - [ ] @types/ember
12 | - [ ] @types/ember__string
13 | - [ ] @types/ember__polyfills
14 | - [ ] @types/ember__object
15 | - [ ] @types/ember__utils
16 | - [ ] @types/ember__array
17 | - [ ] @types/ember__engine
18 | - [ ] @types/ember__debug
19 | - [ ] @types/ember__runloop
20 | - [ ] @types/ember__error
21 | - [ ] @types/ember__controller
22 | - [ ] @types/ember__component
23 | - [ ] @types/ember__routing
24 | - [ ] @types/ember__application
25 | - [ ] @types/ember__test
26 | - [ ] @types/ember__test-helpers
27 | - [ ] @types/ember__service
28 | - [ ] @types/ember-data
29 | - [ ] @types/rsvp
30 | - [ ] Other
31 | - [ ] I don't know
32 |
33 | ### What are instructions we can follow to reproduce the issue?
34 | ```sh
35 | ember new sample; cd ./sample # Create a new ember app
36 | ember install ember-cli-typescript # Set up typescript support
37 |
38 | >> Your Instructions Go Here <<
39 |
40 | ```
41 |
42 | ##### Reproduction Case
43 | If you can, please try to fork [this codesandbox](https://codesandbox.io/s/github/mike-north/ember-new-output/tree/ts), and give us an example that demonstrates the problem. Paste the link below so that we can see what's going on
44 |
45 | **Link: **
46 |
47 |
48 |
49 |
50 | ### Now about that bug. What did you expect to see?
51 |
52 |
53 | ### What happened instead?
54 |
55 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/---types-enhancement.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: "\U0001F381 Types Enhancement"
3 | about: A feature request for the type information of Ember & Ember-Data
4 |
5 | ---
6 |
7 |
9 |
10 | ### Which package(s) does this enhancement pertain to?
11 | - [ ] @types/ember
12 | - [ ] @types/ember__string
13 | - [ ] @types/ember__polyfills
14 | - [ ] @types/ember__object
15 | - [ ] @types/ember__utils
16 | - [ ] @types/ember__array
17 | - [ ] @types/ember__engine
18 | - [ ] @types/ember__debug
19 | - [ ] @types/ember__runloop
20 | - [ ] @types/ember__error
21 | - [ ] @types/ember__controller
22 | - [ ] @types/ember__component
23 | - [ ] @types/ember__routing
24 | - [ ] @types/ember__application
25 | - [ ] @types/ember__test
26 | - [ ] @types/ember__test-helpers
27 | - [ ] @types/ember__service
28 | - [ ] @types/ember-data
29 | - [ ] @types/rsvp
30 | - [ ] Other
31 | - [ ] I don't know
32 |
33 | ### Please write a user story for this feature
34 |
35 | Follow the form
36 |
37 | > *As a **role**, I want **feature** so that **reason**.*
38 |
39 | > Example:
40 | >
41 | > As an **addon publisher**, I want tobe able to **precompile my addon's typescript into js**, so that **my consumers can use my code, regardless of whether their app is written in typescript**
42 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | # These trigger patterns based on https://github.com/broccolijs/broccoli/pull/436
4 | on:
5 | pull_request:
6 | push:
7 | # filtering branches here prevents duplicate builds from pull_request and push
8 | branches:
9 | - master
10 | - 'v*'
11 | # always run CI for tags
12 | tags:
13 | - '*'
14 |
15 | # early issue detection: run CI weekly on Sundays
16 | schedule:
17 | - cron: '0 6 * * 0'
18 |
19 | env:
20 | CI: true
21 |
22 | jobs:
23 | test-locked-deps:
24 | name: Test (linux, locked dependencies)
25 | runs-on: ubuntu-latest
26 | steps:
27 | - name: Checkout Code
28 | uses: actions/checkout@v2
29 | - name: Install Node
30 | uses: volta-cli/action@v4
31 | # https://github.com/expo/expo-github-action/issues/20#issuecomment-541676895
32 | - name: Raise Watched File Limit
33 | run: echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p
34 | - name: Install Dependencies
35 | run: yarn install --frozen-lockfile
36 | - name: Lint JS/TS Code
37 | run: yarn lint:js
38 | - name: Prepare CI Environment
39 | run: yarn ci:prepare
40 | - name: Ember App Tests
41 | run: yarn ci:test:app
42 | - name: Node Tests
43 | run: yarn ci:test:node
44 |
45 | test-windows:
46 | name: Test (windows, locked dependencies)
47 | runs-on: windows-latest
48 | steps:
49 | - name: Checkout Code
50 | uses: actions/checkout@v2
51 | - name: Install Node
52 | uses: volta-cli/action@v4
53 | - name: Install Dependencies
54 | run: yarn install --frozen-lockfile
55 | - name: Prepare CI Environment
56 | run: yarn ci:prepare
57 | - name: Ember App Tests
58 | run: yarn ci:test:app
59 | - name: Node Tests
60 | run: yarn ci:test:node
61 |
62 | test-floating:
63 | name: Test (linux, floating dependencies)
64 | runs-on: ubuntu-latest
65 | needs: [test-locked-deps]
66 | steps:
67 | - name: Checkout Code
68 | uses: actions/checkout@v2
69 | - name: Install Node
70 | uses: volta-cli/action@v4
71 | - name: Raise Watched File Limit
72 | run: echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p
73 | - name: Install Dependencies
74 | run: yarn install --no-lockfile
75 | - name: Prepare CI Environment
76 | run: yarn ci:prepare
77 | - name: Ember App Tests
78 | run: yarn ci:test:app
79 | - name: Node Tests
80 | run: yarn ci:test:node
81 |
82 | test-ts-cli-matrix:
83 | name: Test
84 | runs-on: ubuntu-latest
85 | needs: [test-locked-deps]
86 | strategy:
87 | fail-fast: false
88 | matrix:
89 | deps:
90 | - ember-cli@latest
91 | - ember-cli@beta
92 | - typescript@latest
93 | - typescript@next
94 | steps:
95 | - name: Checkout Code
96 | uses: actions/checkout@v2
97 | - name: Install Node
98 | uses: volta-cli/action@v4
99 | - name: Raise Watched File Limit
100 | run: echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p
101 | - name: Install Dependencies
102 | run: yarn add --dev ${{ matrix.deps }}
103 | - name: Prepare CI Environment
104 | run: yarn ci:prepare
105 | - name: Ember App Tests
106 | run: yarn ci:test:app
107 | - name: Node Tests
108 | run: yarn ci:test:node
109 |
110 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # compiled output
4 | /dist/
5 | /tmp/
6 | /js/
7 |
8 | # dependencies
9 | /bower_components/
10 | /node_modules/
11 |
12 | # misc
13 | /.sass-cache
14 | /connect.lock
15 | /coverage/
16 | /libpeerconnection.log
17 | /npm-debug.log*
18 | /testem.log
19 | /yarn-error.log
20 |
21 | # ember-try
22 | .node_modules.ember-try/
23 | bower.json.ember-try
24 | package.json.ember-try
25 |
26 | # TypeScript
27 | jsconfig.json
28 | *-tests.xml
29 | test-skeleton-app*
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | # compiled output
2 | /dist/
3 | /tmp/
4 |
5 | # dependencies
6 | /bower_components/
7 |
8 | # misc
9 | /.bowerrc
10 | /.editorconfig
11 | /.ember-cli
12 | /.env*
13 | /.eslintcache
14 | /.eslintignore
15 | /.eslintrc.js
16 | /.git/
17 | /.github/
18 | /.gitignore
19 | /.prettierignore
20 | /.prettierrc.js
21 | /.template-lintrc.js
22 | /.travis.yml
23 | /.watchmanconfig
24 | /bower.json
25 | /config/ember-try.js
26 | /CONTRIBUTING.md
27 | /ember-cli-build.js
28 | /testem.js
29 | /tests/
30 | /yarn-error.log
31 | /yarn.lock
32 | .gitkeep
33 |
34 | # ember-try
35 | /.node_modules.ember-try/
36 | /bower.json.ember-try
37 | /npm-shrinkwrap.json.ember-try
38 | /package.json.ember-try
39 | /package-lock.json.ember-try
40 | /yarn.lock.ember-try
41 |
42 | # TS sources and compiled tests
43 | /ts/
44 | /js/tests/
45 | /test-fixtures/
46 |
47 | # custom
48 | /commitlint.config.js
49 | /.vscode/
50 | /renovate.json
51 | /CODE_OF_CONDUCT.md
52 | /rfcs
53 | /*.sh
54 | /.gitbook.yaml
55 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | blueprint-files/
2 | test-fixtures/
3 | node_modules/
4 | .azure/
5 | .github/
6 | .vscode/
7 | testem.js
8 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "type": "node",
9 | "request": "launch",
10 | "name": "Debug Tests",
11 | "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha",
12 | "args": [
13 | "-r", "register-ts-node",
14 | "-f", "Acceptance: ember-cli-typescript generator",
15 | "-f", "basic app",
16 | "-t", "1000000",
17 | "ts/tests/**/*.{ts,js}"
18 | ],
19 | "internalConsoleOptions": "openOnSessionStart"
20 | }
21 | ]
22 | }
23 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "files.exclude": {
3 | "**/.git": true,
4 | "**/.svn": true,
5 | "**/.hg": true,
6 | "**/CVS": true,
7 | "**/.DS_Store": true,
8 | "node_modules": true,
9 | "dist": true,
10 | "tmp": true,
11 | "integrated-node-tests": true,
12 | "ember-lts-2.12": true,
13 | "ember-lts-2.16": true,
14 | "ember-release": true,
15 | "ember-beta": true,
16 | "ember-canary": true,
17 | "ember-default": true
18 | },
19 | "typescript.tsdk": "node_modules/typescript/lib"
20 | }
21 |
--------------------------------------------------------------------------------
/.watchmanconfig:
--------------------------------------------------------------------------------
1 | {
2 | "ignore_dirs": ["tmp", "dist"]
3 | }
4 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | The Ember team and community are committed to everyone having a safe and inclusive experience.
2 |
3 | **Our Community Guidelines / Code of Conduct can be found here**:
4 |
5 | https://emberjs.com/guidelines/
6 |
7 | For a history of updates, see the page history here:
8 |
9 | https://github.com/emberjs/website/commits/master/source/guidelines.html.erb
10 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
10 |
--------------------------------------------------------------------------------
/blueprint-files/ember-cli-typescript/__config_root__/config/environment.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Type declarations for
3 | * import config from 'my-app/config/environment'
4 | */
5 | declare const config: {
6 | environment: string;
7 | modulePrefix: string;
8 | podModulePrefix: string;
9 | locationType: 'history' | 'hash' | 'none' | 'auto';
10 | rootURL: string;
11 | APP: Record;
12 | };
13 |
14 | export default config;
15 |
--------------------------------------------------------------------------------
/blueprint-files/ember-cli-typescript/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@tsconfig/ember/tsconfig.json",
3 | "compilerOptions": {
4 |
5 | // The combination of `baseUrl` with `paths` allows Ember's classic package
6 | // layout, which is not resolvable with the Node resolution algorithm, to
7 | // work with TypeScript.
8 | "baseUrl": ".",
9 | "paths": <%= pathsFor(dasherizedPackageName) %>
10 | },
11 | "include": <%= includes %>
12 | }
13 |
--------------------------------------------------------------------------------
/blueprint-files/ember-cli-typescript/types/__app_name__/index.d.ts:
--------------------------------------------------------------------------------
1 | <%= indexDeclarations(dasherizedPackageName) %>
2 |
--------------------------------------------------------------------------------
/blueprint-files/ember-cli-typescript/types/ember-data/types/registries/model.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Catch-all for ember-data.
3 | */
4 | export default interface ModelRegistry {
5 | [key: string]: any;
6 | }
7 |
--------------------------------------------------------------------------------
/blueprint-files/ember-cli-typescript/types/global.d.ts:
--------------------------------------------------------------------------------
1 | <%= globalDeclarations(dasherizedPackageName) %>
2 |
--------------------------------------------------------------------------------
/commitlint.config.js:
--------------------------------------------------------------------------------
1 | /* eslint-env node */
2 | 'use strict';
3 |
4 | module.exports = {
5 | extends: ['@commitlint/config-conventional'],
6 | rules: {
7 | 'header-max-length': [0, 'always', 288],
8 | },
9 | };
10 |
--------------------------------------------------------------------------------
/config/ember-try.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | useYarn: true,
3 | command: 'yarn ci:test',
4 | scenarios: [
5 | {
6 | name: 'defaults',
7 | npm: {
8 | devDependencies: {},
9 | },
10 | },
11 | {
12 | name: 'typescript-release',
13 | npm: {
14 | devDependencies: {
15 | typescript: 'latest',
16 | },
17 | },
18 | },
19 | {
20 | name: 'typescript-beta',
21 | npm: {
22 | devDependencies: {
23 | typescript: 'next',
24 | },
25 | },
26 | },
27 | {
28 | name: 'ember-cli-release',
29 | npm: {
30 | devDependencies: {
31 | 'ember-cli': 'latest',
32 | },
33 | },
34 | },
35 | {
36 | name: 'ember-cli-beta',
37 | npm: {
38 | devDependencies: {
39 | 'ember-cli': 'beta',
40 | },
41 | },
42 | },
43 | ],
44 | };
45 |
--------------------------------------------------------------------------------
/config/environment.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = function (/* environment, appConfig */) {
4 | return {};
5 | };
6 |
--------------------------------------------------------------------------------
/debug.sh:
--------------------------------------------------------------------------------
1 | node --inspect --debug-brk node_modules/.bin/ember build
2 |
--------------------------------------------------------------------------------
/docs/SUMMARY.md:
--------------------------------------------------------------------------------
1 | # Table of contents
2 |
3 | * [ember-cli-typescript](index.md)
4 | * [Installation](installation.md)
5 | * [Configuration](configuration.md)
6 | * [TypeScript and Ember](ts/README.md)
7 | * [Using TypeScript With Ember Effectively](ts/using-ts-effectively.md)
8 | * [Decorators](ts/decorators.md)
9 | * [Current limitations](ts/current-limitations.md)
10 | * [Building Addons in TypeScript](ts/with-addons.md)
11 | * [Understanding the @types Package Names](ts/package-names.md)
12 | * [Working With Ember](ember/README.md)
13 | * [Components](ember/components.md)
14 | * [Services](ember/services.md)
15 | * [Routes](ember/routes.md)
16 | * [Controllers](ember/controllers.md)
17 | * [Helpers](ember/helpers.md)
18 | * [Testing](ember/testing.md)
19 | * [Working With Ember Data](ember-data/README.md)
20 | * [Models](ember-data/models.md)
21 | * [Transforms](ember-data/transforms.md)
22 | * [Cookbook](cookbook/README.md)
23 | * [Working with route models](cookbook/working-with-route-models.md)
24 | * [Working With Ember Classic](legacy/README.md)
25 | * [EmberComponent](legacy/ember-component.md)
26 | * [Mixins](legacy/mixins.md)
27 | * [Computed Properties](legacy/computed-properties.md)
28 | * [EmberObject](legacy/ember-object.md)
29 | * [Upgrading from 1.x](upgrade-notes.md)
30 | * [Troubleshooting](troubleshooting/README.md)
31 | * [Conflicting Type Dependencies](troubleshooting/conflicting-types.md)
32 |
--------------------------------------------------------------------------------
/docs/configuration.md:
--------------------------------------------------------------------------------
1 | # Configuration
2 |
3 | ## `tsconfig.json`
4 |
5 | We generate a good default [`tsconfig.json`](https://github.com/typed-ember/ember-cli-typescript/blob/master/blueprint-files/ember-cli-typescript/tsconfig.json), which will usually make everything _Just Work™_. In general, you may customize your TypeScript build process as usual using the `tsconfig.json` file.
6 |
7 | However, there are a few things worth noting if you're already familiar with TypeScript and looking to make further or more advanced customizations (but _most_ users can just ignore this section!):
8 |
9 | 1. The generated tsconfig file does not set `"outDir"` and sets `"noEmit"` to `true`. The default configuration we generate allows you to run editors which use the compiler without creating extraneous `.js` files throughout your codebase, leaving the compilation to ember-cli-typescript to manage.
10 |
11 | You _can_ still customize those properties in `tsconfig.json` if your use case requires it, however. For example, to see the output of the compilation in a separate folder you are welcome to set `"outDir"` to some path and set `"noEmit"` to `false`. Then tools which use the TypeScript compiler (e.g. the watcher tooling in JetBrains IDEs) will generate files at that location, while the Ember.js/[Broccoli](https://broccoli.build) pipeline will continue to use its own temp folder.
12 |
13 | 2. Closely related to the previous point: any changes you do make to `outDir` won't have any effect on how _Ember_ builds your application—we run the entire build pipeline through Babel's TypeScript support instead of through the TypeScript compiler.
14 | 3. Since your application is built by Babel, and only _type-checked_ by TypeScript, we set the `target` key in `tsconfig.json` to the current version of the ECMAScript standard so that type-checking uses the latest and greatest from the JavaScript standard library. The Babel configuration in your app's `config/targets.js` and any included polyfills will determine the final build output.
15 | 4. If you make changes to the paths included in or excluded from the build via your `tsconfig.json` (using the `"include"`, `"exclude"`, or `"files"` keys), you will need to restart the server to take the changes into account: ember-cli-typescript does not currently watch the `tsconfig.json` file. For more details, see [the TypeScript reference materials for `tsconfig.json`](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html).
16 |
17 | ## Enabling Sourcemaps
18 |
19 | To enable TypeScript sourcemaps, you'll need to add the corresponding configuration for Babel to your `ember-cli-build.js` file:
20 |
21 | ```typescript
22 | const app = new EmberApp(defaults, {
23 | babel: {
24 | sourceMaps: 'inline',
25 | },
26 | });
27 | ```
28 |
29 | (Note that this _will_ noticeably slow down your app rebuilds.)
30 |
31 | If you are using [Embroider](https://github.com/embroider-build/embroider), you might need to include [devtool](https://webpack.js.org/configuration/devtool/) in your webpack configuration:
32 |
33 | ```ts
34 | return require('@embroider/compat').compatBuild(app, Webpack, {
35 | packagerOptions: {
36 | webpackConfig: {
37 | devtool: 'source-map'
38 | }
39 | }
40 | }
41 | ```
42 |
43 | If you're updating from an older version of the addon, you may also need to update your `tsconfig.json`. (Current versions generate the correct config at installation.) Either run `ember generate ember-cli-typescript` or verify you have the same sourcemap settings in your `tscsonfig.json` that appear in [the blueprint](https://github.com/typed-ember/ember-cli-typescript/blob/master/blueprint-files/ember-cli-typescript/files/tsconfig.json).
44 |
--------------------------------------------------------------------------------
/docs/cookbook/README.md:
--------------------------------------------------------------------------------
1 | # Cookbook
2 |
3 | This “cookbook” section contains recipes for various scenarios you may encounter while working on your app or addon.
4 |
5 | {% hint style="info" %}
6 | Have an idea for an item that should fit here? [Open an issue for it!](https://github.com/typed-ember/ember-cli-typescript/issues/new/choose) We'd love to help you help us make this experience more awesome for everyone.
7 | {% endhint %}
8 |
9 | ## Contents
10 |
11 | * [Working with route models](./working-with-route-models.md)
12 |
13 |
--------------------------------------------------------------------------------
/docs/cookbook/working-with-route-models.md:
--------------------------------------------------------------------------------
1 | # Working with route models
2 |
3 | We often use routes’ models throughout our application, since they’re a core ingredient of our application’s data. As such, we want to make sure that we have good types for them!
4 |
5 | We can start by defining some type utilities to let us get the resolved value returned by a route’s model hook:
6 |
7 | ```typescript
8 | import Route from '@ember/routing/route';
9 |
10 | /**
11 | Get the resolved type of an item.
12 |
13 | - If the item is a promise, the result will be the resolved value type
14 | - If the item is not a promise, the result will just be the type of the item
15 | */
16 | export type Resolved
= P extends Promise ? T : P;
17 |
18 | /** Get the resolved model value from a route. */
19 | export type ModelFrom = Resolved>;
20 | ```
21 |
22 | How that works:
23 |
24 | * `Resolved
` says "if this is a promise, the type here is whatever the promise resolves to; otherwise, it's just the value"
25 | * `ReturnType` gets the return value of a given function
26 | * `R['model']` \(where `R` has to be `Route` itself or a subclass\) uses TS's mapped types to say "the property named `model` on `R`
27 |
28 | Putting those all together, `ModelFrom` ends up giving you the resolved value returned from the `model` hook for a given route:
29 |
30 | ```typescript
31 | type MyRouteModel = ModelFrom;
32 | ```
33 |
34 | ## `model` on the controller
35 |
36 | We can use this functionality to guarantee that the `model` on a `Controller` is always exactly the type returned by `Route::model` by writing something like this:
37 |
38 | ```typescript
39 | import Controller from '@ember/controller';
40 | import MyRoute from '../routes/my-route';
41 | import { ModelFrom } from '../lib/type-utils';
42 |
43 | export default class ControllerWithModel extends Controller {
44 | declare model: ModelFrom;
45 | }
46 | ```
47 |
48 | Now, our controller’s `model` property will _always_ stay in sync with the corresponding route’s model hook.
49 |
50 | **Note:** this _only_ works if you do not mutate the `model` in either the `afterModel` or `setupController` hooks on the route! That's generally considered to be a bad practice anyway. If you do change the type there, you'll need to define the type in some other way and make sure your route's model is defined another way.
51 |
52 |
--------------------------------------------------------------------------------
/docs/ember-data/README.md:
--------------------------------------------------------------------------------
1 | # Working With Ember Data
2 |
3 | In this section, we cover how to use TypeScript effectively with specific Ember Data APIs \(anything you'd find under the `@ember-data` package namespace\).
4 |
5 | We do _not_ cover general usage of Ember Data; instead, we assume that as background knowledge. Please see the Ember Data [Guides](https://guides.emberjs.com/release/models) and [API docs](https://api.emberjs.com/ember-data/release)!
6 |
7 |
--------------------------------------------------------------------------------
/docs/ember-data/models.md:
--------------------------------------------------------------------------------
1 | # Models
2 |
3 | Ember Data models are normal TypeScript classes, but with properties decorated to define how the model represents an API resource and relationships to other resources. The decorators the library supplies "just work" with TypeScript at runtime, but require type annotations to be useful with TypeScript.
4 |
5 | For details about decorator usage, see [our overview of how Ember's decorators work with TypeScript](../ts/decorators.md).
6 |
7 | ## `@attr`
8 |
9 | The type returned by the `@attr` decorator is whatever [Transform](https://api.emberjs.com/ember-data/release/classes/Transform) is applied via the invocation. See [our overview of Transforms for more information](./transforms.md).
10 |
11 | * If you supply no argument to `@attr`, the value is passed through without transformation.
12 | * If you supply one of the built-in transforms, you will get back a corresponding type:
13 | * `@attr('string')` → `string`
14 | * `@attr('number')` → `number`
15 | * `@attr('boolean')` → `boolean`
16 | * `@attr('date')` → `Date`
17 | * If you supply a custom transform, you will get back the type returned by your transform.
18 |
19 | So, for example, you might write a class like this:
20 |
21 | ```typescript
22 | import Model, { attr } from '@ember-data/model';
23 | import CustomType from '../transforms/custom-transform';
24 |
25 | export default class User extends Model {
26 | @attr()
27 | declare name?: string;
28 |
29 | @attr('number')
30 | declare age: number;
31 |
32 | @attr('boolean')
33 | declare isAdmin: boolean;
34 |
35 | @attr('custom-transform')
36 | declare myCustomThing: CustomType;
37 | }
38 | ```
39 |
40 | **Very important:** Even more than with decorators in general, you should be careful when deciding whether to mark a property as optional `?` or definitely present \(no annotation\): Ember Data will default to leaving a property empty if it is not supplied by the API or by a developer when creating it. That is: the _default_ for Ember corresponds to an optional field on the model.
41 |
42 | The _safest_ type you can write for an Ember Data model, therefore, leaves every property optional: this is how models _actually_ behave. If you choose to mark properties as definitely present by leaving off the `?`, you should take care to guarantee that this is a guarantee your API upholds, and that ever time you create a record from within the app, _you_ uphold those guarantees.
43 |
44 | One way to make this safer is to supply a default value using the `defaultValue` on the options hash for the attribute:
45 |
46 | ```typescript
47 | import Model, { attr } from '@ember-data/model';
48 |
49 | export default class User extends Model {
50 | @attr()
51 | declare name?: string;
52 |
53 | @attr('number', { defaultValue: 13 })
54 | declare age: number;
55 |
56 | @attr('boolean', { defaultValue: false })
57 | declare isAdmin: boolean;
58 | }
59 | ```
60 |
61 | ## Relationships
62 |
63 | Relationships between models in Ember Data rely on importing the related models, like `import User from './user';`. This, naturally, can cause a recursive loop, as `/app/models/post.ts` imports `User` from `/app/models/user.ts`, and `/app/models/user.ts` imports `Post` from `/app/models/post.ts`. Recursive importing triggers an [`import/no-cycle`](https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-cycle.md) error from eslint.
64 |
65 | To avoid these errors, use [type-only imports](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-8.html), available since TypeScript 3.8:
66 |
67 | ```ts
68 | import type User from './user';
69 | ```
70 |
71 | ### `@belongsTo`
72 |
73 | The type returned by the `@belongsTo` decorator depends on whether the relationship is `{ async: true }` \(which it is by default\).
74 |
75 | * If the value is `true`, the type you should use is `AsyncBelongsTo`, where `Model` is the type of the model you are creating a relationship to.
76 | * If the value is `false`, the type is `Model`, where `Model` is the type of the model you are creating a relationship to.
77 |
78 | So, for example, you might define a class like this:
79 |
80 | ```typescript
81 | import Model, { belongsTo, type AsyncBelongsTo } from '@ember-data/model';
82 | import type User from './user';
83 | import type Site from './site';
84 |
85 | export default class Post extends Model {
86 | @belongsTo('user')
87 | declare user: AsyncBelongsTo;
88 |
89 | @belongsTo('site', { async: false })
90 | declare site: Site;
91 | }
92 | ```
93 |
94 | These are _type_-safe to define as always present, that is to leave off the `?` optional marker:
95 |
96 | * accessing an async relationship will always return an `AsyncBelongsTo` object, which itself may or may not ultimately resolve to a value—depending on the API response—but will always be present itself.
97 | * accessing a non-async relationship which is known to be associated but has not been loaded will trigger an error, so all access to the property will be safe _if_ it resolves at all.
98 |
99 | Note, however, that this type-safety is not a guarantee of there being no runtime error: you still need to uphold the contract for non-async relationships \(that is: loading the data first, or side-loading it with the request\) to avoid throwing an error!
100 |
101 | ### `@hasMany`
102 |
103 | The type returned by the `@hasMany` decorator depends on whether the relationship is `{ async: true }` \(which it is by default\).
104 |
105 | * If the value is `true`, the type you should use is `AsyncHasMany`, where `Model` is the type of the model you are creating a relationship to.
106 | * If the value is `false`, the type is `SyncHasMany`, where `Model` is the type of the model you are creating a relationship to.
107 |
108 | So, for example, you might define a class like this:
109 |
110 | ```typescript
111 | import Model, { hasMany, type AsyncHasMany, type SyncHasMany } from '@ember-data/model';
112 | import type Comment from './comment';
113 | import type User from './user';
114 |
115 | export default class Thread extends Model {
116 | @hasMany('comment')
117 | declare comments: AsyncHasMany;
118 |
119 | @hasMany('user', { async: false })
120 | declare participants: SyncHasMany;
121 | }
122 | ```
123 |
124 | The same basic rules about the safety of these lookups as with `@belongsTo` apply to these types. The difference is just that in `@hasMany` the resulting types are _arrays_ rather than single objects.
125 |
126 |
--------------------------------------------------------------------------------
/docs/ember-data/transforms.md:
--------------------------------------------------------------------------------
1 | # Transforms
2 |
3 | In Ember Data, `attr` defines an attribute on a [Model](https://guides.emberjs.com/release/models/defining-models/).
4 | By default, attributes are passed through as-is, however you can specify an
5 | optional type to have the value automatically transformed.
6 | Ember Data ships with four basic transform types: `string`, `number`, `boolean` and `date`.
7 |
8 | You can define your own transforms by subclassing [Transform](https://guides.emberjs.com/release/models/defining-models/#toc_custom-transforms).
9 | Ember Data transforms are normal TypeScript classes.
10 | The return type of `deserialize` method becomes type of the model class property.
11 |
12 | You may define your own transforms in TypeScript like so:
13 | ```typescript
14 | # app/transforms/coordinate-point.ts
15 | import Transform from '@ember-data/serializer/transform';
16 |
17 | declare module 'ember-data/types/registries/transform' {
18 | export default interface TransformRegistry {
19 | 'coordinate-point': CoordinatePointTransform;
20 | }
21 | }
22 |
23 | export type CoordinatePoint = {
24 | x: number;
25 | y: number;
26 | };
27 |
28 | export default class CoordinatePointTransform extends Transform {
29 | deserialize(serialized): CoordinatePoint {
30 | return { x: value[0], y: value[1] };
31 | }
32 |
33 | serialize(value): number {
34 | return [value.x, value.y];
35 | }
36 | }
37 |
38 | # app/models/cursor.ts
39 | import Model, { attr } from '@ember-data/model';
40 | import { CoordinatePoint } from 'agwa-data/transforms/coordinate-point';
41 |
42 | declare module 'ember-data/types/registries/model' {
43 | export default interface ModelRegistry {
44 | cursor: Cursor;
45 | }
46 | }
47 |
48 | export default class Cursor extends Model {
49 | @attr('coordinate-point') declare position: CoordinatePoint;
50 | }
51 | ```
52 |
53 | Note that you should declare your own transform under `TransformRegistry` to make `attr` to work with your transform.
54 |
--------------------------------------------------------------------------------
/docs/ember/README.md:
--------------------------------------------------------------------------------
1 | # Working With Ember
2 |
3 | In this section, we cover how to use TypeScript effectively with specific Ember APIs \(anything you'd find under the `@ember` package namespace\).
4 |
5 | We do _not_ cover general usage of Ember; instead, we assume that as background knowledge. Please see the Ember [Guides](https://guides.emberjs.com/release/) and [API docs](https://api.emberjs.com/ember/release)!
6 |
7 | ## Outline
8 |
9 | * [Components](./components.md)
10 | * [Services](./services.md)
11 | * [Routes](./routes.md)
12 | * [Controllers](./controllers.md)
13 | * [Helpers](./helpers.md)
14 | * [Testing](./testing.md)
15 |
--------------------------------------------------------------------------------
/docs/ember/controllers.md:
--------------------------------------------------------------------------------
1 | # Controllers
2 |
3 | Like [routes](./routes.md), controllers are just normal classes with a few special Ember lifecycle hooks and properties available.
4 |
5 | The main thing you need to be aware of is special handling around query params. In order to provide type safety for query param configuration, Ember's types specify that when defining a query param's `type` attribute, you must supply one of the allowed types: `'boolean'`, `'number'`, `'array'`, or `'string'` \(the default\). However, if you supply these types as you would in JS, like this:
6 |
7 | ```typescript
8 | import Controller from "@ember/controller";
9 |
10 | export default class HeyoController extends Controller {
11 | queryParams = [
12 | {
13 | category: { type: "array" },
14 | },
15 | ];
16 | }
17 | ```
18 |
19 | Then you will see a type error like this:
20 |
21 | ```text
22 | Property 'queryParams' in type 'HeyoController' is not assignable to the same property in base type 'Controller'.
23 | Type '{ category: { type: string; }; }[]' is not assignable to type '(string | Record)[]'.
24 | Type '{ category: { type: string; }; }' is not assignable to type 'string | Record'.
25 | Type '{ category: { type: string; }; }' is not assignable to type 'Record'.
26 | Property 'category' is incompatible with index signature.
27 | Type '{ type: string; }' is not assignable to type 'string | QueryParamConfig | undefined'.
28 | Type '{ type: string; }' is not assignable to type 'QueryParamConfig'.
29 | Types of property 'type' are incompatible.
30 | Type 'string' is not assignable to type '"string" | "number" | "boolean" | "array" | undefined'.ts(2416)
31 | ```
32 |
33 | This is because TS currently infers the type of `type: "array"` as `type: string`. You can work around this by supplying `as const` after the declaration:
34 |
35 | ```diff
36 | import Controller from "@ember/controller";
37 |
38 | export default class HeyoController extends Controller {
39 | queryParams = [
40 | {
41 | - category: { type: "array" },
42 | + category: { type: "array" as const },
43 | },
44 | ];
45 | }
46 | ```
47 |
48 | Now it will type-check.
49 |
50 |
--------------------------------------------------------------------------------
/docs/ember/helpers.md:
--------------------------------------------------------------------------------
1 | # Helpers
2 |
3 | Helpers in Ember are just functions or classes with a well-defined interface, which means they largely Just Work™ with TypeScript. However, there are a couple things you’ll want to watch out for.
4 |
5 | {% hint style="info" %}
6 | As always, you should start by reading and understanding the [Ember Guide on Helpers](https://guides.emberjs.com/release/templates/writing-helpers/)!
7 | {% endhint %}
8 |
9 | ## Function-based helpers
10 |
11 | The basic type of a helper function in Ember is:
12 |
13 | ```typescript
14 | type FunctionBasedHelper =
15 | (positional: unknown[], named: Record) => string | void;
16 | ```
17 |
18 | This represents a function which _may_ have an arbitrarily-long list of positional arguments, which _may_ be followed by a single dictionary-style object containing any named arguments.
19 |
20 | There are three important points about this definition:
21 |
22 | 1. `positional` is an array of `unknown`, of unspecified length.
23 | 2. `named` is a `Record`.
24 | 3. Both arguments are always set, but may be empty.
25 |
26 | Let’s walk through each of these.
27 |
28 | ### Handling `positional` arguments
29 |
30 | The type is an array of `unknown` because we don’t \(yet!\) have any way to make templates aware of the information in this definition—so users could pass in _anything_. We can work around this using [type narrowing](https://microsoft.github.io/TypeScript-New-Handbook/chapters/narrowing/)—TypeScript’s way of using runtime checks to inform the types at runtime.
31 |
32 | ```typescript
33 | function totalLength(positional: unknown[]) {
34 | // Account for case where user passes no arguments
35 | assert(
36 | 'all positional args to `total-length` must be strings',
37 | positional.every(arg => typeof arg === 'string')
38 | );
39 |
40 | // safety: we can cast `positional as string[]` because we asserted above
41 | return (positional as string[]).reduce((sum, s) => sum + s.length, 0);
42 | }
43 | ```
44 |
45 | ### Handling `named` arguments
46 |
47 | We specified the type of `named` as a `Record`. `Record` is a built-in TypeScript type representing a fairly standard type in JavaScript: an object being used as a simple map of keys to values. Here we set the values to `unknown` and the keys to `string`, since that accurately represents what callers may actually pass to a helper.
48 |
49 | \(As with `positional`, we specify the type here as `unknown` to account for the fact that the template layer isn’t aware of types yet.\)
50 |
51 | ### `positional` and `named` presence
52 |
53 | Note that even if the user passes _no_ arguments, both `positional` and `named` are always present. They will just be _empty_ in that case. For example:
54 |
55 | ```typescript
56 | import { helper } from '@ember/component/helper';
57 |
58 | const describe = (entries: string): string => (entries.length > 0 ? entries : '(none)');
59 |
60 | export function showAll(positional: unknown[], named: Record) {
61 | // pretty print each item with its index, like `0: { neat: true }` or
62 | // `1: undefined`.
63 | const positionalEntries = positional
64 | .reduce((items, arg, index) => items.concat(`${index}: ${JSON.stringify(arg)}`), [])
65 | .join(', ');
66 |
67 | // pretty print each item with its name, like `cool: beans` or
68 | // `answer: 42`.
69 | const namedEntries = Object.keys(named)
70 | .reduce(
71 | (items, key) => items.concat(`${key}: ${JSON.stringify(named[key], undefined, 2)}`),
72 | []
73 | )
74 | .join(', ');
75 |
76 | return `positional: ${describe(positionalEntries)}\nnamed: ${describe(namedEntries)}`;
77 | }
78 |
79 | export default helper(showAll);
80 | ```
81 |
82 | ### Putting it all together
83 |
84 | Given those constraints, let’s see what a \(very contrived\) actual helper might look like in practice. Let’s imagine we want to take a pair of strings and join them with a required separator and optional prefix and postfixes:
85 |
86 | ```typescript
87 | import { helper } from '@ember/component/helper';
88 | import { assert } from '@ember/debug';
89 | import { is } from '../../type-utils'
90 |
91 | export function join(positional: [unknown, unknown], named: Dict) {
92 | assert(
93 | `'join' requires two 'string' positional parameters`,
94 | is<[string, string]>(
95 | positional,
96 | positional.length === 2 &&
97 | positional.every(el => typeof el === 'string')
98 | )
99 | );
100 | assert(`'join' requires argument 'separator'`, typeof named.separator === 'string');
101 |
102 | const joined = positional.join(named.separator);
103 | const prefix = typeof named.prefix === 'string' ? named.prefix : '';
104 |
105 | return `${prefix}${joined}`;
106 | }
107 |
108 | export default helper(join);
109 | ```
110 |
111 | ## Class-based helpers
112 |
113 | The basic type of a class-based helper function in Ember is:
114 |
115 | ```typescript
116 | interface ClassBasedHelper {
117 | compute(positional?: unknown[], named?: Record): string | void;
118 | }
119 | ```
120 |
121 | Notice that the signature of `compute` is the same as the signature for the function-based helper! This means that everything we said above applies in exactly the same way here. The only differences are that we can have local state and, by extending from Ember’s `Helper` class, we can hook into the dependency injection system and use services.
122 |
123 | ```typescript
124 | import Helper from '@ember/component/helper';
125 | import { inject as service } from '@ember/service';
126 | import Authentication from 'my-app/services/authentication';
127 |
128 | export default class Greet extends Helper {
129 | @service authentication: Authentication;
130 |
131 | compute() {
132 | return this.authentication.isAuthenticated
133 | ? `Welcome back, ${authentication.userName}!`
134 | : 'Sign in?';
135 | }
136 | ```
137 |
138 | For more details on using decorators, see our [guide to using decorators](https://github.com/typed-ember/ember-cli-typescript/tree/3a434def8b8c8214853cea0762940ccedb2256e8/docs/ember/%28../ts/decorators/%29/README.md). For details on using services, see our [guide to services](https://github.com/typed-ember/ember-cli-typescript/tree/3a434def8b8c8214853cea0762940ccedb2256e8/docs/ember/%28./services/%29/README.md).
139 |
140 |
--------------------------------------------------------------------------------
/docs/ember/routes.md:
--------------------------------------------------------------------------------
1 | # Routes
2 |
3 | Working with Routes is in general just working normal TypeScript classes. Ember's types supply the definitions for the various lifecycle events available within route subclasses, which will provide autocomplete and type-checking along the way in general.
4 |
5 | However, there is one thing to watch out for: the types of the arguments passed to methods will _not_ autocomplete as you may expect. This is because in _general_ a subclass may override a superclass method as long as it calls its superclass's method correctly. This is very bad practice, but it is legal JavaScript! This is never a concern for lifecycle hooks in Ember, because they are called by the framework itself. However, TypeScript does not and cannot know that, so we have to provide the types directly.
6 |
7 | Accordingly, and because the `Transition` type is not currently exported as a public type, you may find it convenient to define it using TypeScript's `ReturnType` utility type, which does exactly what it sounds like and gives us a local type which is the type returned by some function. The `RouterService.transitionTo` returns a `Transition`, so we can rely on that as stable public API to define `Transition` locally ourselves:
8 |
9 | ```typescript
10 | import Route from '@ember/routing/route';
11 | import type RouterService from '@ember/routing/router-service';
12 | type Transition = ReturnType;
13 |
14 | export default class MyRoute extends Route {
15 | beforeModel(transition: Transition) {
16 | // ...
17 | }
18 | }
19 | ```
20 |
21 | This inconsistency will be solved in the future. For now, this workaround gets the job done, and also shows the way to using this information to provide the type of the route's model to other consumers: see [Working with Route Models](../cookbook/working-with-route-models.md) for details!
22 |
23 | ```typescript
24 | import Route from '@ember/routing/route';
25 |
26 | type Resolved
= P extends Promise ? T : P;
27 |
28 | export type MyRouteModel = Resolved>;
29 |
30 | export default class MyRoute extends Route {
31 | model() {
32 | // ...
33 | }
34 | }
35 | ```
36 |
37 | The `Resolved` utility type takes in any type, and if the type is a `Promise` it transforms the type into whatever the `Promise` resolves to; otherwise it just returns the same type. (If you’re using TypeScript 4.5 or later, you can use the built-in `Awaited` type, which does the same thing but more robustly: it also handles nested promises.) As we saw above, `ReturnType` gets us the return type of the function. So our final `MyRouteModel` type takes the return type from our `model` hook, and uses the `Resolved` type to get the type the promise will resolve to—that is, exactly the type we will have available as `@model` in the template and as `this.model` on a controller.
38 |
39 | This in turn allows us to use the route class to define the type of the model on an associated controller.
40 |
41 | ```typescript
42 | import Controller from '@ember/controller';
43 | import type { MyRouteModel } from '../routes/my-route';
44 |
45 | export default class MyController extends Controller {
46 | declare model?: MyRouteModel;
47 |
48 | // ...
49 | }
50 | ```
51 |
52 | Notice here that the `model` is declared as optional. That’s intentional: the `model` for a given controller is _not_ set when the controller is constructed (that actually happens _either_ when the page corresponding to the controller is created _or_ the first time a `` which links to that page is rendered). Instead, the `model` is set on the controller when the corresponding route is successfully entered, via its `setupController` hook.
53 |
--------------------------------------------------------------------------------
/docs/ember/services.md:
--------------------------------------------------------------------------------
1 | # Services
2 |
3 | Ember Services are global singleton classes that can be made available to different parts of an Ember application via dependency injection. Due to their global, shared nature, writing services in TypeScript gives you a build-time-enforcable API for some of the most central parts of your application.
4 |
5 | {% hint style="info" %}
6 | If you are not familiar with Services in Ember, first make sure you have read and understood the [Ember Guide on Services](https://guides.emberjs.com/release/services/)!
7 | {% endhint %}
8 |
9 | ## A basic service
10 |
11 | Let's take this example from the [Ember Guide](https://guides.emberjs.com/release/services/):
12 |
13 | ```typescript
14 | import { A } from '@ember/array';
15 | import Service from '@ember/service';
16 |
17 | export default class ShoppingCartService extends Service {
18 | items = A([]);
19 |
20 | add(item) {
21 | this.items.pushObject(item);
22 | }
23 |
24 | remove(item) {
25 | this.items.removeObject(item);
26 | }
27 |
28 | empty() {
29 | this.items.clear();
30 | }
31 | }
32 | ```
33 |
34 | Just making this a TypeScript file gives us some type safety without having to add any additional type information. We'll see this when we use the service elsewhere in the application.
35 |
36 | {% hint style="info" %}
37 | When working in Octane, you're better off using a `TrackedArray` from [tracked-built-ins](https://github.com/pzuraq/tracked-built-ins) instead of the classic EmberArray:
38 |
39 | ```typescript
40 | import { TrackedArray } from 'tracked-built-ins';
41 | import Service from '@ember/service';
42 |
43 | export default class ShoppingCartService extends Service {
44 | items = new TrackedArray();
45 |
46 | add(item) {
47 | this.items.push(item);
48 | }
49 |
50 | remove(item) {
51 | this.items.splice(1, this.items.findIndex((i) => i === item));
52 | }
53 |
54 | empty() {
55 | this.items.clear();
56 | }
57 | }
58 | ```
59 |
60 | Notice that here we are using only built-in array operations, not Ember's custom array methods.
61 | {% endhint %}
62 |
63 | ## Using services
64 |
65 | You can use a service in any container-resolved object such as a component or another service. Services are injected into these objects by decorating a property with the `inject` decorator. Because decorators can't affect the type of the property they decorate, we must manually type the property. Also, we must use `declare` modifier to tell the TypeScript compiler to trust that this property will be set up by something outside this component—namely, the decorator.
66 |
67 | Here's an example of using the `ShoppingCartService` we defined above in a component:
68 |
69 | ```typescript
70 | import Component from '@glimmer/component';
71 | import { inject as service } from '@ember/service';
72 | import { action } from '@ember/object';
73 |
74 | import ShoppingCartService from 'my-app/services/shopping-cart';
75 |
76 | export default class CartContentsComponent extends Component {
77 | @service declare shoppingCart: ShoppingCartService;
78 |
79 | @action
80 | remove(item) {
81 | this.shoppingCart.remove(item);
82 | }
83 | }
84 | ```
85 |
86 | Any attempt to access a property or method not defined on the service will fail type-checking:
87 |
88 | ```typescript
89 | import Component from '@glimmer/component';
90 | import { inject as service } from '@ember/service';
91 | import { action } from '@ember/object';
92 |
93 | import ShoppingCartService from 'my-app/services/shopping-cart';
94 |
95 | export default class CartContentsComponent extends Component {
96 | @service declare shoppingCart: ShoppingCartService;
97 |
98 | @action
99 | remove(item) {
100 | // Error: Property 'saveForLater' does not exist on type 'ShoppingCartService'.
101 | this.shoppingCart.saveForLater(item);
102 | }
103 | }
104 | ```
105 |
106 | Services can also be loaded from the dependency injection container manually:
107 |
108 | ```typescript
109 | import Component from '@glimmer/component';
110 | import { getOwner } from '@ember/owner';
111 | import { action } from '@ember/object';
112 |
113 | import ShoppingCartService from 'my-app/services/shopping-cart';
114 |
115 | export default class CartContentsComponent extends Component {
116 | get cart() {
117 | return getOwner(this)?.lookup('service:shopping-cart') as ShoppingCartService;
118 | }
119 |
120 | @action
121 | remove(item) {
122 | this.cart.remove(item);
123 | }
124 | }
125 | ```
126 |
127 | Here we need to cast the lookup result to `ShoppingCartService` in order to get any type-safety because the lookup return type is `any` \(see caution below\).
128 |
129 | {% hint style="danger" %}
130 | This type-cast provides no guarantees that what is returned by the lookup is actually the service you are expecting. Because TypeScript cannot resolve the lookup micro-syntax \(`service:`\) to the service class, a typo would result in returning something other than the specified type. It only gurantees that _if_ the expected service is returned that you are using it correctly.
131 |
132 | There is a merged \(but not yet implemented\) [RFC](https://emberjs.github.io/rfcs/0585-improved-ember-registry-apis.html) which improves this design and makes it straightforward to type-check. Additionally, TypeScript 4.1's introduction of [template types](https://devblogs.microsoft.com/typescript/announcing-typescript-4-1/#template-literal-types) may allow us to supply types that work with the microsyntax.
133 |
134 | For now, however, remember that _the cast is unsafe_!
135 | {% endhint %}
136 |
137 |
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | {% hint style="success" %}
2 |
3 | -------------------
4 |
5 | ## TypeScript docs have moved! 🎉
6 |
7 | This documentation is now hosted on the ember guides website here: [Using TypeScript with Ember](https://guides.emberjs.com/release/typescript/)
8 |
9 | -----------------
10 |
11 | {% endhint %}
12 |
13 | # ember-cli-typescript
14 |
15 | This guide is designed to help you get up and running with TypeScript in an Ember app.
16 |
17 | {% hint style="warning" %}
18 |
19 | **This is _not_ an introduction to TypeScript _or_ Ember. Throughout this guide, we’ll link back to [the TypeScript docs](https://www.typescriptlang.org/docs/home.html) and [the Ember Guides](https://guides.emberjs.com/release/) when there are specific concepts that we will not explain here but which are important for understanding what we’re covering!**
20 |
21 | {% endhint %}
22 |
23 | To get started, check out the instructions in [Getting Started: Installation](./installation.md)
24 |
25 | * If you're totally new to using TypeScript with Ember, start with [TypeScript and Ember](./ts/README.md).
26 | * Once you have a good handle on the basics, you can dive into the guides to working with the APIs specific to [Ember](./ember/README.md) and [Ember Data](./ember-data/README.md).
27 | * If you're working with legacy (pre-Octane) Ember and TypeScript together, you should read [the Legacy Guide](./legacy/README.md).
28 | * Looking for type-checking in Glimmer templates? Check out [Glint](https://typed-ember.gitbook.io/glint/).
29 |
30 | ## Why TypeScript?
31 |
32 | What is TypeScript, and why should you adopt it?
33 |
34 | > TypeScript is a typed superset of JavaScript that compiles to plain JavaScript.
35 | > —[typescriptlang.org](http://www.typescriptlang.org)
36 |
37 | TypeScript lets you build _ambitious web applications_ with confidence—so it’s a perfect fit for Ember apps!
38 |
39 | * Get rid of `undefined is not a function` and `null is not an object` once and for all.
40 | * Enjoy API docs… that are always up-to-date.
41 | * Experience better developer productivity through top-notch editor support, including incredible autocomplete, guided refactorings, automatic imports, and more.
42 |
43 |
--------------------------------------------------------------------------------
/docs/installation.md:
--------------------------------------------------------------------------------
1 | # Installation
2 |
3 | You can simply `ember install` the dependency like normal:
4 |
5 | ```bash
6 | ember install ember-cli-typescript@latest
7 | ```
8 |
9 | All dependencies will be added to your `package.json`, and you're ready to roll!
10 |
11 | **If you're upgrading from a previous release, see (./upgrade-notes.md).**
12 |
13 | Installing ember-cli-typescript modifies your project in two ways:
14 |
15 | * installing a number of other packages to make TypeScript work in your app or addon
16 | * generating a number of files in your project
17 |
18 | ## Other packages this addon installs
19 |
20 | We install all of the following packages at their current "latest" value, :
21 |
22 | * `typescript`
23 | * `ember-cli-typescript-blueprints`
24 | * `@types/ember`
25 | * `@types/ember-data`
26 | * `@types/ember__*` – `@types/ember__object` for `@ember/object` etc.
27 | * `@types/ember-data__*` – `@types/ember-data__model` for `@ember-data/model` etc.
28 | * `@types/rsvp`
29 |
30 | ## Files this addon generates
31 |
32 | We also add the following files to your project:
33 |
34 | * [`tsconfig.json`](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html)
35 | * `types//index.d.ts` – the location for any global type declarations you need to write for you own application; see [**Using TS Effectively: Global types for your package**](https://github.com/typed-ember/ember-cli-typescript/tree/3a434def8b8c8214853cea0762940ccedb2256e8/docs/getting-started/docs/ts/using-ts-effectively/README.md#global-types-for-your-package) for information on its default contents and how to use it effectively
36 | * `app/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](installation.md#environment-and-configuration-typings) for details
37 |
38 |
--------------------------------------------------------------------------------
/docs/legacy/README.md:
--------------------------------------------------------------------------------
1 | # Working With Ember Classic
2 |
3 | We emphasize the happy path of working with Ember in the [Octane Edition](https://emberjs.com/editions/octane/). However, there are times you’ll need to understand these details:
4 |
5 | 1. Most existing applications make heavy use of the pre-Octane \(“legacy”\) Ember programming model, and we support that model—with caveats.
6 | 2. Several parts of Ember Octane \(specifically: routes, controllers, services, and class-based helpers\) continue to use these concepts under the hood, and our types support that—so understanding them may be important at times.
7 |
8 | The rest of this guide is dedicated to helping you understand how `ember-cli-typescript` and the classic Ember system interact.
9 |
10 |
--------------------------------------------------------------------------------
/docs/legacy/computed-properties.md:
--------------------------------------------------------------------------------
1 | # Computed Properties
2 |
3 | There are two variants of Ember’s computed properties you may encounter:
4 |
5 | * the decorator form used with native \(ES6\) classes
6 | * the callback form used with classic classes \(based on EmberObject\)
7 |
8 | ## Decorator form
9 |
10 | ```typescript
11 | import Component from '@ember/component';
12 | import { computed } from '@ember/object/computed';
13 |
14 | export default class UserProfile extends Compoennt {
15 | name = 'Chris';
16 | age = 33;
17 |
18 | @computed('name', 'age')
19 | get bio() {
20 | return `${this.name} is `${this.age}` years old!`;
21 | }
22 | }
23 | ```
24 |
25 | Note that it is impossible for `@computed` to know whether the keys you pass to it are allowed or not. Migrating to Octane eliminates this issue, since you mark reactive root state with `@tracked` and leave getters undecorated, rather than vice versa.
26 |
27 | ## Callback form
28 |
29 | Computed properties in the classic object model take a callback instead:
30 |
31 | ```typescript
32 | import Component from '@ember/component';
33 | import { computed } from '@ember/object/computed';
34 |
35 | const UserProfile = Component.extend({
36 | name: 'Chris',
37 | age: 32,
38 |
39 | bio: computed('name', 'age', function() {
40 | return `${this.get('name')} is `${this.get('age')}` years old!`;
41 | }),
42 | })
43 |
44 | export default UserProfile;
45 | ```
46 |
47 | This definition will not type-check, however. You will need to explicitly write out a `this` type for computed property callbacks for `get` and `set` to type-check correctly:
48 |
49 | ```typescript
50 | import Component from '@ember/component';
51 | import { computed } from '@ember/object/computed';
52 |
53 | const UserProfile = Component.extend({
54 | name: 'Chris',
55 | age: 32,
56 |
57 | bio: computed('name', 'age', function(this: UserProfile) {
58 | // ^---------------^
59 | // `this` tells TS to use `UserProfile` for `get` and `set` lookups;
60 | // otherwise `this.get` below would not know the types of `'name'` or
61 | // `'age'` or even be able to suggest them for autocompletion.
62 | return `${this.get('name')} is `${this.get('age')}` years old!`;
63 | }),
64 | })
65 |
66 | export default UserProfile;
67 | ```
68 |
69 | Note that this _does not always work_: you may get warnings from TypeScript about the item being defined in terms of itself.
70 |
71 | **Accordingly, we strongly recommend migrating classic classes to ES native classes** _**before**_ **adding TypeScript!**
72 |
73 |
--------------------------------------------------------------------------------
/docs/legacy/ember-component.md:
--------------------------------------------------------------------------------
1 | # EmberComponent
2 |
3 |
--------------------------------------------------------------------------------
/docs/legacy/ember-object.md:
--------------------------------------------------------------------------------
1 | # EmberObject
2 |
3 | When working with the legacy Ember object model, `EmberObject`, there are a number of caveats and limitations you need to be aware of. For today, these caveats and limitations apply to any classes which extend directly from `EmberObject`, or which extend classes which _themselves_ extend `EmberObject`:
4 |
5 | * `Component` – meaning _classic_ Ember components, which imported from `@ember/component`, _not_ Glimmer components which are imported from `@glimmer/component` and do _not_ extend the `EmberObject` base class.
6 | * `Controller`
7 | * `Helper` – note that this applies only to the _class_ form. Function-based helpers do not involve the `EmberObject` base class.
8 | * `Route`
9 | * `Router`
10 | * `Service`
11 | * Ember Data’s `Model` class
12 |
13 | Additionally, Ember’s mixin system is deeply linked to the semantics and implementation details of `EmberObject`, _and_ it has the most caveats and limitations.
14 |
15 | {% hint style="info" %}
16 | In the future, some of these may be able to drop their `EmberObject` base class dependency, but that will not happen till at least the next major version of Ember, and these guides will be updated when that happens.
17 | {% endhint %}
18 |
19 | ## Mixins and classic class syntax
20 |
21 | The Ember mixin system is the legacy Ember construct TypeScript supports _least_ well, as described in [Mixins](https://github.com/typed-ember/ember-cli-typescript/tree/3a434def8b8c8214853cea0762940ccedb2256e8/docs/legacy/mixins/README.md). While this may not be intuitively obvious, the classic class syntax simply _is_ the mixin system. Every classic class creation is a case of mixing together multiple objects to create a new base class with a shared prototype. The result is that any time you see the classic `.extend({ ... })` syntax, regardless of whether there is a named mixin involved, you are dealing with Ember's legacy mixin system. This in turn means that you are dealing with the parts of Ember which TypeScript is _least_ able to handle well.
22 |
23 | While we describe here how to use types with classic \(mixin-based\) classes insofar as they _do_ work, there are many failure modes. As a result, we strongly recommend moving away from both classic classes and mixins, and as quickly as possible. This is the direction the Ember ecosystem as a whole is moving, but it is _especially_ important for TypeScript users.
24 |
25 | {% hint style="info" %}
26 | The [Ember Atlas](https://emberatlas.com) includes guides for migrating [from classic classes to native classes](https://www.notion.so/Native-Classes-55bd67b580ca49f999660caf98aa1897), along with [a variety of patterns](https://www.notion.so/Converting-Classes-with-Mixins-5dc68c0ac3044e51a218fa7aec71c2db) for dealing with specific kinds of mixins in your codebase.
27 | {% endhint %}
28 |
29 | ### Failure modes
30 |
31 | You often need to define `this` in actions hashes, computed properties, etc. That in turn often leads to problems with self-referential `this`: TypeScript simply cannot figure out how to stop recursing through the definitions of the type.
32 |
33 | Additionally, even when you get past the endlessly-recursive type definition problems, when enough mixins are resolved TypeScript will occasionally just give up because it cannot resolve the property or method you're interested in across the many shared base classes.
34 |
35 | Finally, when you have "zebra-striping" of your classes between classic classes and native classes, your types will often stop resolving.
36 |
37 | ## Native classes
38 |
39 | ### `EmberObject`
40 |
41 | In general, we recommend \(following the Ember Octane guides\) that any class which extends directly from the `EmberObject` base class eliminate any use of `EmberObject`-specific API and convert to standalone class, with no base class at all. You can follow the [ember-classic-decorator](https://github.com/emberjs/ember-classic-decorator) workflow to eliminate the base class—switching from `init` to `constructor`, getting rid of uses of methods like `this.set` and `this.get` in favor of using standalone `set` and `get`, and so on.
42 |
43 | ### `EmberObject`-descended classes
44 |
45 | The framework base classes which depend on `EmberObject` cannot follow the exact same path. However, as long as you are using native class syntax, all of these \(`Component`, `Controller`, `Helper`, etc.\) work nicely and safely with TypeScript. In each of these cases, the same caveats apply as with `EmberObject` itself, and you should follow the [ember-classic-decorator](https://github.com/emberjs/ember-classic-decorator) workflow with them as well if you are converting an existing app or addon. However, because these base classes themselves descend from `EmberObject`, you will not be able to remove the base classes as you can with your _own_ classes which descend _directly_ from `EmberObject`. Instead, you will continue to extend from the Ember base classes:
46 |
47 | ```typescript
48 | import Component from '@ember/component';
49 | export default class Profile extends Component {}
50 | ```
51 |
52 | ```typescript
53 | import Controller from '@ember/controller';
54 | export default class IndexController extends Controller {}
55 | ```
56 |
57 | ```typescript
58 | import Helper from '@ember/component/helper';
59 | export default class Localize extends Helper {}
60 | ```
61 |
62 | ```typescript
63 | import Route from '@ember/routing/route';
64 | export default class ApplicationRoute extends Route {}
65 | ```
66 |
67 | ```typescript
68 | import EmberRouter from '@ember/routing/router'
69 | export default class AppRouter extends EmberRouter {}
70 | ```
71 |
72 | ```typescript
73 | import Service from '@ember/service';
74 | export default class Session extends Service {}
75 | ```
76 |
77 | ```typescript
78 | import Model from '@ember-data/model';
79 | export default class User extends Model {}
80 | ```
81 |
82 |
--------------------------------------------------------------------------------
/docs/legacy/mixins.md:
--------------------------------------------------------------------------------
1 | # Mixins
2 |
3 | Mixins are fundamentally hostile to robust typing with TypeScript. While you can supply types for them, you will regularly run into problems with self-referentiality in defining properties within the mixins.
4 |
5 | As a stopgap, you can refer to the type of a mixin using the `typeof` operator.
6 |
7 | In general, however, prefer to use one of the following four strategies for migrating _away_ from mixins before attempting to convert code which relies on them to TypeScript:
8 |
9 | 1. For functionality which encapsulates DOM modification, rewrite as a custom modifier using [ember-modifier](https://github.com/emeber-modifier/ember-modifier).
10 | 2. If the mixin is a way of supplying shared behavior \(not data\), extract it to utility functions, usually just living in module scope and imported and exported as needed.
11 | 3. If the mixin is a way of supplying non-shared state which follows the lifecycle of a given object, replace it with a utility class instantiated in the owning class's `constructor` \(or `init` for legacy classes\).
12 | 4. If the mixin is a way of supplying long-lived, shared state, replace it with a service and inject it where it was used before. This pattern is uncommon, but sometimes appears when mixing functionality into multiple controllers or services.
13 |
14 | You can also use inheritance and class decorators to accomplish some of the same semantics as mixins classically supplied. However, these patterns are more fragile and therefore not recommended.
15 |
16 |
--------------------------------------------------------------------------------
/docs/troubleshooting/README.md:
--------------------------------------------------------------------------------
1 | # Troubleshooting
2 |
3 | Stuck with something? Hopefully one of the documents below can help. If not, file an issue on GitHub and we'll try to help you get it sorted (and it may end up in here).
4 |
5 | ## Outline
6 |
7 | * [Conflicting Type Dependencies](./conflicting-types.md)
8 |
--------------------------------------------------------------------------------
/docs/troubleshooting/conflicting-types.md:
--------------------------------------------------------------------------------
1 | # Conflicting Type Dependencies
2 |
3 | You will sometimes see **Duplicate identifier** errors when type-checking your application.
4 |
5 | An example duplicate identifier error \`\`\`sh yarn tsc --noEmit yarn run v1.15.2 $ /Users/chris/dev/teaching/emberconf-2019/node\_modules/.bin/tsc --noEmit node\_modules/@types/ember\_\_object/index.d.ts:23:22 - error TS2300: Duplicate identifier 'EmberObject'. 23 export default class EmberObject extends CoreObject.extend\(Observable\) {} ~~~~~~~~~~~ node\_modules/@types/ember\_\_component/node\_modules/@types/ember\_\_object/index.d.ts:23:22 23 export default class EmberObject extends CoreObject.extend\(Observable\) {} ~~~~~~~~~~~ 'EmberObject' was also declared here. node\_modules/@types/ember\_\_component/node\_modules/@types/ember\_\_object/index.d.ts:23:22 - error TS2300: Duplicate identifier 'EmberObject'. 8 export default class EmberObject extends CoreObject.extend\(Observable\) {} ~~~~~~~~~~~ node\_modules/@types/ember\_\_object/index.d.ts:23:22 23 export default class EmberObject extends CoreObject.extend\(Observable\) {} ~~~~~~~~~~~ 'EmberObject' was also declared here. Found 2 errors. error Command failed with exit code 1. \`\`\`
6 |
7 | This occurs whenever your `yarn.lock` or `package-lock.json` files include more than a single copy of a given set of type definitions—here, types for `@ember/object`, named `@types/ember__object`. See below for details on the package manager behavior, and **Understanding the Package Names** for details on the package names.
8 |
9 | ## Workarounds
10 |
11 | There are currently three recommended workarounds for this:
12 |
13 | * If using `npm`, you can use `npm upgrade --depth=1 @types/ember__object` to upgrade just that specific dependency and anywhere it is used as a transitive dependency of your top-level dependencies. You can also use its `npm dedupe` command, which may resolve the issue.
14 | * If using `yarn`, you can specify a specific version of the package to use in the `"resolutions"` key in `package.json`. For example, if you saw that you had `@types/ember__object@3.0.8` from the default package installs but `@types/ember__object@3.0.5` from `some-cool-ts-addon`, you could force yarn to use `3.0.8` like so:
15 |
16 | ```javascript
17 | {
18 | "resolutions": {
19 | "@types/ember__object": "3.0.8"
20 | }
21 | }
22 | ```
23 |
24 | * You can identify the dependencies which installed the type dependencies transitively, and uninstall and reinstall them. For example, if running `yarn why` reported you had one version of `@types/ember__object` from [the normally-installed set of packages](https://github.com/typed-ember/ember-cli-typescript/tree/3a434def8b8c8214853cea0762940ccedb2256e8/docs/README.md#other-packages-this-addon-installs), and one from `some-cool-ts-addon`, you could run this:
25 |
26 | ```bash
27 | yarn remove @types/ember some-cool-ts-addon
28 | yarn add -D @types/ember some-cool-ts-addon
29 | ```
30 |
31 | You may _also_ be able to use [`yarn-deduplicate`](https://github.com/atlassian/yarn-deduplicate), but this does not work 100% of the time, so if you try it and are still seeing the issues, try one of the solutions above.
32 |
33 | ## Understanding the Problem
34 |
35 | When you are using TypeScript in your Ember application, you consume Ember's types through [DefinitelyTyped](https://github.com/DefinitelyTyped/DefinitelyTyped), the tool the TypeScript team built to power the `@types/*` definitions. That tooling examines the dependencies implied by the package imports and generates a `package.json` with those types specified with a `*` dependency version. On initial installation of your dependencies, yarn installs the highest version of the package available, and correctly deduplicates that across both your own package and all the `@types` packages which reference each other.
36 |
37 | However, later installs may introduce conflicting versions of the types, simply by way of yarn's normal update rules. TypeScript requires that there be one and only one type definition a given item can resolve to. Yarn actively avoids changing a previously-installed version of a transitive dependency when a newly installed package depends on the same dependency transitively. Thus, if one of your dependencies _also_ depends on the same package from `@types/*` that you do, and you upgrade your dependence on that type by editing your `package.json` file and running `yarn` or `npm install` again, TypeScript will suddenly start offering the error described in detail above:
38 |
39 | > Duplicate identifier 'EmberObject'.ts\(2300\)
40 |
41 | Let's imagine three packages, `A`, `B`, and `C`, where `A` is _your_ app or library, and `B` and `C` have the following versions and dependencies:
42 |
43 | * `C` is currently at version `1.2.3`.
44 | * `B` is at version `4.5.6`. It depends on `C` with a `*` dependency. So the `dependencies` key in its `package.json` looks like this:
45 |
46 | ```javascript
47 | {
48 | "dependencies": {
49 | "C": "*"
50 | }
51 | }
52 | ```
53 |
54 | Now, you install _only_ `B` \(this is the equivalent of installing just the basic type definitions in your package\):
55 |
56 | ```javascript
57 | {
58 | "dependencies": {
59 | "B": "~4.5.6"
60 | }
61 | }
62 | ```
63 |
64 | The first time you install these, you will get a _single_ version of `C` – `1.2.3`.
65 |
66 | Now, let's say that `C` publishes a new version, `1.2.4`, and `A` \(your app or library\) adds a dependency on both `C` like so:
67 |
68 | ```javascript
69 | {
70 | "dependencies": {
71 | "B": "~4.5.6",
72 | "C": "~1.2.0"
73 | }
74 | }
75 | ```
76 |
77 | When your package manager runs \(especially in the case of `yarn`\), it goes out of its way to leave the _existing_ installation of `C` in place, while adding a _new_ version for you as a top-level consumer. So now you have two versions of `C` installed in your `node_modules` directory: `1.2.3` \(for `B`\) and `1.2.4` \(for `A`, your app or library\).
78 |
79 | What's important to understand here is that this is _exactly_ the behavior you want as the default in the Node ecosystem. Automatically updating a transitive dependency—even when the change is simply a bug fix release—_can_ cause your entire app or library to stop working. If one of your dependencies accidentally depended on that buggy behavior, and adding a direct dependency on the fixed version caused the buggy version to be upgraded, you're just out of luck. Yarn accounts for this by resolving packages to the same version during initial installation, but leaving existing package resolutions as they are when adding new dependencies later.
80 |
81 | Unfortunately, this is also the _opposite_ of what you want for TypeScript, which needs a single source of truth for the types in your app or library. When you install the type definitions, and then _later_ install a package which transitively depends on those type definitions, you end up with multiple sources of truth for the types.
82 |
83 | ## Understanding the Workarounds
84 |
85 | The solutions listed above both make sure npm apd Yarn only install a single version of the package.
86 |
87 | * Explicitly upgrading the dependencies or using `dedupe` resolves to a single version in npm.
88 | * Specifying a version in the `"resolutions"` field in your `package.json` simply forces Yarn to resolve _every_ reference to that package to a single version. This actually works extremely well for types, but it means that every time you either update the types package\(s\) yourself _or_ update a package which transitively depends on them, you have to edit this value manually as well.
89 | * Uninstalling and reinstalling both the impacted packages and _all_ the packages which transitively depend on them gives you the same behavior as an initial install… because that's exactly what you're doing. The downside, of course, is that you have to identify and uninstall and reinstall all top-level packages which transitively depend on the files, and this introduces risk by way of _other_ transitive dependencies being updated.
90 |
91 |
--------------------------------------------------------------------------------
/docs/ts/README.md:
--------------------------------------------------------------------------------
1 | # TypeScript and Ember
2 |
3 | This guide covers the common details and "gotchas" of using TypeScript with Ember. Note that we do _not_ cover the use of TypeScript _or_ Ember in general—for those, you should refer to the corresponding documentation:
4 |
5 | * [TypeScript docs](https://www.typescriptlang.org/docs/index.html)
6 | * [TypeScript Deep Dive](https://basarat.gitbook.io/typescript/)
7 | * [Ember docs](https://emberjs.com/learn/)
8 |
9 | ## Outline
10 |
11 | * [Using TypeScript With Ember Effectively](using-ts-effectively.md)
12 | * [Decorators](decorators.md)
13 | * [Current limitations](current-limitations.md)
14 | * [Building Addons in TypeScript](with-addons.md)
15 | * [Understanding the `@types` Package Names](package-names.md)
16 |
--------------------------------------------------------------------------------
/docs/ts/current-limitations.md:
--------------------------------------------------------------------------------
1 | # Current Limitations
2 |
3 | While TS already works nicely for many things in Ember, there are a number of corners where it _won't_ help you out. Some of them are just a matter of further work on updating the [existing typings](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/ember); others are a matter of further support landing in TypeScript itself, or changes to Ember's object model.
4 |
5 | ## Some `import`s don't resolve
6 |
7 | You'll frequently see errors for imports which TypeScript doesn't know how to resolve. **These won't stop the build from working;** they just mean TypeScript doesn't know where to find those.
8 |
9 | Writing these missing type definitions is a great way to pitch in! Jump in `#topic-typescript` on the [Ember Community Discord server](https://discord.gg/zT3asNS) and we'll be happy to help you.
10 |
11 | ## Templates
12 |
13 | Templates are currently totally non-type-checked. This means that you lose any safety when moving into a template context, even if using a Glimmer `Component` in Ember Octane.
14 |
15 | Addons need to import templates from the associated `.hbs` file to bind to the layout of any components they export. The TypeScript compiler will report that it cannot resolve the module, since it does not know how to resolve files ending in `.hbs`. To resolve this, you can provide this set of definitions to `my-addon/types/global.d.ts`, which will allow the import to succeed:
16 |
17 | ```ts
18 | declare module '\*/template' {
19 | import { TemplateFactory } from 'ember-cli-htmlbars';
20 | const template: TemplateFactory; export default template;
21 | }
22 |
23 |
24 | declare module 'app/templates/\*' {
25 | import { TemplateFactory } from 'ember-cli-htmlbars';
26 | const template: TemplateFactory; export default template;
27 | }
28 |
29 | declare module 'addon/templates/\*' {
30 | import { TemplateFactory } from 'ember-cli-htmlbars';
31 | const template: TemplateFactory; export default template;
32 | }
33 | ```
34 |
35 | ## Invoking actions
36 |
37 | TypeScript won't detect a mismatch between this action and the corresponding call in the template:
38 |
39 | ```ts
40 | import Component from '@ember/component';
41 | import { action } from '@ember/object';
42 |
43 | export default class MyGame extends Component {
44 | @action turnWheel(degrees: number) {
45 | // ...
46 | }
47 | }
48 | ```
49 |
50 | ```hbs
51 |
54 | ```
55 |
56 | Likewise, it won't notice a problem when you use the `send` method:
57 |
58 | ```ts
59 | // TypeScript compiler won't detect this type mismatch
60 | this.send\('turnWheel', 'ALSO-NOT-A-NUMBER'\);
61 | ```
62 |
--------------------------------------------------------------------------------
/docs/ts/decorators.md:
--------------------------------------------------------------------------------
1 | # Decorators
2 |
3 | Ember makes heavy use of decorators, and TypeScript does not currently support deriving type information from decorators.
4 |
5 | As a result, there are three important points that apply to _all_ decorator usage in Ember:
6 |
7 | 1. Whenever using a decorator to declare a class field the framework sets up for you, you should mark it with `declare`. That includes all service and controller injections as well as all Ember Data attributes and relationships.
8 |
9 | Normally, TypeScript determines whether a property is definitely not `null` or `undefined` by checking what you do in the constructor. In the case of service injections, controller injections, or Ember Data model decorations, though, TypeScript does not have visibility into how instances of the class are _initialized_. The `declare` annotation informs TypeScript that a declaration is defined somewhere else, outside its scope.
10 |
11 | 2. For Ember Data Models, you will need to use the optional `?` operator on field declarations if the field is optional \(`?`\). See the Ember Data section of the guide for more details!
12 |
13 | 3. You are responsible to write the type correctly. TypeScript does not currently use decorator information at all in its type information. If you write `@service foo` or even `@service('foo') foo`, _Ember_ knows that this resolves at runtime to the service `Foo`, but TypeScript does not and—for now—_cannot_.
14 |
15 | This means that you are responsible to provide this type information, and that you are responsible to make sure that the information remains correct and up to date
16 |
17 | For examples, see the detailed discussions of the two main places decorators are used in the framework:
18 |
19 | * [Services](../ember/services.md)
20 | * [Ember Data Models](../ember-data/models.md)
21 |
--------------------------------------------------------------------------------
/docs/ts/package-names.md:
--------------------------------------------------------------------------------
1 | # Understanding the `@types` Package Names
2 |
3 | You may be wondering why the packages added to your `package.json` and described in [**Installation: Other packages this addon installs**](https://github.com/typed-ember/ember-cli-typescript/tree/3a434def8b8c8214853cea0762940ccedb2256e8/docs/README.md#other-packages-this-addon-installs) are named things like `@types/ember__object` instead of something like `@types/@ember/object`. This is a conventional name used to allow both the compiler and the [DefinitelyTyped](https://github.com/DefinitelyTyped/DefinitelyTyped) publishing infrastructure \([types-publisher](https://github.com/Microsoft/types-publisher)\) to handle scoped packages, documented under [**What about scoped packages?**](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master#what-about-scoped-packages) in [the DefinitelyTyped README](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master).
4 |
5 | See also:
6 |
7 | * [Microsoft/types-publisher\#155](https://github.com/Microsoft/types-publisher/issues/155)
8 | * [Microsoft/Typescript\#14819](https://github.com/Microsoft/TypeScript/issues/14819)
9 |
10 |
--------------------------------------------------------------------------------
/docs/ts/with-addons.md:
--------------------------------------------------------------------------------
1 | # Building Addons in TypeScript
2 |
3 | Building addons in TypeScript offers many of the same benefits as building apps that way: it puts an extra tool at your disposal to help document your code and ensure its correctness. For addons, though, there's one additional bonus: publishing type information for your addons enables autocomplete and inline documentation for your consumers, even if they're not using TypeScript themselves.
4 |
5 | ## Key Differences from Apps
6 |
7 | To process `.ts` files, `ember-cli-typescript` tells Ember CLI to [register a set of Babel plugins](https://devblogs.microsoft.com/typescript/typescript-and-babel-7/) so that Babel knows how to strip away TypeScript-specific syntax. This means that `ember-cli-typescript` operates according to the same set of rules as other preprocessors when used by other addons.
8 |
9 | * Like other addons that preprocess source files, **`ember-cli-typescript` must be in your addon's `dependencies`, not `devDependencies`**.
10 | * Because addons have no control over how files in `app/` are transpiled, **you cannot have `.ts` files in your addon's `app/` folder**.
11 |
12 | ## Publishing
13 |
14 | When you publish an addon written in TypeScript, the `.ts` files will be consumed and transpiled by Babel as part of building the host application the same way `.js` files are, in order to meet the requirements of the application's `config/targets.js`. This means that no special steps are required for your source code to be consumed by users of your addon.
15 |
16 | Even though you publish the source `.ts` files, though, by default you consumers who also use TypeScript won't be able to benefit from those types, because the TS compiler isn't aware of how `ember-cli` resolves import paths for addon files. For instance, if you write `import { foo } from 'my-addon/bar';`, the typechecker has no way to know that the actual file on disk for that import path is at `my-addon/addon/bar.ts`.
17 |
18 | In order for your addon's users to benefit from type information from your addon, you need to put `.d.ts` _declaration files_ at the location on disk where the compiler expects to find them. This addon provides two commands to help with that: `ember ts:precompile` and `ember ts:clean`. The default `ember-cli-typescript` blueprint will configure your `package.json` to run these commands in the `prepack` and `postpack` phases respectively, but you can also run them by hand to verify that the output looks as you expect.
19 |
20 | The `ts:precompile` command will populate the overall structure of your package with `.d.ts` files laid out to match their import paths. For example, `addon/index.ts` would produce an `index.d.ts` file in the root of your package.
21 |
22 | The `ts:clean` command will remove the generated `.d.ts` files, leaving your working directory back in a pristine state.
23 |
24 | The TypeScript compiler has very particular rules when generating declaration files to avoid letting private types leak out unintentionally. You may find it useful to run `ember ts:precompile` yourself as you're getting a feel for these rules to ensure everything will go smoothly when you publish.
25 |
26 | ## Linking Addons
27 |
28 | Often when developing an addon, it can be useful to run that addon in the context of some other host app so you can make sure it will integrate the way you expect, e.g. using [`yarn link`](https://yarnpkg.com/en/docs/cli/link#search) or [`npm link`](https://docs.npmjs.com/cli/link).
29 |
30 | When you do this for a TypeScript addon, the source files will be picked up in the host app build and everything will execute at runtime as you'd expect. If the host app is also using TypeScript, though, it won't be able to resolve imports from your addon by default, for the reasons outlined above in the Publishing section.
31 |
32 | You could run `ember ts:precompile` in your addon any time you change a file, but for development a simpler option is to temporarily update the `paths` configuration in the host application so that it knows how to resolve types from your linked addon.
33 |
34 | Add entries for `` and `/*` in your `tsconfig.json` like so:
35 |
36 | ```javascript
37 | compilerOptions: {
38 | // ...other options
39 | paths: {
40 | // ...other paths, e.g. for your app/ and tests/ trees
41 | // resolve: import x from 'my-addon';
42 | "my-addon": [
43 | "node_modules/my-addon/addon"
44 | ],
45 | // resolve: import y from 'my-addon/utils/y';
46 | "my-addon/*": [
47 | "node_modules/my-addon/addon/*"
48 | ]
49 | }
50 | }
51 | ```
52 |
53 | ## In-Repo Addons
54 |
55 | [In-repo addons](https://ember-cli.com/extending/#detailed-list-of-blueprints-and-their-use) work in much the same way as linked ones. Their `.ts` files are managed automatically by `ember-cli-typescript` in their `dependencies`, and you can ensure imports resolve correctly from the host by adding entries in `paths` in the base `tsconfig.json` file.
56 |
57 | ```javascript
58 | compilerOptions: {
59 | // ...other options
60 | paths: {
61 | // ...other paths, e.g. for your tests/ tree
62 | "my-app": [
63 | "app/*",
64 | // add addon app directory that will be merged with the host application
65 | "lib/my-addon/app/*"
66 | ],
67 | // resolve: import x from 'my-addon';
68 | "my-addon": [
69 | "lib/my-addon/addon"
70 | ],
71 | // resolve: import y from 'my-addon/utils/y';
72 | "my-addon/*": [
73 | "lib/my-addon/addon/*"
74 | ]
75 | }
76 | }
77 | ```
78 |
79 | One difference as compared to regular published addons: you know whether or not the host app is using `ember-cli-typescript`, and if it is, you can safely put `.ts` files in an in-repo addon's `app/` folder.
80 |
81 |
--------------------------------------------------------------------------------
/docs/upgrade-notes.md:
--------------------------------------------------------------------------------
1 | # Upgrading from 1.x
2 |
3 | There are a number of important changes between ember-cli-typescript v1 and v2, which mean the upgrade process is _straightforward_ but _specific_:
4 |
5 | 1. Update ember-cli-babel. Fix any problems introduced during the upgrade.
6 | 2. Update ember-decorators. Fix any problems introduced during the upgrade.
7 | 3. Update ember-cli-typescript. Follow the detailed upgrade guide below to fix discrepancies between Babel and TypeScript's compiled output.
8 |
9 | If you deviate from this order, you are likely to have a _much_ more difficult time upgrading!
10 |
11 | ## Update ember-cli-babel
12 |
13 | ember-cli-typescript **requires** ember-cli-babel at version 7.1.0 or above, which requires ember-cli 2.13 or above. It also **requires** @babel/core 7.2.0 or higher.
14 |
15 | The recommended approach here is to deduplicate existing installations of the dependency, remove and reinstall ember-cli-babel to make sure that all its transitive dependencies are updated to the latest possible, and then to deduplicate _again_.
16 |
17 | If using yarn:
18 |
19 | ```bash
20 | npx yarn-deduplicate
21 | yarn remove ember-cli-babel
22 | yarn add --dev ember-cli-babel
23 | npx yarn-deduplicate
24 | ```
25 |
26 | If using npm:
27 |
28 | ```bash
29 | npm dedupe
30 | npm uninstall ember-cli-babel
31 | npm install --save-dev ember-cli-babel
32 | npm dedupe
33 | ```
34 |
35 | Note: If you are also using ember-decorators—and specifically the babel-transform that gets added with it—you will need update @ember-decorators/babel-transforms as well \(anything over 3.1.0 should work\):
36 |
37 | ```bash
38 | ember install ember-decorators@^3.1.0 @ember-decorators/babel-transforms@^3.1.0
39 | ```
40 |
41 | ## Update ember-decorators
42 |
43 | If you're on a version of Ember before 3.10, follow the same process of deduplication, reinstallation, and re-deduplication as described for ember-cli-babel above for ember-decorators. This will get you the latest version of ember-decorators and, importantly, its @ember-decorators/babel-transforms dependency.
44 |
45 | ## Update ember-cli-typescript
46 |
47 | Now you can simply `ember install` the dependency like normal:
48 |
49 | ```bash
50 | ember install ember-cli-typescript@latest
51 | ```
52 |
53 | _**Note:**_ **To work properly, starting from v2, ember-cli-typescript must be declared as a `dependency`, not a `devDependency` for addons. With `ember install` this migration would be automatically handled for you.**
54 |
55 | If you choose to make the upgrade manually with yarn or npm, here are the steps you need to follow:
56 |
57 | 1. Remove ember-cli-typescript from your `devDependencies`.
58 |
59 | With yarn:
60 |
61 | ```bash
62 | yarn remove ember-cli-typescript
63 | ```
64 |
65 | With npm:
66 |
67 | ```bash
68 | npm uninstall ember-cli-typescript
69 | ```
70 |
71 | 2. Install the latest of ember-cli-typescript as a `dependency`:
72 |
73 | With yarn:
74 |
75 | ```bash
76 | yarn add ember-cli-typescript@latest
77 | ```
78 |
79 | With npm:
80 |
81 | ```bash
82 | npm install --save ember-cli-typescript@latest
83 | ```
84 |
85 | 3. Run `ember generate`:
86 |
87 | ```bash
88 | ember generate ember-cli-typescript
89 | ```
90 |
91 | ### Account for addon build pipeline changes
92 |
93 | Since we now integrate in a more traditional way into Ember CLI's build pipeline, there are two changes required for addons using TypeScript.
94 |
95 | * Addons can no longer use `.ts` in `app`, because an addon's `app` directory gets merged with and uses the _host's_ \(i.e. the other addon or app's\) preprocessors, and we cannot guarantee the host has TS support. Note that `.ts` will continue to work for in-repo addons because the app build works with the host's \(i.e. the app's, not the addon's\) preprocessors.
96 | * Similarly, apps must use `.js` to override addon defaults in `app`, since the different file extension means apps no longer consistently "win" over addon versions \(a limitation of how Babel + app merging interact\).
97 |
98 | ### Account for TS → Babel issues
99 |
100 | ember-cli-typescript v2 uses Babel to compile your code, and the TypeScript compiler only to _check_ your code. This makes for much faster builds, and eliminates the differences between Babel and TypeScript in the build output that could cause problems in v1. However, because of those differences, you’ll need to make a few changes in the process of upgrading.
101 |
102 | Any place where a type annotation overrides a _getter_
103 |
104 | * Fields like `element`, `disabled`, etc. as annotated defined on a subclass of `Component` and \(correctly\) not initialized to anything, e.g.:
105 |
106 | ```typescript
107 | import Component from '@ember/component';
108 |
109 | export default class Person extends Component {
110 | element!: HTMLImageElement;
111 | }
112 | ```
113 |
114 | This breaks because `element` is a getter on `Component`. This declaration then shadows the getter declaration on the base class and stomps it to `undefined` \(effectively `Object.defineProperty(this, 'element', void 0)`. \(It would be nice to use `declare` here, but that doesn't work: you cannot use `declare` with a getter in a concrete subclass.\)
115 |
116 | Two solutions:
117 |
118 | 1. Annotate locally \(slightly more annoying, but less likely to troll you\):
119 |
120 | ```typescript
121 | class Image extends Component {
122 | useElement() {
123 | let element = this.element as HTMLImageElement;
124 | console.log(element.src);
125 | }
126 | }
127 | ```
128 |
129 | 2. Use a local getter:
130 |
131 | ```typescript
132 | class Image extends Component {
133 | // We do this because...
134 | get _element(): HTMLImageElement {
135 | return this.element as HTMLImageElement;
136 | }
137 |
138 | useElement() {
139 | console.log(this._element.src);
140 | }
141 | }
142 | ```
143 |
144 | Notably, this is not a problem for Glimmer components, so migrating to Octane will also help!
145 |
146 | * `const enum` is not supported at all. You will need to replace all uses of `const enum` with simply `enum` or constants.
147 | * Using ES5 getters or setters with `this` type annotations is not supported through at least Babel 7.3. However, they should also be unnecessary with ES6 classes, so you can simply _remove_ the `this` type annotation.
148 | * Trailing commas after rest function parameters \(`function foo(...bar[],) {}`\) are disallowed by the ECMAScript spec, so Babel also disallows them.
149 | * Re-exports of types have to be disambiguated to be _types_, rather than values. Neither of these will work:
150 |
151 | ```typescript
152 | export { FooType } from 'foo';
153 | ```
154 |
155 | ```typescript
156 | import { FooType } from 'foo';
157 | export { FooType };
158 | ```
159 |
160 | In both cases, Babel attempts to emit a _value_ export, not just a _type_ export, and fails because there is no actual value to emit. You can do this instead as a workaround:
161 |
162 | ```typescript
163 | import * as Foo from 'foo';
164 | export type FooType = Foo.FooType;
165 | ```
166 |
167 |
--------------------------------------------------------------------------------
/ember-cli-build.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const EmberAddon = require('ember-cli/lib/broccoli/ember-addon');
4 |
5 | module.exports = function (defaults) {
6 | let app = new EmberAddon(defaults, {
7 | 'ember-cli-babel': {
8 | throwUnlessParallelizable: true,
9 | },
10 | babel: {
11 | sourceMaps: 'inline',
12 | },
13 | });
14 |
15 | /*
16 | This build file specifies the options for the dummy test app of this
17 | addon, located in `/tests/dummy`
18 | This build file does *not* influence how the addon or the app using it
19 | behave. You most likely want to be modifying `./index.js` or app's build file
20 | */
21 |
22 | return app.toTree();
23 | };
24 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const fs = require('fs');
4 |
5 | // If transpiled output is present, always default to loading that first.
6 | // Otherwise, register ts-node if necessary and load from source.
7 | if (fs.existsSync(`${__dirname}/js/addon.js`)) {
8 | // eslint-disable-next-line node/no-missing-require
9 | module.exports = require('./js/addon').default;
10 | } else {
11 | require('./register-ts-node');
12 |
13 | // eslint-disable-next-line node/no-unpublished-require
14 | module.exports = require('./ts/addon').default;
15 | }
16 |
--------------------------------------------------------------------------------
/is_md_only.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -e
4 |
5 | [[ -z $TRAVIS_COMMIT ]] && exit 1
6 |
7 | CHANGED_FILES=`git diff --name-only master...${TRAVIS_COMMIT}`
8 |
9 | [[ -z $CHANGED_FILES ]] && exit 1
10 |
11 | for CHANGED_FILE in $CHANGED_FILES; do
12 | if ! [[ $CHANGED_FILE =~ .md$ ]]; then
13 | exit 1
14 | fi
15 | done
16 |
--------------------------------------------------------------------------------
/known-typings.md:
--------------------------------------------------------------------------------
1 | # Known Typings
2 |
3 | This is a list of all known typings specific to the Ember.js ecosystem. (You'll of
4 | course find many other modules with their own typings out there.)
5 |
6 | Don't see an addon you use listed here? You might be the one to write them! Check
7 | out [this blog post] or [this quest issue] for tips on how to do it, and feel free
8 | to ask for help in the `#topic-typescript` channel on the [Ember Community Discord server].
9 |
10 | [this blog post]: http://www.chriskrycho.com/2017/typing-your-ember-part-5.html
11 | [this quest issue]: https://github.com/typed-ember/ember-typings/issues/14
12 | [Ember Community Discord server]: https://discord.gg/zT3asNS
13 |
14 | ## Integrated in the addon
15 |
16 | (Someday soon this list should get _long and awesome_! Help us make it happen!)
17 |
18 | * [ember-test-friendly-error-handler](https://github.com/rwjblue/ember-test-friendly-error-handler)
19 | * [True Myth](https://github.com/chriskrycho/true-myth)
20 |
21 | ## DefinitelyTyped (`@types`)
22 |
23 | For addons which do not have types shipped with the addon directly, you may be find them on DefinitelyTyped:
24 |
25 | * `@types/ember-data`
26 | * `@types/ember-mocha`
27 | * `@types/ember-qunit`
28 | * `@types/ember__test-helpers`
29 | * `@types/ember`
30 | * `@types/rsvp`
31 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ember-cli-typescript",
3 | "version": "5.3.0",
4 | "description": "Allow Ember apps to use TypeScript files.",
5 | "keywords": [
6 | "ember-addon",
7 | "typescript"
8 | ],
9 | "repository": "https://github.com/typed-ember/ember-cli-typescript.git",
10 | "license": "MIT",
11 | "author": "Chris Krycho (https://www.chriskrycho.com)",
12 | "directories": {
13 | "doc": "doc",
14 | "test": "tests"
15 | },
16 | "contributors": [
17 | "James C. Davis",
18 | "Dan Freeman",
19 | "Marius Seritan",
20 | "David Gardiner",
21 | "Philip Bjorge"
22 | ],
23 | "bugs": {
24 | "url": "https://github.com/typed-ember/ember-cli-typescript"
25 | },
26 | "homepage": "https://github.com/typed-ember/ember-cli-typescript",
27 | "scripts": {
28 | "build": "ember build",
29 | "lint:js": "eslint --ext js,ts .",
30 | "start": "ember serve",
31 | "test": "yarn test:app && yarn test:node",
32 | "test:app": "ember test",
33 | "test:node": "mocha --loader=ts-node/esm --extension ts ts/tests/**/*.ts",
34 | "ci:prepare": "yarn prepublishOnly && rimraf ts",
35 | "ci:log-version-info": "echo '---- Ember CLI ----' && ember -v && echo '---- TypeScript ----' && tsc -v",
36 | "ci:test": "yarn ci:log-version-info && yarn ci:test:app && yarn ci:test:node",
37 | "ci:test:app": "ember test",
38 | "ci:test:node": "mocha --recursive js/tests",
39 | "prepublishOnly": "yarn tsc --noEmit false --project ts",
40 | "postpublish": "rimraf js"
41 | },
42 | "dependencies": {
43 | "ansi-to-html": "^0.6.15",
44 | "broccoli-stew": "^3.0.0",
45 | "debug": "^4.0.0",
46 | "execa": "^4.0.0",
47 | "fs-extra": "^9.0.1",
48 | "resolve": "^1.5.0",
49 | "rsvp": "^4.8.1",
50 | "semver": "^7.3.2",
51 | "stagehand": "^1.0.0",
52 | "walk-sync": "^2.2.0"
53 | },
54 | "devDependencies": {
55 | "@ember/optional-features": "2.0.0",
56 | "@glimmer/component": "^1.1.2",
57 | "@glimmer/tracking": "^1.1.2",
58 | "@release-it-plugins/lerna-changelog": "^6.1.0",
59 | "@tsconfig/ember": "^1.0.0",
60 | "@typed-ember/renovate-config": "1.2.1",
61 | "@types/capture-console": "1.0.1",
62 | "@types/chai": "4.3.0",
63 | "@types/chai-as-promised": "7.1.4",
64 | "@types/console-ui": "2.2.6",
65 | "@types/core-object": "3.0.1",
66 | "@types/debug": "4.1.7",
67 | "@types/ember": "4.0.1",
68 | "@types/ember-qunit": "5.0.0",
69 | "@types/esprima": "4.0.3",
70 | "@types/express": "4.17.13",
71 | "@types/fs-extra": "9.0.13",
72 | "@types/got": "9.6.12",
73 | "@types/mocha": "10.0.0",
74 | "@types/node": "14.14.31",
75 | "@types/qunit": "2.19.3",
76 | "@types/resolve": "1.20.1",
77 | "@types/semver": "7.3.9",
78 | "@typescript-eslint/eslint-plugin": "5.10.1",
79 | "@typescript-eslint/parser": "5.10.1",
80 | "broccoli-asset-rev": "3.0.0",
81 | "broccoli-node-api": "1.7.0",
82 | "broccoli-plugin": "4.0.3",
83 | "capture-console": "1.0.1",
84 | "co": "4.6.0",
85 | "ember-cli": "^4.8.0",
86 | "ember-cli-app-version": "4.0.0",
87 | "ember-cli-babel": "7.23.0",
88 | "ember-cli-blueprint-test-helpers": "0.19.2",
89 | "ember-cli-dependency-checker": "3.2.0",
90 | "ember-cli-htmlbars": "5.3.1",
91 | "ember-cli-inject-live-reload": "2.0.2",
92 | "ember-cli-sri": "2.1.1",
93 | "ember-cli-uglify": "3.0.0",
94 | "ember-cli-update": "0.54.6",
95 | "ember-disable-prototype-extensions": "1.1.3",
96 | "ember-export-application-global": "2.0.1",
97 | "ember-load-initializers": "2.1.2",
98 | "ember-maybe-import-regenerator": "0.1.6",
99 | "ember-qunit": "4.6.0",
100 | "ember-resolver": "8.0.2",
101 | "ember-source": "~3.28.0",
102 | "ember-try": "1.4.0",
103 | "eslint": "8.7.0",
104 | "eslint-config-prettier": "8.3.0",
105 | "eslint-plugin-ember": "10.5.8",
106 | "eslint-plugin-node": "11.1.0",
107 | "eslint-plugin-prettier": "4.0.0",
108 | "esprima": "4.0.1",
109 | "fixturify": "^2.1.1",
110 | "got": "12.5.2",
111 | "handlebars": "4.7.7",
112 | "in-repo-a": "link:tests/dummy/lib/in-repo-a",
113 | "in-repo-b": "link:tests/dummy/lib/in-repo-b",
114 | "loader.js": "4.7.0",
115 | "mocha": "10.1.0",
116 | "prettier": "2.5.1",
117 | "prettier-eslint": "13.0.0",
118 | "qunit-dom": "1.6.0",
119 | "release-it": "^17.1.1",
120 | "rimraf": "3.0.2",
121 | "testdouble": "3.16.1",
122 | "ts-node": "^10.9.1",
123 | "typescript": "4.8"
124 | },
125 | "resolutions": {
126 | "hawk": "7",
127 | "ember-cli-typescript": "link:."
128 | },
129 | "engines": {
130 | "node": ">= 12.*"
131 | },
132 | "ember-addon": {
133 | "configPath": "tests/dummy/config",
134 | "before": [
135 | "broccoli-watcher"
136 | ]
137 | },
138 | "prettier": {
139 | "printWidth": 100,
140 | "semi": true,
141 | "singleQuote": true,
142 | "trailingComma": "es5",
143 | "tabWidth": 2,
144 | "proseWrap": "never"
145 | },
146 | "changelog": {
147 | "labels": {
148 | "BREAKING": "Changed 💥",
149 | "enhancement": "Added ⭐️",
150 | "bug": "Fixed 🔧",
151 | "docs": "Documentation 📖",
152 | "internal": "Under the hood 🚗"
153 | }
154 | },
155 | "release-it": {
156 | "git": {
157 | "tagName": "v${version}"
158 | },
159 | "github": {
160 | "release": true
161 | },
162 | "plugins": {
163 | "@release-it-plugins/lerna-changelog": {
164 | "infile": "CHANGELOG.md",
165 | "launchEditor": true
166 | }
167 | }
168 | },
169 | "volta": {
170 | "node": "18.18.1",
171 | "yarn": "1.22.19"
172 | }
173 | }
174 |
--------------------------------------------------------------------------------
/register-ts-node.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // eslint-disable-next-line node/no-deprecated-api
4 | if (!require.extensions['.ts']) {
5 | let options = { project: `${__dirname}/ts/tsconfig.json` };
6 |
7 | // If we're operating in the context of another project, which might happen
8 | // if someone has installed ember-cli-typescript from git, only perform
9 | // transpilation. In this case, we also overwrite the default ignore glob
10 | // (which ignores everything in `node_modules`) to instead ignore anything
11 | // that doesn't end with `.ts`.
12 | if (process.cwd() !== __dirname) {
13 | options.ignore = [/\.(?!ts$)\w+$/];
14 | options.transpileOnly = true;
15 | }
16 |
17 | // eslint-disable-next-line node/no-unpublished-require
18 | require('ts-node').register(options);
19 | }
20 |
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@typed-ember/renovate-config"
3 | }
4 |
--------------------------------------------------------------------------------
/rfcs/0000-template.md:
--------------------------------------------------------------------------------
1 | - Start Date: (fill me in with today's date, YYYY-MM-DD)
2 | - Relevant Team(s): (fill this in with the [team(s)](README.md#relevant-teams) to which this RFC applies)
3 | - RFC PR: (after opening the RFC PR, update this with a link to it and update the file name)
4 | - Tracking: (leave this empty)
5 |
6 | #
7 |
8 | ## Summary
9 |
10 | > One paragraph explanation of the feature.
11 |
12 | ## Motivation
13 |
14 | > Why are we doing this? What use cases does it support? What is the expected
15 | outcome?
16 |
17 | ## Detailed design
18 |
19 | > This is the bulk of the RFC.
20 |
21 | > Explain the design in enough detail for somebody
22 | familiar with the framework to understand, and for somebody familiar with the
23 | implementation to implement. This should get into specifics and corner-cases,
24 | and include examples of how the feature is used. Any new terminology should be
25 | defined here.
26 |
27 | ## How we teach this
28 |
29 | > What names and terminology work best for these concepts and why? How is this
30 | idea best presented? As a continuation of existing Ember patterns, or as a
31 | wholly new one?
32 |
33 | > Would the acceptance of this proposal mean the Ember guides must be
34 | re-organized or altered? Does it change how Ember is taught to new users
35 | at any level?
36 |
37 | > How should this feature be introduced and taught to existing Ember
38 | users?
39 |
40 | ## Drawbacks
41 |
42 | > Why should we *not* do this? Please consider the impact on teaching Ember,
43 | on the integration of this feature with other existing and planned features,
44 | on the impact of the API churn on existing apps, etc.
45 |
46 | > There are tradeoffs to choosing any path, please attempt to identify them here.
47 |
48 | ## Alternatives
49 |
50 | > What other designs have been considered? What is the impact of not doing this?
51 |
52 | > This section could also include prior art, that is, how other frameworks in the same domain have solved this problem.
53 |
54 | ## Unresolved questions
55 |
56 | > Optional, but suggested for first drafts. What parts of the design are still
57 | TBD?
58 |
--------------------------------------------------------------------------------
/rfcs/README.md:
--------------------------------------------------------------------------------
1 | # Typed Ember RFCs
2 |
3 | (This document is intentionally effectively identical with the README for
4 | [emberjs/rfcs](https://github.com/emberjs/rfcs/).)
5 |
6 | Many changes, including bug fixes and documentation improvements can be
7 | implemented and reviewed via the normal GitHub pull request workflow.
8 |
9 | Some changes though are "substantial", and we ask that these be put
10 | through a bit of a design process and produce a consensus among the Ember
11 | core teams.
12 |
13 | The "RFC" (request for comments) process is intended to provide a
14 | consistent and controlled path for new features to enter the framework.
15 |
16 | [Active RFC List](https://github.com/emberjs/rfcs/pulls)
17 |
18 | ## When you need to follow this process
19 |
20 | You need to follow this process if you intend to make "substantial"
21 | changes to Ember, Ember Data, Ember CLI, their documentation, or any other
22 | projects under the purview of the [Ember core teams](https://emberjs.com/team/).
23 | What constitutes a "substantial" change is evolving based on community norms,
24 | but may include the following:
25 |
26 | - A new feature that creates new API surface area, and would
27 | require a [feature flag] if introduced.
28 | - The removal of features that already shipped as part of the release
29 | channel.
30 | - The introduction of new idiomatic usage or conventions, even if they
31 | do not include code changes to Ember itself.
32 |
33 | Some changes do not require an RFC:
34 |
35 | - Rephrasing, reorganizing or refactoring
36 | - Addition or removal of warnings
37 | - Additions that strictly improve objective, numerical quality
38 | criteria (speedup, better browser support)
39 | - Additions only likely to be _noticed by_ other implementors-of-Ember,
40 | invisible to users-of-Ember.
41 |
42 | If you submit a pull request to implement a new feature without going
43 | through the RFC process, it may be closed with a polite request to
44 | submit an RFC first.
45 |
46 | ## Gathering feedback before submitting
47 |
48 | It's often helpful to get feedback on your concept before diving into the
49 | level of API design detail required for an RFC. **You may open an
50 | issue on this repo to start a high-level discussion**, with the goal of
51 | eventually formulating an RFC pull request with the specific implementation
52 | design. We also highly recommend sharing drafts of RFCs in `#dev-rfc` on
53 | the [Ember Discord](https://discord.gg/emberjs) for early feedback.
54 |
55 | ## The process
56 |
57 | In short, to get a major feature added to Ember, one must first get the
58 | RFC merged into the RFC repo as a markdown file. At that point the RFC
59 | is 'active' and may be implemented with the goal of eventual inclusion
60 | into Ember.
61 |
62 | * Fork the RFC repo http://github.com/emberjs/rfcs
63 | * Copy the appropriate template. For most RFCs, this is `0000-template.md`,
64 | for deprecation RFCs it is `deprecation-template.md`.
65 | Copy the template file to `text/0000-my-feature.md`, where
66 | 'my-feature' is descriptive. Don't assign an RFC number yet.
67 | * Fill in the RFC. Put care into the details: **RFCs that do not
68 | present convincing motivation, demonstrate understanding of the
69 | impact of the design, or are disingenuous about the drawbacks or
70 | alternatives tend to be poorly-received**.
71 | * Submit a pull request. As a pull request the RFC will receive design
72 | feedback from the larger community, and the author should be prepared
73 | to revise it in response.
74 | * Update the pull request to add the number of the PR to the filename and
75 | add a link to the PR in the header of the RFC.
76 | * Build consensus and integrate feedback. RFCs that have broad support
77 | are much more likely to make progress than those that don't receive any
78 | comments.
79 | * Eventually, the Typed Ember team will decide whether the RFC is a candidate
80 | for inclusion by ember-cli-typescript.
81 | * RFCs that are candidates for inclusion will enter a "final comment period"
82 | lasting 7 days. The beginning of this period will be signaled with a comment
83 | and tag on the RFC's pull request. Furthermore, the Typed Ember team will post
84 | in Discord in `#topic-typescript` and `#news-and-announcements`.
85 | * An RFC can be modified based upon feedback from the Typed Ember team and
86 | the community. Significant modifications may trigger a new final comment period.
87 | * An RFC may be rejected by the team after public discussion has settled and
88 | comments have been made summarizing the rationale for rejection. The RFC will
89 | enter a "final comment period to close" lasting 7 days. At the end of the
90 | "FCP to close" period, the PR will be closed.
91 | * An RFC may also be closed by the core teams if it is superseded by a merged
92 | RFC. In this case, a link to the new RFC should be added in a comment.
93 | * An RFC author may withdraw their own RFC by closing it themselves.
94 | * An RFC may be accepted at the close of its final comment period. A [core team]
95 | member will merge the RFC's associated pull request, at which point the RFC will
96 | become 'active'.
97 |
98 | ## The RFC life-cycle
99 |
100 | Once an RFC becomes active the relevant teams will plan the feature and create
101 | issues in the relevant repositories.
102 | Becoming 'active' is not a rubber stamp, and in particular still does not mean
103 | the feature will ultimately be merged; it does mean that the core team has agreed
104 | to it in principle and are amenable to merging it.
105 |
106 | Furthermore, the fact that a given RFC has been accepted and is
107 | 'active' implies nothing about what priority is assigned to its
108 | implementation, nor whether anybody is currently working on it.
109 |
110 | Modifications to active RFC's can be done in followup PR's. We strive
111 | to write each RFC in a manner that it will reflect the final design of
112 | the feature; but the nature of the process means that we cannot expect
113 | every merged RFC to actually reflect what the end result will be at
114 | the time of the next major release; therefore we try to keep each RFC
115 | document somewhat in sync with the feature as planned,
116 | tracking such changes via followup pull requests to the document.
117 |
118 | ## Implementing an RFC
119 |
120 | The author of an RFC is not obligated to implement it. Of course, the
121 | RFC author (like any other developer) is welcome to post an
122 | implementation for review after the RFC has been accepted.
123 |
124 | If you are interested in working on the implementation for an 'active'
125 | RFC, but cannot determine if someone else is already working on it,
126 | feel free to ask (e.g. by leaving a comment on the associated issue).
127 |
128 | ## For Core Team Members
129 |
130 | ### Reviewing RFCs
131 |
132 | Each core team is responsible for reviewing open RFCs. The team must ensure
133 | that if an RFC is relevant to their team's responsibilities the team is
134 | correctly specified in the 'Relevant Team(s)' section of the RFC front-matter.
135 | The team must also ensure that each RFC addresses any consequences, changes, or
136 | work required in the team's area of responsibility.
137 |
138 | As it is with the wider community, the RFC process is the time for
139 | teams and team members to push back on, encourage, refine, or otherwise comment
140 | on proposals.
141 |
142 | ### Referencing RFCs
143 |
144 | - When mentioning RFCs that have been merged, link to the merged version,
145 | not to the pull-request.
146 |
--------------------------------------------------------------------------------
/test-fixtures/skeleton-app/app/index.html:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/typed-ember/ember-cli-typescript/27637e095bac8e3fadea8a142977cd0212b30701/test-fixtures/skeleton-app/app/index.html
--------------------------------------------------------------------------------
/test-fixtures/skeleton-app/app/styles/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/typed-ember/ember-cli-typescript/27637e095bac8e3fadea8a142977cd0212b30701/test-fixtures/skeleton-app/app/styles/.gitkeep
--------------------------------------------------------------------------------
/test-fixtures/skeleton-app/config/environment.js:
--------------------------------------------------------------------------------
1 | /* eslint-env node */
2 | 'use strict';
3 |
4 | module.exports = function(environment) {
5 | return {
6 | environment,
7 | modulePrefix: 'skeleton-app',
8 | rootURL: '/'
9 | };
10 | };
11 |
--------------------------------------------------------------------------------
/test-fixtures/skeleton-app/ember-cli-build.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const EmberApp = require('ember-cli/lib/broccoli/ember-app');
4 |
5 | module.exports = function(defaults) {
6 | return new EmberApp(defaults, {}).toTree();
7 | };
8 |
--------------------------------------------------------------------------------
/test-fixtures/skeleton-app/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "skeleton-app",
3 | "devDependencies": {
4 | "ember-cli": "*",
5 | "ember-cli-htmlbars": "*",
6 | "ember-cli-babel": "*",
7 | "ember-source": "*",
8 | "ember-qunit": "*",
9 | "loader.js": "*",
10 | "typescript": "*"
11 | },
12 | "ember-addon": {
13 | "paths": [".."]
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/test-fixtures/skeleton-app/testem.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | test_page: 'tests/index.html?hidepassed',
5 | disable_watching: true,
6 | launch_in_ci: ['Chrome'],
7 | launch_in_dev: ['Chrome'],
8 | browser_start_timeout: 120,
9 | browser_args: {
10 | Chrome: {
11 | ci: [
12 | // --no-sandbox is needed when running Chrome inside a container
13 | process.env.CI ? '--no-sandbox' : null,
14 | '--headless',
15 | '--disable-dev-shm-usage',
16 | '--disable-software-rasterizer',
17 | '--mute-audio',
18 | '--remote-debugging-port=0',
19 | '--window-size=1440,900'
20 | ].filter(Boolean)
21 | }
22 | }
23 | };
24 |
--------------------------------------------------------------------------------
/test-fixtures/skeleton-app/tests/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Dummy Tests
7 |
8 |
9 |
10 | {{content-for "head"}}
11 | {{content-for "test-head"}}
12 |
13 |
14 |
15 |
16 |
17 | {{content-for "head-footer"}}
18 | {{content-for "test-head-footer"}}
19 |
20 |
21 | {{content-for "body"}}
22 | {{content-for "test-body"}}
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | {{content-for "body-footer"}}
31 | {{content-for "test-body-footer"}}
32 |
33 |
34 |
--------------------------------------------------------------------------------
/test-fixtures/skeleton-app/tests/test-helper.ts:
--------------------------------------------------------------------------------
1 | import { start } from 'ember-qunit';
2 |
3 | start();
4 |
--------------------------------------------------------------------------------
/test-fixtures/skeleton-app/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2021",
4 | "allowJs": false,
5 | "module": "Node16",
6 | "moduleResolution": "Node16",
7 | "noEmitOnError": true,
8 | "baseUrl": ".",
9 | "paths": {
10 | "skeleton-app/*": [
11 | "app/*"
12 | ]
13 | },
14 | // We *really* don't want this on, but our hand is forced somewhat at the
15 | // moment: the types for `console-ui` are correct for consuming `inquirer`
16 | // in the pre-Node16 world, but don't interoperate correctly when consumed
17 | // by a CJS-default package in the TS Node16+ world *and* consume an ESM
18 | // package. Our path forward there is to update `console-ui` to publish a
19 | // dual-mode package with types and runtime code to support it.
20 | //
21 | // NOTE TO READERS: this is *only* required because of a very specific bit
22 | // of weird wiring in our test harness; it will *not* affect normal apps.
23 | "skipLibCheck": true
24 | },
25 | "include": [
26 | "app"
27 | ]
28 | }
29 |
--------------------------------------------------------------------------------
/testem.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | test_page: 'tests/index.html?hidepassed',
3 | disable_watching: true,
4 | reporter: 'xunit',
5 | report_file: 'ember-tests.xml',
6 | xunit_exclude_stack: true, // we *probably* want this on to keep the xunit file clean
7 | xunit_intermediate_output: true,
8 | launch_in_ci: ['Chrome', 'Firefox'],
9 | launch_in_dev: ['Chrome'],
10 | browser_start_timeout: 60000,
11 | browser_args: {
12 | Firefox: { ci: ['-headless', '--window-size=1440,900'] },
13 | Chrome: {
14 | mode: 'ci',
15 | // prettier-disable -- args are useful to have in a sane order
16 | args: [
17 | '--disable-gpu',
18 | '--headless',
19 | '--remote-debugging-port=0',
20 | '--window-size=1440,900',
21 | ].concat(
22 | /*
23 | --no-sandbox is needed when running Chrome inside a container.
24 | See https://github.com/ember-cli/ember-cli-chai/pull/45/files.
25 | */
26 | (process.env.TRAVIS || process.env.CI) ? '--no-sandbox' : []
27 | ),
28 | },
29 | },
30 | };
31 |
--------------------------------------------------------------------------------
/tests/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "predef": [
3 | "document",
4 | "window",
5 | "location",
6 | "setTimeout",
7 | "$",
8 | "-Promise",
9 | "define",
10 | "console",
11 | "visit",
12 | "exists",
13 | "fillIn",
14 | "click",
15 | "keyEvent",
16 | "triggerEvent",
17 | "find",
18 | "findWithAssert",
19 | "wait",
20 | "DS",
21 | "andThen",
22 | "currentURL",
23 | "currentPath",
24 | "currentRouteName"
25 | ],
26 | "node": false,
27 | "browser": false,
28 | "boss": true,
29 | "curly": true,
30 | "debug": false,
31 | "devel": false,
32 | "eqeqeq": true,
33 | "evil": true,
34 | "forin": false,
35 | "immed": false,
36 | "laxbreak": false,
37 | "newcap": true,
38 | "noarg": true,
39 | "noempty": false,
40 | "nonew": false,
41 | "nomen": false,
42 | "onevar": false,
43 | "plusplus": false,
44 | "regexp": false,
45 | "undef": true,
46 | "sub": true,
47 | "strict": false,
48 | "white": false,
49 | "eqnull": true,
50 | "esversion": 6,
51 | "unused": true
52 | }
53 |
--------------------------------------------------------------------------------
/tests/dummy/app/app.js:
--------------------------------------------------------------------------------
1 | import Application from '@ember/application';
2 | import Resolver from './resolver';
3 | import loadInitializers from 'ember-load-initializers';
4 | import config from './config/environment';
5 |
6 | class App extends Application {
7 | modulePrefix = config.modulePrefix;
8 | podModulePrefix = config.podModulePrefix;
9 | Resolver = Resolver;
10 | }
11 |
12 | loadInitializers(App, config.modulePrefix);
13 |
14 | export default App;
15 |
--------------------------------------------------------------------------------
/tests/dummy/app/components/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/typed-ember/ember-cli-typescript/27637e095bac8e3fadea8a142977cd0212b30701/tests/dummy/app/components/.gitkeep
--------------------------------------------------------------------------------
/tests/dummy/app/components/js-importing-ts.hbs:
--------------------------------------------------------------------------------
1 |