What are the best methods for exchanging code among different TypeScript projects?

Imagine having two separate projects, each with its own unique file structure:

/my-projects/

  /project-a/
    lib.ts
    app.ts
    tsconfig.json

  /project-b/
    app.ts         // import '../project-a/lib.ts'
    tsconfig.json

How can I access and use the lib.ts file located in project-a from project-b without turning it into an NPM module?

  • Distributing it as an NPM module is not desired, as it is unnecessary for such a simple task. The goal is to simply share one file between both projects.

  • Attempting to use the code import '../project-a/lib.ts' results in TypeScript errors.

'lib.ts' is not under 'rootDir'. 'rootDir' should contain all source files.

  • Moving the tsconfig.json file up a level to cover both project-a and project-b is not feasible due to differing configurations and inconvenience.

Are there any alternative methods to achieve this?

Answer №1

In Typescript 3.0 and above, utilizing Project References allows for seamless organization of code.

If you need to move lib.ts into a new project named 'lib', follow these steps:

// lib/tsconfig.json
{
      "compilerOptions": {
        /* Important compiler options */
        "declaration": true, 
        "declarationMap": true,
        "rootDir": ".",   
        "composite": true,     
      },
      "references": []  
    }

Include the reference to the lib project in both project-a and project-b tsconfigs:

// project-a/ts-config.json
// project-b/ts-config.json
{
    "compilerOptions": {
        "target": "es5", 
        "module": "es2015",
        "moduleResolution": "node"
        // ...
    },
    "references": [
        { 
            "path": "../lib",
            "prepend": true, 
        }
    ]
}

Create an index.ts file in the lib project exporting all shared code:

// lib/index.ts    
export * from 'lib.ts';

For example, assume lib/lib.ts contains:

// lib/lib.ts
export const log = (message: string) => console.log(message);

Now, import the log function from lib/lib.ts in project-a and project-b:

// project-a/app.ts 
// project-b/app.ts
import { log } from '../lib';
log("This is a message");

To enable intelissense, build both projects using:

tsc -b 

The typescript compiler relies on declaration files (*.d.ts) generated during building rather than the actual typescript files from lib.

Ensure your lib/tsconfig.json file has:

"declaration": true,

If configured correctly, F12 navigation to the log function in project-a/app.ts should display the correct typescript file.

A sample github repo showcasing project references with typescript can be found here:

https://github.com/thdk/TS3-projects-references-example

Answer №2

To utilize the 'paths' property of 'CompilerOptions' in tsconfig.json, you can achieve the desired outcome.

{
  "compilerOptions": {
    "paths": {
      "@otherProject/*": [
        "../otherProject/src/*"
      ]
    }
  },
}

Displayed below is a snapshot of the directory structure.

https://i.sstatic.net/JG19V.png

Here is the content of tsconfig.json that makes reference to another TypeScript project.

{
  "compilerOptions": {
    "baseUrl": "./",
    "outDir": "./tsc-out",
    "sourceMap": false,
    "declaration": false,
    "moduleResolution": "node",
    "module": "es6",
    "target": "es5",
    "typeRoots": [
      "node_modules/@types"
    ],
    "lib": [
      "es2017",
      "dom"
    ],
    "paths": {
      "@src/*": [ "src/*" ],
      "@qc/*": [
        "../Pti.Web/ClientApp/src/app/*"
      ]
    }
  },
  "exclude": [
    "node_modules",
    "dist",
    "tsc-out"
  ]
}

Below is an example import statement used to access exports from another project.

import { IntegrationMessageState } from '@qc/shared/states/integration-message.state';

Answer №3

I believe @qqilihq is heading in the right direction with their insights - Despite the potential challenges associated with manually curating the contents of a node_modules directory.

