14

I have a fairly simple mono repo. It is available on GitLab here. This uses yarn workspaces, TypeScript, Jest, ts-jest and ESLint with eslint-plugin-import.

I am trying to properly build the project packages using TypeScript. Previously I was just publishing the TypeScript files alongside their JavaScript code in the same directory.

My attempt to build the project is available in a GitLab merge request here

Now the repository follows the following layout:

 |- example/
 |   |- index.ts
 |   |- package.json
 |   `- tsconfig.json
 |- packages/
 |   |- koas-core/
 |   |   |- src/
 |   |   |   `- index.ts
 |   |   |- package.json
 |   |   `- tsconfig.json
 |   |- koas-status-code/
 |   |   |- src/
 |   |   |   `- index.ts
 |   |   |- package.json
 |   |   `- tsconfig.json
 |   `- more similar workspaces…
 |- package.json
 |- tsconfig.json
 `- more configuration files…

Some packages depend on each other. I have managed to get Jest tests and eslint-plugin-import to work with this setup, but I’m having trouble building the project.

The root tsconfig.json looks like this:

{
  "compilerOptions": {
    "baseUrl": ".",
    "composite": true,
    "declaration": true,
    "module": "commonjs",
    "noEmit": true,
    "resolveJsonModule": true,
    "target": "esnext",
    "paths": {
      "koas-*": ["packages/koas-*/src"]
    }
  },
  "exclude": ["**/*.test.ts"]
}

The workspace tsconfig.json files look like this:

{
  "extends": "../../tsconfig.json",
  "compilerOptions": {
    "rootDir": "src",
    "outDir": "lib"
  }
}

Each workspace has a prepack script defined in package.json that looks like this:

{
  "scripts": {
    "prepack": "tsc --noEmit false"
  }
}

The main field refers to lib.

If I run yarn workspace koas-status-code prepack, I get the following error:

$ tsc --noEmit false
error TS6059: File 'koas/packages/koas-core/src/SchemaValidationError.ts' is not under 'rootDir' 'koas/packages/koas-status-code'. 'rootDir' is expected to contain all source files.

error TS6059: File 'koas/packages/koas-core/src/SchemaValidationError.ts' is not under 'rootDir' 'koas/packages/koas-status-code/src'. 'rootDir' is expected to contain all source files.

error TS6059: File 'koas/packages/koas-core/src/createDefaultValidator.ts' is not under 'rootDir' 'koas/packages/koas-status-code'. 'rootDir' is expected to contain all source files.

error TS6059: File 'koas/packages/koas-core/src/createDefaultValidator.ts' is not under 'rootDir' 'koas/packages/koas-status-code/src'. 'rootDir' is expected to contain all source files.

error TS6059: File 'koas/packages/koas-core/src/createMatcher.ts' is not under 'rootDir' 'koas/packages/koas-status-code'. 'rootDir' is expected to contain all source files.

error TS6059: File 'koas/packages/koas-core/src/createMatcher.ts' is not under 'rootDir' 'koas/packages/koas-status-code/src'. 'rootDir' is expected to contain all source files.

error TS6059: File 'koas/packages/koas-core/src/index.ts' is not under 'rootDir' 'koas/packages/koas-status-code'. 'rootDir' is expected to contain all source files.

error TS6059: File 'koas/packages/koas-core/src/index.ts' is not under 'rootDir' 'koas/packages/koas-status-code/src'. 'rootDir' is expected to contain all source files.

error TS6307: File 'koas/packages/koas-core/src/SchemaValidationError.ts' is not listed within the file list of project 'koas/packages/koas-status-code/tsconfig.json'. Projects must list all files or use an 'include' pattern.

error TS6307: File 'koas/packages/koas-core/src/createDefaultValidator.ts' is not listed within the file list of project 'koas/packages/koas-status-code/tsconfig.json'. Projects must list all files or use an 'include' pattern.

error TS6307: File 'koas/packages/koas-core/src/createMatcher.ts' is not listed within the file list of project 'koas/packages/koas-status-code/tsconfig.json'. Projects must list all files or use an 'include' pattern.

error TS6307: File 'koas/packages/koas-core/src/index.ts' is not listed within the file list of project 'koas/packages/koas-status-code/tsconfig.json'. Projects must list all files or use an 'include' pattern.


Found 12 errors.

I have also tried this tsconfig.json for koas-status-code:

{
  "extends": "../../tsconfig.json",
  "include": ["src"],
  "references": [{ "path": "../koas-core" }],
  "compilerOptions": {
    "outDir": "lib"
  }
}

This builds the workspace, but still gives me the following error:

$ tsc --noEmit false
src/index.ts:1:23 - error TS6305: Output file '/home/remco/Projects/koas/packages/koas-core/lib/src/index.d.ts' has not been built from source file '/home/remco/Projects/koas/packages/koas-core/src/index.ts'.

1 import * as Koas from 'koas-core';
                        ~~~~~~~~~~~


Found 1 error.

How do I fix this?

1 Answer 1

14

I have found a solution for this problem based on https://github.com/NiGhTTraX/lerna-ts.

The project now contains a file called tsconfig.build.json in the project root. This file specifies the compiler options and that test and build files should be excluded from the build process.

{
  "compilerOptions": {
    "declaration": true,
    "module": "commonjs",
    "resolveJsonModule": true,
    "target": "esnext"
  },
  "exclude": ["**/*.test.ts", "**/lib"]
}

Another file in the root is tsconfig.json. This file is used for type checking outside of the build process, e.g. by Jest and VSCode. This file extends the build configuration. It includes tests by overriding the extended exclude configuration. It also contains noEmit: true, because processes using TypeScript while developing shouldn’t emit anything. Also it contains the baseUrl and paths compiler options that are needed to resolve the TypeScript source files at runtime. This is because the main field in the packages’ package.json files refer the lib, which contains the output files.

{
  "extends": "./tsconfig.build.json",
  "compilerOptions": {
    "baseUrl": ".",
    "noEmit": true,
    "paths": {
      "koas-*": ["packages/koas-*/src"]
    }
  },
  "exclude": ["**/lib"]
}

Each workspace contains a tsconfig.build.json file. This is needed, because it requires the src and outDir options need to be relative to the tsconfig file. That is used to build the project. It is important this file is not named tsconfig.json.

{
  "extends": "../../tsconfig.build.json",
  "include": ["src"],
  "compilerOptions": {
    "outDir": "lib"
  }
}

Each workspace also contains a tsconfig.json. This can be used to override compiler options for type checking, for example if one repository would use the dom typings. This could be omitted.

{
  "extends": "../../tsconfig.json"
}

Each workspace has the following scripts section in package.json. This causes the TypeScript to be compiled when the package is built.

  // …
  "scripts": {
    "prepack": "tsc --project tsconfig.build.json"
  }

There are two CI jobs for typechecking. One runs tsc to make sure type checking still works in tools using these types in development, the other makes sure building the packages doesn’t break.

tsc:
  script:
    - yarn --frozen-lockfile
    - yarn workspaces run tsc

pack:
  script:
    - yarn --frozen-lockfile
    - yarn workspaces run pack
    - find packages -name '*.tgz' -exec mv {} ./ +
  artifacts:
    name: packages
    paths:
      - '*.tgz'
Sign up to request clarification or add additional context in comments.

3 Comments

Thank you for carefully phrasing the question and documenting the answer! Does this setup by any chance allow importing files from parent directories, outside the rootDir? I can't figure out how TypeScript is supposed to be used in monorepos, where ModuleA wants to import from ../lib/Something, unless you don't use rootDir and outDir, and accept that .js and .js.map files will accompany every .ts file and pollute the workspace.
TypeScript doesn’t allow you to import outside of the root directory. If you wish to use code across multiple packages, create a package for that code and add it as a dependency.
It is possible but your rootDir needs to include the code you're importing. The only drawback is that code structure in dist has a lot of nesting

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.