These directions are for converting an existing Ember app to TypeScript. If you are starting a new app, you can use the directions in Getting Started.
Enable TypeScript Features
A Bit of a Hack
Since ember-cli
currently has no flag to convert your project to TypeScript, we're going to use a bit of a hack and temporarily install the legacy ember-cli-typescript
addon to complete most of the migration:
ember install ember-cli-typescript@latest
The ember-cli-typescript
addon will install most of the necessary packages and create or update most of the necessary files as described in Getting Started.
You can then immediately remove the ember-cli-typescript
dependency and follow the rest of this guide.
Manually Enable TypeScript Transpilation
To enable TypeScript transpilation in your app, simply add the corresponding configuration for Babel to your ember-cli-build.js
file.
const app = new EmberApp(defaults, {
'ember-cli-babel': { enableTypeScriptTransform: true },
});
Manually Add lint:types
Script
To easily check types with the command line, add the lint:types
script as shown here.
The default lint
script generated by Ember CLI will include the lint:types
script automatically.
Manually Force Blueprint Generators to Use TypeScript
With the following configuration, project files will be generated with .ts
extensions instead of .js
:
{
"isTypeScriptProject": false,
"isTypeScriptProject": true,
}
Manually Set Up @typescript-eslint
npm add --save-dev @typescript-eslint/eslint-plugin @typescript-eslint/parser
Then, update your .eslintrc.js
to include the current output from the Ember CLI blueprints. You might consider using ESLint overrides configuration to separately configure your JavaScript and TypeScript files during the migration.
Migrate Existing Code to TypeScript
Once you have set up TypeScript following the guides above, you can begin to migrate your files incrementally by changing their extensions from .js
to .ts
. Fortunately, TypeScript allows for gradual typing. This means that you can use TypeScript and JavaScript files interchangeably, so you can convert your app piecemeal.
Some specific tips for success on the technical front:
Strictness
Use the strictest strictness settings that our typings allow. While it may be tempting to start with the loosest strictness settings and then to tighten them down as you go, this will actually mean that "getting your app type-checking" will become a repeated process—getting it type-checking with every new strictness setting you enable—rather than something you do just once.
Gradual Typing Hacks
Many of your files might reference types in other files that haven't been converted yet. There are several strategies you can employ to avoid a chain-reaction resulting in having to convert your entire app at once:
The unknown
type—You can sometimes get pretty far just by annotating types as unknown
. If unknown
is too wide of a type, however, you'll need a more robust solution.
TypeScript declaration files (.d.ts
)—These files are a straightforward way to document TypeScript types for JavaScript files without converting them. One downside of declaration files, however, is that they can easily get out-of-sync with the corresponding JavaScript file, so we only recommend this option as a temporary step.
JSDoc and allowJs
—Another way to document TypeScript types for JavaScript files without converting them is to add JSDoc "type hints" to the files and enable the allowJs
compiler option in your tsconfig.json
. While the JSDoc type syntax can be a bit cumbersome, it is much more likely to stay in sync. You can even type-check your JavaScript files using the @ts-check
directive.
The any
type—Opt out of type checking altogether for a value by annotating it as any
.
The @ts-expect-error
directive—A better strategy than any
, however, is to mark offending parts of your code with a @ts-expect-error
directive. This comment will ignore a type-checking error and allow the TypeScript compiler to assume that the value is of the type any
. If the code stops triggering the error, TypeScript will let you know.
Outer Leaves First
A good approach to gradual typing is to start at your outer "leaf" modules (the ones that don't import anything else from your app, only from Ember or third-party libraries) and then work your way "inward" (toward the modules with many internal imports). Often the highest-value modules are your EmberData models and any core services that are used everywhere else in the app–and those are also the ones that tend to have the most cascading effects (having to update tons of other places in your app) when you type them later in the process. By starting with the outer leaves, you won't have to use as many of our gradual typing hacks.
Prefer Octane Idioms
In general, we recommend migrating to Octane idioms before, or in conjunction with, your migration to TypeScript. See "Working With Ember Classic" for more details.
ember-cli-typescript
If you're migrating from ember-cli-typescript
, particularly an older version, to Ember's out-of-the-box TypeScript support, you may also need to update your tsconfig.json
. Current versions of ember-cli-typescript
generate the correct config at installation. You do not need ember-cli-typescript
installed for new apps or addons.