Reputation: 7598
I am trying to configure my Typescript project to have the following directory structure:
src/
├── client/
│ └── tsconfig.json
│
├── server/
│ └── tsconfig.json
│
├── shared/
│ ├── utils/
│ │ └── type-utils.d.ts
│ │
│ └── tsconfig.json
│
└── tsconfig-base.json
client/tsconfig.json
, server/tsconfig.json
, and shared/tsconfig.json
each have "extends": "../tsconfig-base.json"
client/tsconfig.json
and server/tsconfig.json
both have "references": [{ "path": "../shared" }]
shared/utils/type-utils.d.ts
is not a module, but instead contains ambient type declarations like Id
and CouldBeNull
, as well as a declaration merge for Array
(defining the flatMap
method).
The idea being that tsconfig-base.json
could include a lot of common settings, the individual tsconfig.json
s could define settings relevant for that sub-project (e.g. React for client/tsconfig.json
, Node for server/tsconfig.json
), and shared/
would be shared between client/
and server/
.
The problem is that the ambient declarations and declaration mergin shared/utils/type-utils.ts
is not seen by client/
or server/
, but only by shared/
. This causes errors, for example tsc -b client --verbose
produces
$ tsc -b client --verbose
15:31:41 - Projects in this build:
* shared/tsconfig.json
* client/tsconfig.json
15:31:41 - Project 'shared/tsconfig.json' is up to date because newest input 'shared/formatting/numbers.ts' is older than oldest output 'shared/formatting/numbers.js.map'
15:31:41 - Project 'client/tsconfig.json' is out of date because output file 'client/client/index.js' does not exist
15:31:41 - Building project 'C:/Users/KRyan/Web/5heet/client/tsconfig.json'...
client/index.tsx(32,29): error TS7006: Parameter 'skill' implicitly has an 'any' type.
client/index.tsx(80,37): error TS2339: Property 'flatMap' does not exist on type 'Ability[]'.
client/index.tsx(80,45): error TS7006: Parameter 'ability' implicitly has an 'any' type.
client/index.tsx(88,16): error TS7006: Parameter 'one' implicitly has an 'any' type.
client/index.tsx(88,21): error TS7006: Parameter 'another' implicitly has an 'any' type.
client/index.tsx(151,32): error TS7031: Binding element 'id' implicitly has an 'any' type.
client/parts/editing.tsx(31,60): error TS2339: Property 'oneOperand' does not exist on type 'never'.
client/parts/editing.tsx(31,81): error TS2339: Property 'operator' does not exist on type 'never'.
client/parts/editing.tsx(31,108): error TS2339: Property 'anotherOperand' does not exist on type 'never'.
client/parts/portal.tsx(55,61): error TS2304: Cannot find name 'Omit'.
shared/ability.d.ts(2,33): error TS2304: Cannot find name 'Id'.
shared/ability.d.ts(8,36): error TS2304: Cannot find name 'Id'.
shared/ability.d.ts(12,37): error TS2304: Cannot find name 'Id'.
shared/character.d.ts(3,35): error TS2304: Cannot find name 'Id'.
shared/expression.d.ts(7,35): error TS2304: Cannot find name 'Id'.
shared/expression.d.ts(30,38): error TS2304: Cannot find name 'ElementOf'.
shared/expression.d.ts(33,118): error TS2304: Cannot find name 'Id'.
shared/formatting/numbers.d.ts(1,41): error TS2304: Cannot find name 'Id'.
shared/skill.d.ts(3,31): error TS2304: Cannot find name 'Id'.
shared/utils/basic.d.ts(5,46): error TS2304: Cannot find name 'CouldBeNull'.
The implicit any
types are caused directly by the inability to find the various names listed in the second half of the output.
And, for completeness’s sake, the full files:
shared/utils/type-utils.d.ts
:
type PropsOf<T> = T[keyof T];
type ElementOf<T> = T extends (infer E)[] ? E : T;
type Subtract<T, U> = Pick<T, Exclude<keyof T, keyof U>>;
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
type CouldBeNull<T> = null extends T ? unknown : never;
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends Array<infer U>
? Array<DeepPartial<U>>
: T[P] extends ReadonlyArray<infer U>
? ReadonlyArray<DeepPartial<U>>
: DeepPartial<T[P]>
};
declare class Tagged<Tag> {
private 'is tagged as': [Tag];
private dummy(): Tag;
}
type Id<Tag> = string & Tagged<Tag>;
interface Array<T> {
/**
* Calls a defined callback function on each element of an array. Then, flattens the result into
* a new array.
* This is identical to a map followed by a flatten of depth 1.
*
* @param callback A function that accepts up to three arguments. The flatMap method calls the
* callback function one time for each element in the array.
* @param thisArg An object to which the this keyword can refer in the callback function. If
* thisArg is omitted, undefined is used as the this value.
*/
flatMap<U, This = undefined>(
callback: (this: This, value: T, index: number, array: T[]) => U | U[],
thisArg?: This,
): U[];
}
tsconfig-base.json
:
{
"compileOnSave": true,
"compilerOptions": {
"target": "es2018",
"baseUrl": ".",
"module": "amd",
"moduleResolution": "classic",
"paths": {
"csstype": [
"node_modules/csstype/index"
],
},
"composite": true,
"declaration": true,
"declarationMap": true,
"noEmitOnError": true,
"strictNullChecks": true,
"allowJs": false,
"allowUnusedLabels": false,
"noImplicitAny": true,
"noImplicitThis": true,
"noImplicitReturns": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noStrictGenericChecks": true,
"noUnusedLocals": true,
"noErrorTruncation": true,
"allowUnreachableCode": false,
"forceConsistentCasingInFileNames": false,
"preserveConstEnums": true,
"sourceMap": true
}
}
client/tsconfig.json
:
{
"extends": "../tsconfig-base.json",
"include": [
"**/*.ts"
],
"outDir": "../../built/client",
"compilerOptions": {
"rootDir": "..",
"jsx": "react",
"types": [
"react",
"react-dom"
]
},
"references": [
{
"path": "../shared"
}
],
"watch": true
}
server/tsconfig.json
:
{
"extends": "../tsconfig-base.json",
"include": [
"**/*.ts"
],
"outDir": "../../built/server",
"compilerOptions": {
"rootDir": "..",
"module": "commonjs",
"types": [
"node",
"mime-types"
]
},
"references": [
{
"path": "../shared"
}
]
}
shared/tsconfig.json
:
{
"extends": "../../tsconfig-base.json",
"include": [
"**/*.ts"
],
"outDir": "built/shared",
"compilerOptions": {
"module": "amd",
"types": []
}
}
Upvotes: 8
Views: 5093
Reputation: 7598
Figured out a solution, though I’m not wild about it:
client/tsconfig.json
:
{
"extends": "../tsconfig-base.json",
"include": [
"**/*.ts",
"**/*.tsx",
"../shared/utils/type-utils.d.ts"
],
"outDir": "../built/client",
"compilerOptions": {
"baseUrl": "..",
"rootDir": "..",
"jsx": "react",
"types": [
"react",
"react-dom"
]
},
"references": [
{
"path": "../shared"
}
],
"watch": true
}
Note the ../shared/utils/type-utils.d.ts
in the include
section. This solves the problem.
I’m not wild about this solution because it means explicitly listing something in shared/
rather than it just being included as a result of the project reference. I am not happy about the thought that I would have to manually update my tsconfig.json
s for each new .d.ts
file I want to add shared/
. But this is an answer, in that the project builds, and I’m not overly likely to add more of my own .d.ts
files to the project, so my concerns are possibly philosophical/theoretical for this project.
Upvotes: 6