Creating a Build-Free Workflow in a TypeScript Monorepo

Imagine having this monorepo structure:

/apps
/apps/app1
/apps/app1/src  (includes index.ts and various other files and subdirectories)
/apps/app1/tsconfig.json
/apps/app1/package.json
/apps/app2 
/apps/app2/src   (contains index.ts and many other files and subfolders here)
/apps/app2/tsconfig.json
/apps/app2/package.json
/common
/common/package1
/common/package1/src
/common/package1/src/utility1.ts
/common/package1/src/utility2.ts
/common/package1/tsconfig.json
/common/package1/package.json
/tsconfig.json
/package.json

If only esm functionality is needed, how can I run tsx, tsc, or any ts tool for apps using resources from "common/*/src" without the need to compile "common" contents into a dist folder?

This setup should enable immediate implementation of changes made within /common when running an app in watch mode.

Answer №1

It seems there may be some confusion in your question, but I believe you are interested in a feature similar to pnpm workspaces: . It's unclear how this concept would interact with different package managers.

You can find an example I've created here: https://github.com/quentinkrammer/mono.

To see it in action, navigate to mono/app1 and run pnpm install, followed by pnpm dev.

This image provides a visual representation of the process: https://i.sstatic.net/HPC6V.png

In this setup, the App2 workspace is linked to the node_modules folder of App1. Any changes made in App2 will immediately reflect in App1. This mechanism applies to type checking and Hot Module Replacement (HMR) as well.

I hope this explanation proves useful to you.

Answer №2

To begin, navigate to the main directory of your monorepo and run npm init -y to generate a package.json file. Next, install TypeScript and ts-node as development dependencies with the command

npm install --save-dev typescript ts-node
. After that, create a tsconfig.json file in the root directory with the following configuration:

{
   "compilerOptions": {
     "module": "es2020",
     "resolveJsonModule": true,
     "esModuleInterop": true,
     "rootDir": ".",
     "outDir": "./dist"
   },
   "include": [
     "apps/*/src",
     "common/*/src"
   ]
}

Now, within each app's folder (/apps/app1 and /apps/app2), update the tsconfig.json as follows:

{
   "extends": "../../tsconfig.json",
   "compilerOptions": {
     "outDir": "./dist",
     "rootDir": "./src",
     "jsx": "react-jsx"
   },
   "include": [
     "src"
   ]
}

Additionally, make changes to the package.json file in each app's folder (/apps/app1 and /apps/app2) and then execute npm start to launch the app in watch mode:

{
   "scripts": {
     "start": "ts-node src/index.ts"
   }
}

Finally, any modifications made to files within the /common directory will be immediately reflected when running the apps!

Answer №3

Regrettably, Windows does not provide straightforward support for folder links, requiring manual command input.

In contrast, Linux allows me to effortlessly incorporate an entire folder from another repository into the current one without rebuilding everything. This method is more reliable than various path mapping and aliases, which often result in issues either with web functionality or unit testing. Furthermore, incorrect path mappings can disrupt source maps, creating debugging challenges.

Nevertheless, this approach is not recommended for large packages.

An issue arises with import path mapping; all paths are made relative, making relocation problematic.

It is advisable to create a separate package if the code is intended to be shared among multiple applications. Doing so allows for easier management of changes and unit tests while focusing on component development.

A separate package will always use fully qualified module paths rather than relative paths, simplifying the process of copying and handling multiple packages.

While a monorepo may streamline deployment processes by reducing dependencies, it is not a universal solution. The primary purpose of a monorepo is to have a unified build and deploy script, enabling integration of dependent npm packages within a single script.

As the codebase grows, components should exist outside of the monorepo to facilitate their reuse across different projects. For instance, if developing accounting software for one client and a social network for another, both projects could share reusable components sourced from a central registry.

Creating a single monorepo for all clients and projects may not be the most effective solution in the long run.

Answer №4

For apps to recognize common/package1 as a package and import it from a Javascript module instead of raw Typescript files, the package must be built in some way - there's no avoiding it. This approach simplifies consumption for the apps since they don't need to worry about the dependencies or build specifics of the package.

Utilizing workspaces streamlines the process. Simply list the apps and common package in the main package.json like so:

{
  "workspaces": [
    "common/package1",
    "apps/app1",
    "apps/app2"
  ],
  ...
}

This will compile the projects into the top-level node_modules directory for mutual access.

The common/package1 needs to be configured as a package and a watcher should continue building it upon updates:

{
  "name": "common/package1",
  "version": "1.0.1",
  "scripts": {
    "watch": "tsc --watch"
  },
  "module": "dist/index.mjs",
  "types": "dist/index.d.ts",
  ...
}

(This example assumes the use of plain tsc with a setup for building a single file module - feel free to request an illustration of tsconfig or Rollup setup)

The benefit is that you can now treat it as a regular dependency in the apps:

{
  "name": "apps/app1",
  "dependencies": {
    "common/package1": "^1"
  },
  ...
}

If the app's build tool is able to detect changes, it will automatically rebuild when common/package1 is updated (i.e. its watcher runs). It should function smoothly. Keep me posted on your progress.

Answer №5

What you're mentioning is referred to as the concept of going buildless.

While there are numerous articles discussing this topic, I'd like to highlight the modern-web getting started article. Our team is currently exploring the use of web components and adopting a buildless approach within a mono repo structure.

As an illustration:

Shown below is our configuration snippet for setting up storybook using @rollup in the main.js file.

module.exports = {
  stories: ['../packages/*/stories/*.stories.ts'],
  rollupConfig(config) {
    config.plugins.unshift(
      typescript(),
      nodeResolve(),
      commonjs({
        include: /node_modules/,
      }),
    );

    return config;
  },
};

