KRyan
KRyan

Reputation: 7598

Project references in tsconfig and ambient declarations

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

The idea being that tsconfig-base.json could include a lot of common settings, the individual tsconfig.jsons 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

Answers (1)

KRyan
KRyan

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.jsons 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

Related Questions