Skip to content

Commit ad1e4ee

Browse files
authored
Merge pull request #1488 from typed-ember/update-tsconfig
Update tsconfig and corresponding docs
2 parents 48bf736 + a9e3fff commit ad1e4ee

File tree

5 files changed

+127
-42
lines changed

5 files changed

+127
-42
lines changed

blueprint-files/ember-cli-typescript/tsconfig.json

+59-14
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,70 @@
11
{
22
"compilerOptions": {
3-
"target": "es2020",
4-
"allowJs": true,
3+
"target": "ES2021",
4+
"module": "ES2020",
55
"moduleResolution": "node",
6-
"allowSyntheticDefaultImports": true,
7-
"noImplicitAny": true,
8-
"noImplicitThis": true,
9-
"alwaysStrict": true,
10-
"strictNullChecks": true,
11-
"strictPropertyInitialization": true,
12-
"noFallthroughCasesInSwitch": true,
13-
"noUnusedLocals": true,
14-
"noUnusedParameters": true,
15-
"noImplicitReturns": true,
6+
7+
// Trying to check Ember apps and addons with `allowJs: true` is a recipe
8+
// for many unresolveable type errors, because with *considerable* extra
9+
// configuration it ends up including many files which are *not* valid and
10+
// cannot be: they *appear* to be resolve-able to TS, but are in fact not in
11+
// valid Node-resolveable locations and may not have TS-ready types. This
12+
// will likely improve over time
13+
"allowJs": false,
14+
15+
// --- TS for SemVer Types compatibility
16+
// Strictness settings -- you should *not* change these: Ember code is not
17+
// guaranteed to type check with these set to looser values.
18+
"strict": true,
19+
"noUncheckedIndexedAccess": true,
20+
21+
// Interop: these are viral and will require anyone downstream of your
22+
// package to *also* set them to true. If you *must* enable them to consume
23+
// an upstream package, you should document that for downstream consumers to
24+
// be aware of.
25+
//
26+
// These *are* safe for apps to enable, since they do not *have* downstream
27+
// consumers; but leaving them off is still preferred when possible, since
28+
// it makes it easier to switch between apps and addons and have the same
29+
// rules for what can be imported and how.
30+
"allowSyntheticDefaultImports": false,
31+
"esModuleInterop": false,
32+
33+
// --- Lint-style rules
34+
35+
// TypeScript also supplies some lint-style checks; nearly all of them are
36+
// better handled by ESLint with the `@typescript-eslint`. This one is more
37+
// like a safety check, though, so we leave it on.
38+
"noPropertyAccessFromIndexSignature": true,
39+
40+
// --- Compilation/integration settings
41+
// Setting `noEmitOnError` here allows ember-cli-typescript to catch errors
42+
// and inject them into Ember CLI's build error reporting, which provides
43+
// nice feedback for when
1644
"noEmitOnError": true,
45+
46+
// We use Babel for emitting runtime code, because it's very important that
47+
// we always and only use the same transpiler for non-stable features, in
48+
// particular decorators. If you were to change this to `true`, it could
49+
// lead to accidentally generating code with `tsc` instead of Babel, and
50+
// could thereby result in broken code at runtime.
1751
"noEmit": true,
52+
53+
// Ember makes heavy use of decorators; TS does not support them at all
54+
// without this flag.
55+
"experimentalDecorators": true,
56+
57+
// Support generation of source maps. Note: you must *also* enable source
58+
// maps in your `ember-cli-babel` config and/or `babel.config.js`.
59+
"declaration": true,
60+
"declarationMap": true,
1861
"inlineSourceMap": true,
1962
"inlineSources": true,
63+
64+
// The combination of `baseUrl` with `paths` allows Ember's classic package
65+
// layout, which is not resolveable with the Node resolution algorithm, to
66+
// work with TypeScript.
2067
"baseUrl": ".",
21-
"module": "es6",
22-
"experimentalDecorators": true,
2368
"paths": <%= pathsFor(dasherizedPackageName) %>
2469
},
2570
"include": <%= includes %>

docs/ts/using-ts-effectively.md

+17-13
Original file line numberDiff line numberDiff line change
@@ -8,24 +8,28 @@ Some specific tips for success on the technical front:
88

99
First, use the _strictest_ strictness settings that our typings allow (currently all strictness settings except `strictFunctionTypes`). 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.
1010

11-
The full recommended _strictness_ settings in your `"compilerOptions"` hash:
11+
The full recommended _strictness_ settings in your `"compilerOptions"` hash (which are also the settings generated by the ember-cli-typescript blueprint):
1212

13-
```json
13+
```json5
1414
{
15-
"noImplicitAny": true,
16-
"noImplicitThis": true,
17-
"alwaysStrict": true,
18-
"strictNullChecks": true,
19-
"strictPropertyInitialization": true,
20-
"noFallthroughCasesInSwitch": true,
21-
"noUnusedLocals": true,
22-
"noUnusedParameters": true,
23-
"noImplicitReturns": true,
24-
"noUncheckedIndexedAccess": true,
15+
"compilerOptions": {
16+
// Strictness settings -- you should *not* change these: Ember code is not
17+
// guaranteed to type check with these set to looser values.
18+
"strict": true,
19+
"noUncheckedIndexedAccess": true,
20+
21+
// You should feel free to change these, especially if you are already
22+
// covering them via linting (e.g. with @typescript-eslint).
23+
"noFallthroughCasesInSwitch": true,
24+
"noUnusedLocals": true,
25+
"noUnusedParameters": true,
26+
"noImplicitReturns": true,
27+
"noPropertyAccessFromIndexSignature": true,
28+
}
2529
}
2630
```
2731

28-
A good approach is to start at your "leaf" files (the ones that don't import anything else from your app, only Ember types) and then work your way back inward toward the most core types that are used everywhere. Often the highest-value modules are your Ember Data 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.
32+
A good approach is to start at your "leaf" modules (the ones that don't import anything else from your app, only Ember or third-party types) and then work your way back inward toward the most core modules that are used everywhere. Often the highest-value modules are your Ember Data 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.
2933

3034
Finally, leave `"noEmitOnError": true` (the default) in the `"compilerOptions"` hash in your `tsconfig.json`. This will fail your build if you have type errors, which gives you the fastest feedback as you add types.
3135

ts/tests/blueprints/ember-cli-typescript-test.ts

+6-5
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import path from 'path';
55
import helpers from 'ember-cli-blueprint-test-helpers/helpers';
66
import chaiHelpers from 'ember-cli-blueprint-test-helpers/chai';
77
import Blueprint from 'ember-cli/lib/models/blueprint';
8+
import ts from 'typescript';
89

910
import { setupPublishedVersionStashing } from '../helpers/stash-published-version';
1011
import ects from '../../blueprints/ember-cli-typescript/index';
@@ -71,7 +72,7 @@ describe('Acceptance: ember-cli-typescript generator', function () {
7172
const tsconfig = file('tsconfig.json');
7273
expect(tsconfig).to.exist;
7374

74-
const tsconfigJson = JSON.parse(tsconfig.content);
75+
const tsconfigJson = ts.parseConfigFileTextToJson('tsconfig.json', tsconfig.content).config;
7576
expect(tsconfigJson.compilerOptions.paths).to.deep.equal({
7677
'my-app/tests/*': ['tests/*'],
7778
'my-app/*': ['app/*'],
@@ -121,7 +122,7 @@ describe('Acceptance: ember-cli-typescript generator', function () {
121122
const tsconfig = file('tsconfig.json');
122123
expect(tsconfig).to.exist;
123124

124-
const tsconfigJson = JSON.parse(tsconfig.content);
125+
const tsconfigJson = ts.parseConfigFileTextToJson('tsconfig.json', tsconfig.content).config;
125126
expect(tsconfigJson.compilerOptions.paths).to.deep.equal({
126127
'dummy/tests/*': ['tests/*'],
127128
'dummy/*': ['tests/dummy/app/*', 'app/*'],
@@ -197,7 +198,7 @@ describe('Acceptance: ember-cli-typescript generator', function () {
197198
const tsconfig = file('tsconfig.json');
198199
expect(tsconfig).to.exist;
199200

200-
const json = JSON.parse(tsconfig.content);
201+
const json = ts.parseConfigFileTextToJson('tsconfig.json', tsconfig.content).config;
201202
expect(json.compilerOptions.paths).to.deep.equal({
202203
'my-app/tests/*': ['tests/*'],
203204
'my-app/*': ['app/*', 'lib/my-addon-1/app/*', 'lib/my-addon-2/app/*'],
@@ -243,7 +244,7 @@ describe('Acceptance: ember-cli-typescript generator', function () {
243244
const tsconfig = file('tsconfig.json');
244245
expect(tsconfig).to.exist;
245246

246-
const json = JSON.parse(tsconfig.content);
247+
const json = ts.parseConfigFileTextToJson('tsconfig.json', tsconfig.content).config;
247248
expect(json.compilerOptions.paths).to.deep.equal({
248249
'my-app/tests/*': ['tests/*'],
249250
'my-app/mirage/*': ['mirage/*'],
@@ -269,7 +270,7 @@ describe('Acceptance: ember-cli-typescript generator', function () {
269270
const tsconfig = file('tsconfig.json');
270271
expect(tsconfig).to.exist;
271272

272-
const json = JSON.parse(tsconfig.content);
273+
const json = ts.parseConfigFileTextToJson('tsconfig.json', tsconfig.content).config;
273274
expect(json.compilerOptions.paths).to.deep.equal({
274275
'dummy/tests/*': ['tests/*'],
275276
'dummy/mirage/*': ['tests/dummy/mirage/*'],

ts/tests/commands/precompile-test.ts

+21-8
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import * as fs from 'fs-extra';
22
import * as path from 'path';
3+
import { EOL } from 'os';
34
import { hook } from 'capture-console';
45
import ember from 'ember-cli-blueprint-test-helpers/lib/helpers/ember';
56
import blueprintHelpers from 'ember-cli-blueprint-test-helpers/helpers';
7+
import ts from 'typescript';
8+
69
const setupTestHooks = blueprintHelpers.setupTestHooks;
710
const emberNew = blueprintHelpers.emberNew;
811

@@ -26,7 +29,9 @@ describe('Acceptance: ts:precompile command', function () {
2629

2730
let declaration = file('test-file.d.ts');
2831
expect(declaration).to.exist;
29-
expect(declaration.content.trim()).to.equal(`export declare const testString: string;`);
32+
expect(declaration.content.trim()).to.equal(
33+
`export declare const testString: string;${EOL}//# sourceMappingURL=test-file.d.ts.map`
34+
);
3035
});
3136