Additionally, here's our npm script for running storybook through the web development server, where we utilize tsc for compilation checking and linting.

"storybook": "tsc --noEmit && concurrently -k -r \"tsc --noEmit --watch --preserveWatchOutput\" \"wds -c .storybook/server.mjs\"",

We only compile to dist when publishing our npm packages. All changes at the .ts level are compiled on-the-fly and reflected immediately in storybook. This applies to internal package dependencies as well.

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

Error TS2339: The 'phoneType' property cannot be found on the 'Object' data type

Below is the declaration of an object: export class Card { private _phones:Object[] get phones(): Object[]{ if(this._phones === undefined) this._phones = [] return this._phones } set phones(val:Object[]){ ...

Having trouble importing the d3-geo package into a Node.js TypeScript project

Seeking a way to test the inclusion of specific latitude and longitude coordinates within different GeoJSON Features using code. When attempting this with: import d3 from 'd3-geo'; // or: import * as d3 from 'd3-geo' // no difference ...

What is the method for passing an element in Angular2 Typescript binding?

Is there a way to retrieve the specific HTML dom element passed through a binding in Angular? I'm having trouble figuring it out, so here is the relevant code snippet: donut-chart.html <div class="donut-chart" (donut)="$element"> ...

Issue with Angular 5 EventEmitter causing child to parent component emission to result in undefined output

I've been trying to pass a string from a child component to its parent component. Child Component: //imports... @Component({ selector: 'child', templateUrl: './child.component.html', styleUrls: ['./child.c ...

Angular CodeMirror Line-Break function not displaying line numbers

I am currently utilizing Code Mirror from ngx-codemirror. My goal is to split the line when it fits within the width of the parent container. After searching, I found a couple of solutions that suggest using: lineWrapping: true Additionally, in the styles ...

Developing Angular 7 Forms with Conditional Group Requirements

I am currently in the process of developing a form that enables users to configure their payment preferences. The form includes a dropdown menu where users can select their preferred payment method. For each payment method, there is a FormGroup that co ...

Passing data from a container component to a Redux Form component in React with TypeScript

One of my Form container components looks like this: class PersonalDetailContainer extends React.Component<PropTypes> { onSubmit = async (fields: PersonalFields) => { this.props.savePersonalDetail(fields); }; render(): JSX.Element { ...

The function mustAsync onSuccess is not present in this type (typescript)

I currently have 2 mutations that are functioning well. However, I encountered an issue when attempting to implement onSuccess and invalidateQueries. The error message displayed is as follows: Property 'mutateAsync' does not exist on type '{ ...

Discover the most effective method for identifying duplicate items within an array

I'm currently working with angular4 and facing a challenge of displaying a list containing only unique values. Whenever I access an API, it returns an array from which I have to filter out repeated data. The API will be accessed periodically, and the ...

Angular JSON converter - Transform XML data to JSON format

Struggling to convert XML API response to JSON using xml2js library, facing issues with getting 'undefined' in the console. Here is my API service: export class WordgameService { public apiUrl = "http://www.wordgamedictionary.com/api/v1/reference ...

Avoiding useCallback from being called unnecessarily when in conjunction with useEffect (and ensuring compliance with eslint-plugin-react-hooks)

I encountered a scenario where a page needs to call the same fetch function on initial render and when a button is clicked. Here is a snippet of the code (reference: https://stackblitz.com/edit/stackoverflow-question-bink-62951987?file=index.tsx): import ...

Angular is not rendering styles correctly

Having two DOMs as depicted in the figures, I'm facing an issue where the circled <div class=panel-heading largeText"> in the first template receives a style of [_ngcontent-c1], while that same <div> gets the style of panel-primary > .p ...

Troubleshooting problems with contenteditable and input in Firefox and Safari on Angular 5

Currently, I am in the process of creating a table with cells that are editable. However, I am facing a challenge in updating the visual and typescript code simultaneously. I have explored various alternatives but unfortunately, none of them seem to work. ...

What is the process for sending an email using Angular 6 and ASP.NET CORE web api?

When a user inputs their email and password in the designated boxes, they also include a recipient email address with a CC. This information is then sent via a web api. The email will contain text like "Total Sales Today" along with an attached PDF file. ...

Angular expands the HTML of the parent component

Although this question may be old, I am still struggling to find a straightforward answer and it just doesn't make sense to me. I have a main component with its own HTML and several components that inherit from it. Here is what I am trying to achiev ...

The Angular Material Table is not showing any data on the screen

My challenge is to consolidate data from 4 different endpoints in order to generate a ListElement that will populate an angular material table. Despite receiving the correct data in my logs, the table remains empty. Interestingly, when I include a conditio ...

Make an indirect mention of a distant JavaScript web address

Our company is looking to incorporate Rollup with Angular 4/Typescript and NPM, and we have a specific set of requirements: Various teams develop JS libraries that need to be centralized, similar to a CDN These libraries are hosted at remote URLs and sho ...

What is the purpose of adding "/dist" to the library import statement?

Currently, I am in the process of developing a react component library using vite as my primary build tool. After successfully compiling the project and deploying it to the npm registry, I encountered an issue when importing it into my client app. Specifi ...

What is the best method for exporting and importing types in React and Next.js apps?

Is there a way to export and import types from API responses in TypeScript? I have a type called Post that I want to re-use in my project. // pages/users.tsx type Post = { id: number; name: string; username: string; email: string; address: { ...

What's the best way for me to figure out whether type T is implementing an interface?

Is it possible to set a default value for the identifier property in TypeScript based on whether the type extends from Entity or not? Here's an example of what I'm trying to achieve: export interface Entity { id: number; // ... } @Compon ...