In my own experience, I have found success in utilizing lerna for this purpose (although there are various other tools available, such as yarn workspaces which offer similar functionalities, though I haven't personally experimented with them).

I must mention that this approach may seem a bit complex for your current requirements, but it does provide your project with ample flexibility for future growth.

Following this methodology, your code structure might resemble:

/my-projects/
    /common-code/
        lib.ts
        tsconfig.json
        package.json
    /project-a/
        app.ts (Able to import common-code)
        tsconfig.json
        package.json (with dependency on common-code)
    /project-b/
        app.ts (Able to import common-code)
        tsconfig.json
        package.json (with dependency on common-code)

The underlying concept here involves creating symbolic links between your internal libraries and the node_modules directories within their dependent packages.

Some key challenges I've faced while implementing this include

  • common-code requiring both 'main' and 'types' properties to be set in its `package.json` file
  • common-code needing to be compiled before any dependencies can utilize it
  • common-code necessitating 'declaration' to be set true in its `tsconfig.json` file

Overall, my overall experience with this method has been quite favorable; once you grasp the core concepts, it becomes more about following standard practices rather than relying on 'magic', essentially making it a collection of conventional node packages sharing a directory.

Answer №4

As the sole developer working on two projects that share a common code folder, I have implemented a two-way real-time syncing solution to keep the shared code in sync between the two projects.

Project A
 - shared/ -> 2-way sync with Project B
 - abc/

Project B
 - shared/ -> 2-way sync with Project A
 - xyz/

This setup was quick to implement and offers several benefits:

  • No need for configuring and managing mono-repos or multiple tsconfig files
  • No build step required to get the latest code, updates are instant
  • Works seamlessly with cloud build tools as the shared code is within the repository instead of being symlinked
  • Easier debugging locally since all files are within the project structure
  • Ability to implement checks on the shared folder using commit hooks/husky to ensure code quality

If collaborating with other team members in the future, I can recommend using this sync tool as part of the project setup.

Answer №5

Symlinking dependencies can often simplify things, and occasionally it's worth symlinking tsconfig.json too.

While not a one-size-fits-all solution, this approach can be effective in many scenarios, particularly when paired with tools like bun or deno.

Answer №6

Here's an alternative method you might find interesting - consider utilizing a git submodule in your project. Check out this resource for more information on how to use Git submodules.

Answer №7

After numerous experiments and setbacks, I found a way to streamline my project structure without the hassle of NPM releases.

/my-projects/

  /node_modules/
    /my-lib/
      lib.ts
      tsconfig.json
      package.json

  /project-a/
    app.ts
    tsconfig.json

  /project-b/
    app.ts         
    tsconfig.json

The key concept is organizing shared components in a central node_modules folder above individual projects. This leverages NPM's loading process, which first searches for dependencies in the current directory before moving up.

Now, both project-a and project-b can easily import the library with

import { Whatever } from 'my-lib'
.

Important Notes:

  1. In my scenario, my-lib serves as a repository for shared typings (e.g., .d.ts files in a lib subdirectory). As a result, no explicit building of my-lib is necessary. The my-lib/package.json appears like this:

    {
      "name": "my-types",
      "version": "0.0.0",
      "private": true,
      "types": "lib/index"
    }
    
  2. If my-lib includes executable code, you will need to compile it to generate .js files and specify a "main" property in the

    package.json</code to expose the main <code>.js
    file.

  3. Crucially: Despite its name, /my-projects/node_modules only holds custom code, not installed dependencies (those reside in project-a/node_modules and project-b/node_modules). Hence, there must be a deliberate git ignore rule that excludes the /node_modules directory from version control.

Is this solution flawless? Probably not. Did it resolve my problem? Yes. Am I open to suggestions for improvement? Absolutely!

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

Personalized angular subscription malfunction

Recently, as I dive into learning Angular 6, a question has arisen regarding the subscribe method and error handling. A typical use of the subscribe function on an observable typically appears like this: this.myService.myFunction(this.myArg).subscribe( ...

Avoid insecurely assigning an any value in TypeScript

Take a look at this code snippet: const defaultState = () => { return { profile: { id: '', displayName: '', givenName: '', }, photo: '', } } const state = reactive(defaultState() ...

Invoking a self-executing anonymous function using requestAnimationFrame

Recently, I developed a basic 2D-tile-renderer using JavaScript and decided to convert it to TypeScript. The process went smoothly, with the only challenge being when I tried to call window.requestAnimationFrame with a callback function. Eventually, I was ...

Bidirectional data binding in Angular 9 form controls

Implementing two-way binding in an Angular 9 form I attempted to use [(ngmodel)] in the form, however it resulted in an error. This feature has been deprecated in Angular 6 and newer versions. ...

Running an HTTP request conditionally within combineLatest

I'm working on a combineLatest function that makes 2 http requests, but I only want the second request to be made if a specific condition is satisfied. combineLatest([ this.leadsService.fetchALLLeadsActivityChart(this.clientId, this.getParams(option ...

Angular Ionic calendar date selector

I'm encountering an issue while trying to implement a time picker with a minimum and maximum hour range from 10:00 am to 10:00 pm. Unfortunately, I have discovered that I cannot achieve this using the ion-datetime component. Are there any alternative ...

Uncovering the Image Orientation in Angular: Is it Possible to Determine the Direction Post-view or Upon Retrieval from Database?

I am currently working on creating centered and cropped thumbnails for images retrieved from a database. I came across some helpful information on how to achieve this: The resource I found is written for JavaScript, but I am using Angular 7. I am facing d ...

Error TS2451 in GatsbyJS: "react_1" block-scoped variable cannot be redeclared

Trying to implement typescript with custom routes in gatsbyjs: require("source-map-support").install(); require("ts-node").register(); exports.createPages = require("./src/createPages"); tsconfig.json { "include": ["./src/**/*"], "compilerOptions": ...

Tips for refreshing a React component using incremental changes retrieved from an API

I am developing a unique React application using Next.js and TypeScript, with an api-backed data set in one component that needs to be cached indefinitely. Unlike traditional examples I have found online, my component must: Fetch only the most recent 100 ...

Is there a way to set a default value for an Angular service provider?

Imagine an Angular Service that encapsulates the HTTP Client Module. export class HttpWrapperService { private apiKey: string; } Of course, it offers additional features that are not relevant here. Now I'm faced with the task of supplying HttpWr ...

Having trouble retrieving object property despite returning an object type - React with Typescript

I am facing a issue with my React state where I have an array of objects and I am adding an object to it using the setState method. The objects are successfully added to the array. However, when I try to access the properties of the object in another func ...

Getting a list of the stack resources available in cloudformation using TypeScript

My team is developing an application that will deploy multiple stacks to AWS. One of these stacks is called SuperStar, and it can only exist once per AWS account. I am currently exploring how our TypeScript CDK can retrieve a list of stacks from CloudFor ...

Utilize JSON configuration file to extract parameters for Protractor testing

Is there a way to access file values from protractor.config.ts without passing them as command line arguments? I have commented out the lines that attempt to read the files. protractor.config.ts import { } from 'jasmine'; import { Config } from ...

In Typescript, how can we reverse the order of an enum

I am encountering a particular challenge with the following code snippet: enum MyEnum { Colors = 'AreColors', Cars = 'AreCars', } const menuTitle = ((obj: MyEnum) => { const newObj = {}; Object.keys(obj). ...

Enhance the express Response type and then export my updated type as a distinct module

I am currently working on developing a new 'common' module for my team. One of the key features it should have is an extension of both the response and request objects in Express. Our main goal is to achieve two things - first, extending the Req ...

Navigating through JSON object using Angular 2's ngFor iterator

I want to test the front end with some dummy JSON before I write a service to get real JSON data. What is the correct way to iterate through JSON using ngFor? In my component.ts file (ngOnInit()), I tried the following code with a simple interface: var js ...

Angular is used to send an HTTP GET request

I'm seeking assistance with implementing a get and put request in Angular. I understand how to initiate a get or put request when a button is clicked, by binding the request to the button itself. However, I am now looking for a way to trigger a get re ...

Ensuring the Existence of Variables Based on Parameterized Type in TypeScript Return Type Declaration

Within my TypeScript class called Design, there is a method named checkFetched. This method's purpose is to verify the existence of a property of type DesignData within an instance of the class, based on a parameterized type called Filename. Here is a ...

Angular Material sidebar small version with dropdown menus

I am currently using Angular 5 in conjunction with Material design. My application features a side navigation menu with an angular material navigation drawer mini variant, where the items are hidden, leaving only the icons when collapsed (see image). My g ...

Angular - Array binding in view not refreshing

Within the following code snippet, the push function is utilized to transfer only the checked row from an array to another. Despite the successful execution of the push operation, the view does not reflect this update. onNextclick() { this.disable1 ...