Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use yarn workspaces with typescript and out folders?

I'm trying to set up a monorepo using yarn. I'm confused as to how to set up typescript with project references such that things resolve properly.

For example, if I have a folder structure like

/cmd /client 

And I want cmd to depend on client I could have:

cmd/tsconfig.json:

{   "compilerOptions": {     "types": ["reflect-metadata", "jest"],     "experimentalDecorators": true,     "emitDecoratorMetadata": true,     "moduleResolution": "node",     "declaration": true,     "importHelpers": true,     "composite": true,     "target": "esnext"     "sourceRoot": "src",     "outDir": "dist"   },   "references": [     {       "path": "../client"     }   ],   "include": [     "src/**/*"   ] } 

with a package.json

{   "name": "cmd",   "version": "1.0.0",   "dependencies": {     "client": "^1.0.0",   } } 

In this model both cmd and client get compiled with an outDir and sourceRoot field set in their tsconfig. This means all their compiled javascript goes into the dist/ subfolder of cmd/dist and client/dist

If now I try and reference a class from client into cmd like

import Foo from 'client/src/foo' 

The IDE is perfectly happy to resolve this since it seems that its mapped via the typescript references property.

However, the compiled javascript boils down to a

const foo_1 = require("client/src/foo"); 

However, the actual built javascript is in client/dist/src/foo, so at runtime this never works.

On the flip side, if I don't use sourceRoots and outDirs and have the javascript inlined with the typescript files at the same folder everything does work (but makes the repo dirty and requires custom gitignores to exclude things)

Can anyone shed any light on how to properly set up a typescript 3.x monorepo with yarn workspaces such that things just work?

like image 796
devshorts Avatar asked Aug 27 '19 17:08

devshorts


People also ask

What is the point of yarn workspaces?

Yarn Workspaces is a feature that allows users to install dependencies from multiple package. json files in subfolders of a single root package. json file, all in one go. Yarn can also create symlinks between Workspaces that depend on each other, and will ensure the consistency and correctness of all directories.

What is Workspace aggregator yarn?

Workspaces are a new way to set up your package architecture that's available by default starting from Yarn 1.0. It allows you to setup multiple packages in such a way that you only need to run yarn install once to install all of them in a single pass.

What is yarn lerna?

Lerna is a tool used to manage monorepos. The repositories are structured into sub repositories. It is typically used in large codebases for shared dependency management and code deployment. Lerna has two major features, namely bootstrap and publish.


1 Answers

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.
like image 182
a1300 Avatar answered Sep 18 '22 00:09

a1300