Collaborating on a TypeScript library within a monorepo

I'm currently in the process of setting up a monorepo that involves 3 services sharing some library code.

Here's how it stands at the moment:

repo: web
pdf/
  package.json
    referencing shared-ts via github url
  tsconfig.json
frontend/
  package.json
    referencing shared-ts via github url
  tsconfig.json
repo: mobile (react-native)
  package.json
    referencing shared-ts via github url
  tsconfig.json
repo: shared-ts
  package.json
  tsconfig.json

This setup is functional, but the process of committing to shared-ts, building, updating the hash in package.json, and committing again is cumbersome.

My goal is to achieve the following structure:

repo: monorepo
pdf/
  package.json
    reference to ../shared-ts
  tsconfig.json
frontend/
  package.json
    reference to ../shared-ts
  tsconfig.json
mobile/
  package.json
    reference to ../shared-ts
  tsconfig.json
shared-ts/
  package.json
  tsconfig.json

I have attempted various approaches so far, such as:

  • Using TypeScript project references, but struggling with dependencies in the shared-ts project
  • Adding
    "shared-ts": "../shared-ts"
    in package.json, which copies shared-ts into the node_modules of each package, requiring yarn to be re-run after every change
  • Attempting to use yarn link in postinstall, resulting in an error message about not finding the module 'shared-ts' or its type declarations
  • Creating a symlink directly in postinstall using
    ln -s ../shared-ts/ node_modules/shared-ts/
    , causing TypeScript to fail in finding the module
  • Exploring the option of using npm link in postinstall, despite it being slow and running into permissions issues during CI execution

Is there an efficient method to accomplish this? Any suggestions on alternative methods I could experiment with?

Answer №1

To handle your use-case efficiently, you can make use of the npm7 workspaces feature. Essentially, your new monorepo structure should be organized as follows:

repo: monorepo
package.json // <- Define workspaces here
pdf/
  package.json
    linking to shared-ts
  tsconfig.json
frontend/
  package.json
    linking to shared-ts
  tsconfig.json
mobile/
  package.json
    linking to shared-ts
  tsconfig.json
shared-ts/
  package.json
  tsconfig.json

In the root package.json, you need to specify the workspaces like in the example below:

{
  "name": "awesome-monorepo",
  "workspaces": [
    "pdf",
    "frontend",
    "mobile",
    "shared-ts"
  ]
}

Once this setup is done, you can easily reference and use the shared-ts module within any part of the monorepo by adding it to dependencies or devDependencies using its version number instead of a relative path.

All the node modules, including the workspaces, will be consolidated in the root node_modules directory, ensuring smooth module resolution without any issues.

Answer №2

Solution 1:

Utilizing Lerna for Workspace Management

If you want to efficiently manage workspaces, consider using Lerna along with Yarn.

yarn workspace & lerna

├── README.md
├── lerna.json
├── package.json
├── packages
│   ├── pdf
│   │   ├── package.json   /*  "shared-ts": "^1.0.0" */
│   │   └──  src
│   ├── frontend
│   │   ├── package.json
│   │   └── src
│   ├── mobile
│   │   ├── package.json
│   │   └── src
│   ├── shared-ts
│   │   ├── package.json
│   │   └──  src
├── tsconfig.json
└── yarn.lock

For a demonstration, check out this sample repository.

To observe how modules are shared, see x-cli and x-core.

Solution 2:

Bypassing Lerna with mtsl Package

Alternatively, if you prefer not to use Lerna, you can opt for the mtsl package which allows creating symlinks. This package needs to be installed globally.

npm install -g mtsl

After installation, execute separate commands in the terminal as follows:

mtsl startwithoutadd -s path_of_project/packages/shared-ts -d path_of_project/packages/pdf/node_modules/shared-ts

mtsl startwithoutadd -s path_of_project/packages/shared-ts -d path_of_project/packages/frontend/node_modules/shared-ts

mtsl startwithoutadd -s path_of_project/packages/shared-ts -d path_of_project/packages/mobile/node_modules/shared-ts

Note: Maintain these watchers running. Once testing is completed, consolidate them into a single command using scripts in package.json.

Answer №3

In a recent project, I successfully implemented the strategies you're interested in. By utilizing monorepo with tools like lerna and yarn workspace, we were able to achieve our desired outcomes effectively. For more in-depth information, please refer to this informative article

Through this approach, we established a centralized package system for managing types. Subsequently, other packages could easily import these types by referencing them as shown below:

import { Post } from "@types";

This method proved to be significantly more convenient than manually linking individual packages together.

Answer №4

If you're looking for a way to streamline your repositories, consider using NX. With NX, you can organize your projects so that your web and mobile repositories serve as your apps, while the shared-ts repository acts as a library that is shared between them.

You can choose to have a common package.json file for all your repositories or have separate ones for each. NX comes with features like dependencyGraph and affected, which help determine the modules/apps that need to be built when making changes without having to build everything from scratch.

Your code structure within NX will typically follow this format:

apps:
  web:
    src
    package.json

  mobile:
    src
    package.json

libs:
   shared-ts:
     src
     package.json

workspace.json

For more detailed information on setting up and configuring NX, it's recommended to refer to the official documentation. It should offer everything you need for an efficient workflow.

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

Encountering issues with setting up Swiper in Angular2 using npm

