14

I have a src directory with my source code and unit tests, and a test directory containing a separate speed test.

When I build my project with tsc, I get a directory structure like this:

dist/
  src/
    index.js
    ...
  test/
    speed-test.js

I'd prefer, however, to get "flat" output to my dist directory, like this:

dist/
  index.js
  ...
  speed-test.js
  ...

If I delete speed-test.ts from the test directory, tsc doesn't add a src directory to dist. The extra directory structure only gets added when there's a need (or at least, when tsc decides there's a need) to distiguish the sources of the compiled code.

I'm sure that's very useful at times to avoid file name conflicts, but that's not important for me in this case, and I'd prefer not to get this extra "help".

Here's my tsconfig.json:

{
  "compilerOptions": {
    "target": "ES6",
    "module": "commonjs",
    "outDir": "./dist",
    "noImplicitAny": true,
    "noImplicitThis": true,
    "alwaysStrict": true,
    "noImplicitReturns": true,
    "moduleResolution": "node",
    "esModuleInterop": true,
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true,
    "resolveJsonModule": true,
    "allowSyntheticDefaultImports": true
  }
}

I tried adding a "rootDirs" options of ["src", "test"], but that didn't help.

Is there anyway for me to get the output structure I'm looking for?

kshetline
  • 12,547
  • 4
  • 37
  • 73

1 Answers1

11

Why nested output folders inside outDir?

If I delete speed-test.ts from the test directory, tsc doesn't add a src directory to dist.

TypeScript determines one parent folder containing all your source files. This folder is set as rootDir in tsconfig.json or calculated automatically by the compiler.

For example, if you include ./test/speed-test.ts in the compilation (./ = project root), which imports other source files from ./src, its parent folder will be ./ and the compiled version will have the path <outDir>/test/speed-test.js.

How to make the output folder flat?

We need two things: 1.) TS Project References 2.) rootDirs.

Project References will create independent sub-projects, each with its own rootDir configuration. This has several advantages like increased compile-time performance. And we can structure the output folder (outDir) more flexibly.

rootDirs will tell the compiler that src and test are merged to a single folder in the course of the compilation. Now we can write import paths, that are validated from the compiler as if both would form a single folder. Consider ./test/speed-test.ts needs ./src/main.ts - you then can write following import with existing folder structure:

// ./test/inside speed-test.ts
import {something} from "./main" // main.ts is still inside "./src"

Note: TypeScript doesn't re-write import paths, rootDirs only affects the input type validation.

tldr: Code

.
├── package.json
├── src
│   ├── main.test.ts
│   ├── main.ts
│   └── tsconfig.json
├── test
│   ├── speedtest.ts
│   └── tsconfig.json
├── tsconfig-base.json
└── tsconfig.json

./src/main.ts:

export const foo = "foo"

./src/tsconfig.json:

{
  "extends": "../tsconfig-base.json",
  "compilerOptions": {
    "outDir": "../dist",
    "composite": true, // needed for references sub-projects
    "rootDir": ".",
    "tsBuildInfoFile": "../dist/srcbuildinfo"
  }
}

./test/speedtest.ts:

import { foo } from "./main"; // important(!): pretend, `src/main` is same folder
console.log(foo);
// ... do performance tests ...

./test/tsconfig.json:

{
  "extends": "../tsconfig-base.json",
  "compilerOptions": {
    "outDir": "../dist",
    "rootDir": ".",
    "composite": true, 
    "tsBuildInfoFile": "../dist/testbuildinfo",
    "rootDirs": ["../src", "../test"] // important (!)
  },
  "references": [
    {
      "path": "../src" // test project depends on src
    }
  ]
}

./tsconfig.json:

{
  "files": [], // this root tsconfig just exists to compose sub-projects
  "references": [
    {
      "path": "./src"
    },
    {
      "path": "./test"
    }
  ]
}

tsconfig-base.json contains all own config values. Compiling with tsc -b -v will place all files under ./dist, same import paths as specified in the source:

dist
├── main.d.ts
├── main.js
├── main.test.d.ts
├── main.test.js
├── speedtest.d.ts
├── speedtest.js
├── ...

Lastly, if you need more infos on Project References - here is another code example.

ford04
  • 66,267
  • 20
  • 199
  • 171
  • I knew that I could explicitly limit compilation to the `src` directory, but I really want the stuff in the `test` directory to get compiled *at the same time* too, and without getting the extra directory structure. If limiting compilation to the `src` directory, then compiling the extra stuff in the `test` directory as a separate step (possibly with its own config file) is the best I can do -- then I'll just have to settle for that as the best I can do. – kshetline Sep 30 '19 at 20:22
  • If you are looking for the IDE compilation on the fly with intellisense and refactoring, then omitting testes and providing additional `tsconfig` for the tests folder is a working solution, please see this answer to the similar question: https://stackoverflow.com/a/61153019/3082178. – AKd Nov 19 '20 at 11:11
  • @kshetline gave this answer another try – ford04 Nov 19 '20 at 19:03
  • 1
    "TypeScript determines one parent folder containing all your source files. This folder is set as rootDir in tsconfig.json or calculated automatically by the compiler." was very helpful in understanding why tsc would flatten my /src/functions/index.ts to /dist/index.js This was because rootDir was not specified and tsc would image /src/functions is my rootDir. Thank you! – Dziamid Mar 16 '21 at 17:02