Using `rootDirs` in a monorepo setting results in unnecessary subfolders like `src` being generated in the `outDir`

I am in the process of planning a monorepo TypeScript project structured as follows:

/ (root)
+--backend/
|  +-src/
|  \-tsconfig.json
+--shared/
|  \-src/
\--frontend/
   \-src/

The tsconfig.json file looks like this:

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "outDir": "./dist",
    "strict": true,
    "baseUrl": "./src",
    "paths": {
      "shared": [
        "../../shared/src"
      ]
    },
    "rootDirs": [
      "./src",
      "../shared/src"
    ],
    "esModuleInterop": true
  }
}

Upon running tsc within the backend directory, the output structure is currently:

/ (root)
+-backend/
  +-dist/
  | +-backend/
  | | +-src/
  | \-shared/
  |   \-src/
  +-src/
  \-tsconfig.json

In this output, both dist/backend and dist/shared directories contain an additional src subdirectory. However, I would like them to only have the compiled JavaScript files without the src directory:

/ (root)
+-backend/
  +-dist/
  | +-backend/
  | \-shared/
  +-src/
  \-tsconfig.json

Is this achievable? If so, how can I achieve it?

Answer №1

analysis

  • In Typescript, the rootDir setting is crucial for determining the output directory structure. It's important to note that using multiple rootDirs will result in tsc identifying the common parent directory and treating it as the main rootDir.
  • This explains why you might be experiencing a certain output directory structure when dealing with multiple rootDirs.

solution: Organize your monorepo into distinct Typescript sub-projects

A Typescript project can be defined by its own tsconfig file, making it self-contained and bounded by its designated rootDir. This approach aligns well with the principles of encapsulation.

You have the option to set up separate projects within different directories, each having its own tsconfig configuration and rootDir. The dependencies between these projects can be managed through Typescript Project References.

Although the term "project" used by Typescript might seem broad, considering them as subprojects can provide better clarity.

It is recommended to structure your repository as follows:

.
├── dist
└── src
    ├── tsconfig.json
    ├── shared
    │   ├── index.ts
    │   └── tsconfig.json
    ├── backend
    │   ├── index.ts
    │   └── tsconfig.json
    └── frontend
        ├── index.ts
        └── tsconfig.json 

Upon compilation, the resulting code structure should resemble this:

.
├── dist
│   ├── shared
│   ├── backend
│   └── frontend
└── src

Example tsconfigs

  • src/tsconfig.json

    Even if there are no files at the root level, this tsconfig can serve as the central configuration holding common settings. Issuing tsc --build src would then build the entire project structure (use --force for full rebuild).

      {
        "compilerOptions": {
          "rootDir": ".",
          "outDir": "../dist/",
        },
        "files": [],
        "references": [
          { "path": "./shared" },
          { "path": "./backend" },
          { "path": "./frontend" }
        ]
      }
    
    • src/shared/tsconfig.json

      The shared project does not import other projects, containing only internal references and dependencies listed in its package.json. For stricter isolation, consider assigning it a dedicated package.json.

        {
          "compilerOptions": {
            "rootDir": ".",
            "outDir": "../../dist/shared",
            "composite": true
          }
        }
      
    • src/backend/tsconfig.json

      The backend project can reference the shared due to the declared relationship.

        {
          "compilerOptions": {
            "rootDir": ".",
            "outDir": "../../dist/backend",
            "composite": true
          },
          "references": [
            { "path": "../shared" }
          ]
        }
      
    • src/frontend/tsconfig.json

      The frontend project can import both shared and backend thanks to their declared relationships.

        {
          "compilerOptions": {
            "rootDir": ".",
            "outDir": "../../dist/frontend",
            "composite": true
          },
          "references": [
            { "path": "../shared" },
            { "path": "../backend" }
          ]
        }
      

Build Examples

tsc --build src compiles the entire src structure

tsc --build src/backend builds the backend along with shared (if changes have occurred since the last build).

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Unused code splitting chunk in React production build would improve performance and efficiency of

When running the command npm run build, a build directory is generated with js chunks. I have observed an unfamiliar file named [number].[hash].chunk.js that does not appear in the list of entrypoints in the asset-manifest.json file. Instead, this mysteri ...

Preventing nested prototype methods from being transferred between objects in a WebWorker

My challenge is to reserialize an object from a WebWorker while maintaining the same definitions. However, upon receiving the message, all of the prototype functions are lost. The current solution I have only works for first level prototype functions, bu ...

Tips for Simplifying Complex Switch Cases with Object Literals in TypeScript

I have a unique situation where I need to switch between two functions within an object literal. One function takes two numerical arguments, "A" and "B", while the other function only takes a single string argument, "C". My TypeScript code showcases my di ...