I'm attempting to utilize the swiper library within my angular2 + npm application, but I'm encountering an error during the build process. The specific error message is: ERROR in vendor.95f9dda80938ded736ad.bundle.js from UglifyJs Unexpected to ...

An error has occurred: String cannot have property 'innerText' created

I'm encountering an issue while attempting to dynamically add posts to my post div. The problem arises when I try to include image URLs in the process. Switching from innerText to innerHTML did not resolve the issue, and the array I added is also not ...

Transferring information using "this.$router.push" in Vue.js

I'm currently developing a restaurant review project using Django REST and Vue.js. To ensure uniqueness, I have adopted Google Place ID as the primary key for my restaurants. The project also incorporates Google Place Autocomplete functionality. The ...

Unable to complete the task of executing com.github.eirslett:frontend-maven-plugin:1.6:install-node-and-npm (install node and npm)

I'm encountering an issue with Maven that's been causing some trouble: [ERROR] Failed to execute goal com.github.eirslett:frontend-maven-plugin:1.6:install-node-and-npm (install node and npm) on project xxx-frontend: Could not download Node.js: ...

Incorporating unique numbers in the map reduce process

I have a CSV file containing information on which numbers called each other and the details of the calls like duration, time, etc. My goal is to compile all the numbers that a specific number has called into an array. Each element in this array should be ...

Incorporate Javascript to dynamically deactivate a field within an HTML form based on the selected option from a dropdown menu

I'm completely new to JavaScript and have been trying to figure this out on my own by researching online, but unfortunately, I haven't had any luck. I'm currently working on creating a form using HTML and JavaScript. One specific issue I&ap ...

The browser has blocked access to XMLHttpRequest from a specific origin due to the absence of the 'Access-Control-Allow-Origin' header in the requested resource

After developing an Asp.Net Core 3.1 API and deploying it on the server through IIS, everything worked fine when sending GET/POST requests from Postman or a browser. However, I encountered an error with the following code: $.ajax({ type: 'GET' ...

Elessar - addressing touch event issues

I have been experimenting with implementing multiple ranges using a library called elessar. Despite being a great library, the documentation is lacking. I managed to set everything up but it doesn't seem to work on touch devices. There are no errors s ...

Is there a way to prevent the letters from moving when I hover over them?

What occurs when the numbers are hovered over: https://gyazo.com/20b6426d435551c5ee238241d3f96b4d Whenever I hover over the pagination numbers, they shift to the right, and I am unsure of what mistake I made in my code that's causing this. Below, I ...

Unable to resolve the RouterLink tag

When setting up a Vanilla Vue CLI installation, I encountered some issues with getting the Vue router to work properly. Despite following the online documentation and forum discussions, I couldn't resolve the error message "Uncaught ReferenceError: ro ...

Manage numerous receiving bank accounts, allowing customers to transfer money to each specific account

Managing multiple receiving bank accounts and enabling customers to transfer money to specific accounts is a key requirement in my application. Can Plaid help me achieve this functionality? Could you provide guidance on how to implement this feature using ...

When the server restarts, activate the Meteor npm run script

Is it possible to execute an npm script every time a Meteor server restarts? I attempted using the postinstall hook, but it only runs during the initial local application launch. I believe there must be a way to achieve this, as a server restart triggers ...

Spring application: Unable to find a handler for portlet request with mode 'view' and phase 'Resource_PHASE'

It seems like everything is set up correctly, but for some reason, my ajax call fails with the error message "No handler found for portlet request: mode 'view', phase 'Resource_PHASE'". The handler URL I'm using is "getAllFruit", ...

Combine Wordnet thesaurus with browserify technology

I have been using a thesaurus API from altervista for my JavaScript web application, but I am looking to host my own thesaurus on my web server. This way, I can make numerous synonym requests without worrying about API limitations. My goal is to be able to ...

Creating a useRef hook tailored to individual HTML elements is a valuable skill to have

As I dive deeper into Typescript, my aim is to craft a custom hook using React.useRef that can handle any HTML element, not just buttons. Despite my efforts, I am struggling with properly typing this hook. This is the code for my custom hook: type CustomR ...

When utilizing a Javascript event listener within ReactJS with NextJS, the dynamically imported module without server-side rendering fails to load

Hello everyone, I am new to ReactJS and NextJS and would really appreciate some advice on the issue below. Thank you in advance! Here is my current tech stack: Node v16.6.1 React v17.0.2 Next.js v10.0.4 I'm working on implementing a carousel and si ...

NodeJS Code Snippets for Natural Language Processing

In my NodeJS application, I am utilizing the talkify library to create a chatbot that can process code snippets of a closed-source language. These snippets are processed through a REST API and the result is returned by the bot. While I have successfully i ...

Fleeting hover effect

When using the CSS "hover" selector, a temporary style is applied to an element, but it's not permanent: div:hover { background-color: red; } Attempting to achieve the same effect with JavaScript can be more complex and challenging when dealing with ...

browserify Issue : The function http.createServer is not recognized

While attempting to browserify my Node.js script, I encountered this code: var phantom = require('phantom') phantom.create(function(ph) { ph.createPage(function(page) { page.open("editor.html", function(status) { console.log("opened ...

Configuring the release settings for a single page application using webpack

I'm currently working on a Single Page Application and leveraging Webpack for bundling my modules. One of my TypeScript source files contains essential settings and configurations utilized throughout the application: // app-settings.ts export class ...