3237
it('generates nothing from the app tree', async () => {
@@ -63,7 +68,7 @@ describe('Acceptance: ts:precompile command', function () {
6368
fs.writeFileSync('src/test-file.ts', `export const testString: string = 'hello';`);
6469

6570
let pkg = fs.readJsonSync('package.json');
66-
let tsconfig = fs.readJSONSync('tsconfig.json');
71+
let tsconfig = ts.readConfigFile('tsconfig.json', ts.sys.readFile).config;
6772
tsconfig.include.push('src');
6873
tsconfig.compilerOptions.paths[`${pkg.name}/src/*`] = ['src/*'];
6974
fs.writeJSONSync('tsconfig.json', tsconfig);
@@ -72,7 +77,9 @@ describe('Acceptance: ts:precompile command', function () {
7277

7378
let declaration = file('src/test-file.d.ts');
7479
expect(declaration).to.exist;
75-
expect(declaration.content.trim()).to.equal(`export declare const testString: string;`);
80+
expect(declaration.content.trim()).to.equal(
81+
`export declare const testString: string;${EOL}//# sourceMappingURL=test-file.d.ts.map`
82+
);
7683
});
7784
});
7885

@@ -85,7 +92,7 @@ describe('Acceptance: ts:precompile command', function () {
8592
);
8693

8794
let pkg = fs.readJsonSync('package.json');
88-
let tsconfig = fs.readJSONSync('tsconfig.json');
95+
let tsconfig = ts.readConfigFile('tsconfig.json', ts.sys.readFile).config;
8996
tsconfig.include.push('src');
9097
tsconfig.compilerOptions.paths[`${pkg.name}/*`] = ['addon-test-support/*'];
9198
fs.writeJSONSync('tsconfig.json', tsconfig);
@@ -94,7 +101,9 @@ describe('Acceptance: ts:precompile command', function () {
94101

95102
let declaration = file('test-file.d.ts');
96103
expect(declaration).to.exist;
97-
expect(declaration.content.trim()).to.equal(`export declare const testString: string;`);
104+
expect(declaration.content.trim()).to.equal(
105+
`export declare const testString: string;${EOL}//# sourceMappingURL=test-file.d.ts.map`
106+
);
98107
});
99108
});
100109

@@ -120,7 +129,9 @@ describe('Acceptance: ts:precompile command', function () {
120129
return ember(['ts:precompile']).then(() => {
121130
const declaration = file('test-support/test-file.d.ts');
122131
expect(declaration).to.exist;
123-
expect(declaration.content.trim()).to.equal(`export declare const testString: string;`);
132+
expect(declaration.content.trim()).to.equal(
133+
`export declare const testString: string;${EOL}//# sourceMappingURL=test-file.d.ts.map`
134+
);
124135
});
125136
});
126137

@@ -154,12 +165,14 @@ describe('Acceptance: ts:precompile command', function () {
154165
return ember(['ts:precompile']).then(() => {
155166
const componentDecl = file('components/my-component.d.ts');
156167
expect(componentDecl).to.exist;
157-
expect(componentDecl.content.trim()).to.equal(`export declare const testString: string;`);
168+
expect(componentDecl.content.trim()).to.equal(
169+
`export declare const testString: string;${EOL}//# sourceMappingURL=my-component.d.ts.map`
170+
);
158171

159172
const testSupportDecl = file('test-support/test-file.d.ts');
160173
expect(testSupportDecl).to.exist;
161174
expect(testSupportDecl.content.trim()).to.equal(
162-
`export declare const anotherTestString: string;`
175+
`export declare const anotherTestString: string;${EOL}//# sourceMappingURL=test-file.d.ts.map`
163176
);
164177
});
165178
});

ts/tests/helpers/stash-published-version.ts

+24-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,19 @@ import fs from 'fs-extra';
22

33
const PACKAGE_PATH = 'node_modules/ember-cli-typescript';
44

5+
function isNodeError(e: unknown): e is NodeJS.ErrnoException {
6+
// Not full-proof but good enough for our purposes.
7+
return e instanceof Error && 'code' in e;
8+
}
9+
10+
function handleError(e: unknown) {
11+
// Ignore the case where we just don't have the files to move. (And hope this
12+
// works correctly?)
13+
if (!isNodeError(e) || e.code !== 'ENOENT') {
14+
throw e;
15+
}
16+
}
17+
518
/**
619
* We have assorted devDependencies that themselves depend on `ember-cli-typescript`.
720
* This means we have a published copy of the addon present in `node_modules` normally,
@@ -12,10 +25,19 @@ const PACKAGE_PATH = 'node_modules/ember-cli-typescript';
1225
*/
1326
export function setupPublishedVersionStashing(hooks: Mocha.Suite): void {
1427
hooks.beforeAll(async () => {
15-
await fs.move(PACKAGE_PATH, `${PACKAGE_PATH}.published`);
28+
fs.move(PACKAGE_PATH, `${PACKAGE_PATH}.published`).catch();
29+
try {
30+
await fs.move(PACKAGE_PATH, `${PACKAGE_PATH}.published`);
31+
} catch (e: unknown) {
32+
handleError(e);
33+
}
1634
});
1735

1836
hooks.afterAll(async () => {
19-
await fs.move(`${PACKAGE_PATH}.published`, PACKAGE_PATH);
37+
try {
38+
await fs.move(`${PACKAGE_PATH}.published`, PACKAGE_PATH);
39+
} catch (e) {
40+
handleError(e);
41+
}
2042
});
2143
}

0 commit comments

Comments
 (0)