Distinguishing the switch function from the React switch operator (jsx, tsx)

We are in the process of converting our portfolio from JavaScript to TypeScript, utilizing NextJS as the frontend framework and Strapi as the backend. To enable dynamic content, we have implemented a dynamiczone field within the post model that is accesse ...

The map function is calling an unresolved function or method named "map"

I'm encountering an error with the map method in my code, even after correctly importing 'rxjs/add/operator/map'. I've followed all the necessary steps and upgraded to rxjs 5.0.1, but the error persists. Do you have any suggestions on h ...

Error: The object 'exports' is not defined in geotiff.js at line 3

Looking to integrate the geotiff library with Angular 6.1.0 and TypeScript 2.9.2. Installed it using npm i geotiff Encountering the following error in the browser console: Uncaught ReferenceError: exports is not defined at geotiff.js:3 After r ...

Encountered an issue trying to access undefined properties while reading 'PP'

I am trying to showcase the data retrieved from my JSON file. Here is a glimpse of the information stored in the JSON => Within DTA > PP , I am specifically interested in displaying the variable NOMFAMILLE. An error message has caught my attentio ...

TypeScript overload does not take into account the second choice

Here is the method signature I am working with: class CustomClass<T> { sanitize (value: unknown): ReturnType<T> sanitize (value: unknown[]): ReturnType<T>[] sanitize (value: unknown | unknown[]): ReturnType<T> | ReturnType< ...

Vite deciding to generate its own node_modules directory within the workspace rather than depending on a monorepo

I have organized a monorepo for a fullstack webapp with the directory structure outlined below: . ├── client │ ├── index.html │ ├── package.json │ ├── src │ └── vite.config.ts ├── node_modules ├── p ...

Is there a way to ensure that the observer.next(something) received the value before executing observer.complete()?

I have been working on an Angular app where I am using a resolver to preload data in the component. Below is the code for the resolver: resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): void { return Observable.create(observer => { ...

What is the best way to retrieve data from MySQL for the current month using JavaScript?

I need to retrieve only the records from the current month within a table. Here is the code snippet: let startDate = req.body.startDate let endDate = req.body.endDate let result = await caseRegistration.findByDate({ p ...

An error has occurred in Angular: No routes were found that match the URL segment 'null'

I've recently developed a simple Angular page that extracts an ID (a guid) from the URL and uses it to make an API call. While I have successfully implemented similar pages in the past without any issues, this particular one is presenting challenges w ...

Activate the event when the radio button is changed

Is there a way to change the selected radio button in a radio group that is generated using a for loop? I am attempting to utilize the changeRadio function to select and trigger the change of the radio button based on a specific value. <mat-radio-group ...

When configuring Gatsby with Typescript, you may encounter the error message: "You cannot utilize JSX unless the '--jsx' flag is provided."

I am currently working on a Gatsby project and decided to implement Typescript into it. However, I encountered an error in my TSX files which reads: Cannot use JSX unless the '--jsx' flag is provided. What have I tried? I consulted the docume ...

Issue encountered while attempting to utilize a basic redux reducer to define a boolean value, regarding a mismatch in overrides

Currently, I am working on a project to enhance my understanding of Redux and Typescript. I am still navigating through the learning curve in this area. Based on what I have learned from a book, I have established a "slice" that includes definitions for r ...

What is the best way to ensure the website theme remains consistent after a refresh in React?

I am currently enhancing a weather forecast website by incorporating a theme toggler feature. The functionality has been successfully implemented, but I am facing an issue where the selected theme does not persist after reloading the page. Can someone he ...

The type 'Bar<T>' cannot be assigned to type 'T extends number ? number | Bar<T> : Bar<T>'

I'm struggling with this particular code snippet: class Foo<T> { x?: T extends string ? string | Foo<T> : Foo<T> } function bar<T>(): Foo<T> { const x: Foo<T> = { } return { x } } Can anyone explain why the ...

the ng-repeat directive disables input controls within the tfoot

When working with JSON data, I encountered a situation where I needed to display different types of student details in a table. For one specific type of student, namely partners, I wanted to include input controls such as checkboxes and buttons. However, e ...

Issue with triggering blur event in Internet Explorer while using Angular 2+

The issue discussed in the Blur not working - Angular 2 thread is relevant here. I have a custom select shared component and I am attempting to implement a blur event to close it when the component loses focus. // HTML <div (blur)="closeDropDown()" t ...

Underwhelming scroll speed when working with 9 columns in react-virtualized

I am currently utilizing react-virtualized in conjunction with material-ui table cells to establish a table featuring virtual scrolling. Although everything appears to be functioning correctly, I am experiencing intermittent performance slowdowns when navi ...