'tsconfig problems with test and compiling

This is my tsconfig.json

{
  "compilerOptions": {
    "noImplicitAny": true,
    "declaration": false,
    "strict": true,
    "strictNullChecks": false,
    "target": "ES2019",
    "module": "commonjs",
    "resolveJsonModule": true,
    "allowJs": true,
    "checkJs": false,
    "outDir": "dist",
    "esModuleInterop": true,
    "inlineSourceMap": true,
    "rootDir": "./",
    "baseUrl": "./",
    "typeRoots": [
      "./node_modules/@types"
    ],
    "paths": {
      "@/*": [
        "src/*"
      ]
    }
  },
  "include": [
    "src/**/*.ts",
    "src/**/*.js",
    "src/**/*.json",
    "tests/**/*.ts"
  ],
}

My architecture is as follows:

├── tests
├── src
├── eslint.yml
├── tsconfig.json
├── package.json

The problem with this structure is that in the dist folder it looks like this:

dist
├── tests
├── src

So I could change rootDir to src, problem that the tests folder cannot be outside of src.

What I want is to be able to use typescript in both the tests folder and the src folder. What would be the solution in that case. I've searched in several places and haven't found a solution to my problem.

Maybe some changes might affect eslint, because it imports tsconfig.json

My eslintrc.yml:

env:
  es2017: true
  node: true
  jest: true
extends:
  - airbnb-base
  - "eslint:recommended"
  - "plugin:import/typescript"
parser: "@typescript-eslint/parser"
parserOptions:
  ecmaVersion: 2020
  sourceType: module
  project: "./tsconfig.json"
plugins:
  - "@typescript-eslint"
settings:
  "import/resolver":
    typescript: {}
ignorePatterns:
  - "dist/*"

Any idea how to solve this problem?



Solution 1:[1]

Following @fast-reflexes answer and after a lot of research I arrived at the following answer:

  1. On package.json keep using only tsc command to compile:
{
    ...
    "scripts": {
        ...
        "build": "tsc",
        ...
    },
    ...
}
  1. My tsconfig.json change a little bit.
{
  "compilerOptions": {
    "noImplicitAny": true,
    "declaration": false,
    "strict": true,
    "strictNullChecks": false,
    "target": "ES2019",
    "module": "commonjs",
    "resolveJsonModule": true,
    "allowJs": true,
    "checkJs": false,
    "outDir": "dist",
    "esModuleInterop": true,
    "inlineSourceMap": true,
    "rootDir": "./src",        //  this line changed
    "baseUrl": "./src",        //  this line changed
    "typeRoots": [
      "./node_modules/@types"
    ],
    "paths": {
      "@/*": [
        "*"                    //  this line changed
      ]
    }
  },
  "include": [
    "src/**/*.ts",
    "src/**/*.js",
    "src/**/*.json"            //  remove line related to tests
  ],
}
  1. Created tsconfig.eslint.json.
{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "rootDir": "./",
    "baseUrl": "./",
    "paths": {
      "@/*": [
        "src/*"
      ]
    }
  },
  "include": [
    "src/**/*.ts",
    "src/**/*.js",
    "src/**/*.json",
    "tests/**/*.spec.ts"              //  line related to tests
  ]
}
  1. My eslintrc.yml changed:
env:
  es2017: true
  node: true
  jest: true
extends:
  - airbnb-base
  - "eslint:recommended"
  - "plugin:import/typescript"
parser: "@typescript-eslint/parser"
parserOptions:
  ecmaVersion: 2020
  sourceType: module
  project:
    - "./tsconfig.json"
    - "./tsconfig.eslint.json"      <-- added this file
plugins:
  - "@typescript-eslint"
settings:
  "import/resolver":
    typescript: {}
ignorePatterns:
  - ".eslintrc.yml"                 <-- added this file
  - "dist/*"
  1. My architecture is as follows:
??? dist
    ??? subfolders_without_tests               
??? tests
    ??? tsconfig.json               <-- added this file
??? src
??? eslintrc.yml
??? jest.config.js
??? tsconfig.json
??? tsconfig.eslint.json            <-- added this file
??? package.json
  1. My jest.config.js:
const { pathsToModuleNameMapper } = require('ts-jest/utils');
const { compilerOptions } = require('./tsconfig.json');

module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'node',
  testMatch: ['**/?(*.)+(spec|test).ts'],
  moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, { prefix: '<rootDir>/src/' }),
  modulePathIgnorePatterns: ['<rootDir>/dist'],
  coveragePathIgnorePatterns: [
    '<rootDir>/dist',
  ],
  coverageReporters: ['json', 'lcov', 'text', 'cobertura', 'text-summary'],
  moduleDirectories: ['node_modules', 'src'],
  collectCoverage: false,
  collectCoverageFrom: ['src/**/*.{ts,js,jsx}'],
};

Other related links:

Solution 2:[2]

I would advise you to follow the pattern where tests are put in the same folder as the source files they're testing but with an infix test. It will make your life easier in the long run.

Buuut... given your current needs:

1. Post-process output folder

The simplest solution is to replace your (presumed) build script, in package.json:

{
    ...
    "scripts": {
        ...
        "build": "tsc",
        ...
    },
    ...
}

with the following, updating the output folder as desired

{
    ...
    "scripts": {
        ...
        "build": "tsc && rm -rf ./dist/tests && mv ./dist/src/* ./dist && rmdir ./dist/src",
        ...
    },
    ...
}

2. Extend Typescript config

You can also add an extra Typescript config only used by Lint. Set rootDir to ./src and remove the include that involves the tests folder in tsconfig.json and then add the following tsconfig.eslint.json Typescript config:

{
    "extends": "./tsconfig.json",
    "compilerOptions": {
        "rootDir": "./"
    },
    "include": [
        "src/**/*.ts",
        "src/**/*.js",
        "src/**/*.json",
        "tests/**/*.ts"
    ],
}

Now use this config in your .eslintrc.yml only and it should work since it overrides both the rootDir and the include array.

I would have wished for some other solutions as well but even though you can add a temporary rootDir as a command line option to tsc, it will complain about includes that are outside of the root dir and there is no smooth way to replace the include array on the command line (except for literally lining up all the files to compile which is not an option).

In some project I was really motivated to keep a separate tests folder as you do here, but I remember that in the end, I just fell back to the tests-in-sources pattern instead because of the number of ridiculous configuration problems that I got with this other approach.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Cava
Solution 2 fast-reflexes