How to use yarn workspaces with typescript and out folders?

I created a Github Repository to make it easier to follow the following code description:


Code Description

TypeScript Project References make it possible to compile a TypeScript project that consist of multiple smaller TypeScript projects, each project having a tsconfig.json file. (Source: Project References Documentation)


TypeScript Setup

We have a root tsconfig.json file that only manages its sub-projects. The references property specifies the directories that each contain a valid tsconfig.json file. If we now build the project with the --build option (tsc --build tsconfig.json) then we specified the projects which should be compiled, but we didn't specified the build order in which the projects should be compiled.

{
  "references": [
    { "path": "./client" },
    { "path": "./cmd" }
  ],
  "files": [],
  "include": [],
  "exclude": ["**/node_modules"]
}

To correctly specify the build order we need to add a references property to the cmd/tsconfig.json file. This tells the compiler that it first needs to compile client/ before we compile cmd/:

cmd/tsconfig.json:

{
  "extends": "../tsconfig.packages.json",
  "compilerOptions": {
    "rootDir": "src",
    "outDir": "dist"
  },
  "references": [
    {
      "path": "../client"
    }
  ]
}

Build order

client/
  ^
  |
  |
 cmd/

Node Setup

Best practice is that each sub-project has its own package.json file with the main property and the name set. In our example both packages (cmd/ and client/) have a main property pointing to the index.js file in the TypeScript outDir directory (cmd/dist/index.js and client/dist/index.js).

Project structure:

tsconfig.json
cmd/
    tsconfig.json
    package.json
    src/
        index.ts
    dist/  #artifacts
        index.js
client/
    tsconfig.json
    package.json
    src/
        index.ts
    dist/  #artifacts
        index.js

client/packages.json

{
  "name": "client",
  "version": "1.0.0",
  "main": "dist/index",
  ...
}

It is important that we add the client/ as dependency to the cmd/packages.json so the module resolution algorithm can find the client/dist/index.js when we import it in our TypeScript code import Foo from 'client';:

cmd/packages.json

{
  "name": "cmd",
  "version": "1.0.0",
  "main": "dist/index",
  "dependencies": {
    "client": "1.0.0" // important
  }
}

cmd/src/index.ts

import Foo from 'client';

console.log(Foo())

Yarn Setup

The yarn setup is easy. Yarn adds all packages under node_modules instead of:

  • cmd/node_modules
  • client/node_modules

To enable yarn workspaces add the workspaces property and the private: true property to the <root>/package.json file.

<root>/package.json

{
  "private": true,
  "workspaces": [
    "cmd",
    "client"
  ],
  "name": "yarn_workplace",
  "version": "1.0.0"
  ...
}

The cmd/ and client/ packages are symlinked under the <root>/node_modules/ directory:

yarn node_modules


Notes

  • To enable code navigation one has to first build the project
  • Every package lives on its own. The cmd/ package uses the definition file client/dist/index.d.ts for type information instead of using the the TypeScript files directly.