What is the best way to distribute a Typescript module across multiple projects without having to duplicate it each time?

I currently have two folders named shared and project1 within a larger folder called node. I am in the process of working on multiple Node projects that are independent and are all located within the node folder.

Within the shared folder, there is an index.ts file that contains a default export. The outdir parameter in the tsconfig.json file is set to ./dist. Once the project is built, the directory structure looks like this:

node/shared/
           src/
              index.ts
           dest/
               index.js
           tsconfig.json
           ...

For project1, the directory structure post-build looks like:

node/project1/
             src/
                index.ts
             dist/
                 index.js

The purpose of the shared folder is to hold common utilities that can be shared among the different projects within the node folder.

However, when I try to import the shared utilities using the statement

import shared from "../../shared/src/index.js"
in the project1/src/index.ts file, the resulting compiled output in the dest folder is chaotic:

node/project1/
             src/
                index.js
             dest/
                 shared/
                       src/
                          index.js  <- copied from the shared project
                 project1/
                         src/
                            index.js

Nevertheless, this setup is not functional as the node modules that are installed in the shared project are not accessible in project1.

My goal is to have a simple run-time environment without duplicating shared code across projects, like so:

node/
    shared/
          index.js
    project1/
            index.js   <- importing from shared/index
    project2/
            index.js   <- importing from shared/index

Is this feasible, or is converting the shared module into an NPM package the only viable solution?

Answer №1

To implement project references, you can visit the following link: project references.

node/
  project1/
    dist/
      index.js
    src/
      index.ts
    tsconfig.json
  shared/
    dist/
      index.js
      index.d.ts
    src/
      index.ts
    package.json
    tsconfig.json

Shared

This project is constructed like a regular project, with the addition of enabling the composite setting.

shared/tsconfig.json

{
  "compilerOptions": {
    // The referenced projects must have the composite flag turned on
    "composite": true,
    "rootDir": "./src",
    "outDir": "./dist",
    "module": "commonjs"
  }
}

shared/package.json

{
  "name": "shared",
  "version": "0.0.1",
  "main": "./dist",
  "scripts": {
    "build": "tsc"
  },
  "devDependencies": {
    "typescript": "^4.0.3"
  }
}

shared/src/index.ts

export default 'foo'

The resulting output in shared/dist/index.js is as follows:

"use strict";
exports.__esModule = true;
exports["default"] = 'foo';

Due to the presence of the composite compiler flag, type definitions are generated as well:

shared/dist/index.d.ts

declare const _default: "foo";
export default _default;

Project

project1/tsconfig.json

In this file, you need to specify that you are referencing the shared module.

{
  "compilerOptions": {
    "rootDir": "./src",
    "outDir": "./dist"
  },
  "references": [
    {"path": "../shared"}
  ]
}

project1/package.json

Instead of running a plain tsc command, you can opt for tsc --build (or tsc -b for short). This build mode ensures that referenced projects are automatically rebuilt if they are outdated. Keep in mind that there are specific build-only flags and compiler options cannot be overridden with command-line arguments.

{
  "name": "project1",
  "version": "0.0.1",
  "main": "./dist",
  "scripts": {
    "build": "tsc -b"
  },
  "devDependencies": {
    "typescript": "^4.0.3"
  }
}

project1/src/index.ts

Utilize the shared module just like any other module.

import foo from '../../shared'

console.log(foo)

project1/dist/index.js

After compilation, the source code transforms into the following:

"use strict";
exports.__esModule = true;
var shared_1 = require("../../shared");
console.log(shared_1["default"]);

As you can observe, it correctly imports the shared module from the node/shared directory without duplicating files into the dist directory.

There is no need to specify the complete file path (../../shared/dist/index.js) because the shared project's package.json file has the main field set to ./dist, which resolves to ./dist/index.js. TypeScript understands that Node.js will resolve the require to the correct file and thus recognizes the types of the shared module.

If there is no main entry in the package.json, you will have to use

import foo from '../../shared/dist'
.

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

Tips for creating a window closing event handler in Angular 2

Can someone guide me on how to create a window closing event handler in Angular 2, specifically for closing and not refreshing the page? I am unable to use window.onBeforeunLoad(); method. ...

What is the correct way to utilize a variable as a parameter in React Query while employing the axios.request(options) method?

I'm currently working on a React Query component with the following structure: function test () { const [var, setVar] = useState("") const options = { method: "GET", url: "https://api.themoviedb.org/3/search/tv" ...

Error: Unable to access the 'https' property as it is undefined

My nuxt app, which is built with Firebase and Vue, encounters an error when running the emulator. The error message states: TypeError: Cannot Find Property 'https' of undefined. This issue seems to be related to the https property in my index.ts ...

I'm having trouble importing sqlite3 and knex-js into my Electron React application

Whenever I try to import sqlite3 to test my database connection, I encounter an error. Upon inspecting the development tools, I came across the following error message: Uncaught ReferenceError: require is not defined at Object.path (external "path ...

Necessary Typescript class property when executing code

Is there a way to determine if a class property is required in Typescript at runtime? export class A { public readonly ab?: number; public readonly ac?: number; public readonly ad: number; public readonly ae: number; } Can emitDecoratorMetadata o ...

Exploring the depths of friendship with React-Router V6 through recursive routes

I am currently facing an issue with my React-Router V6 implementation. The example I found for recursive routes in React-Router V5 is exactly what I need: However, after migrating to react-router-dom@6, the output is not as expected. import { Routes, ...

Having trouble with the Angular Language Service extension in VS Code for Angular-16?

Upon transitioning to Angular 16, I encountered errors while attempting to edit the components HTML due to the malfunctioning of the Angular Language Service extension. [Info - 09:41:11] Angular language server process ID: 18032 [Info - 09:41:11] Using t ...

The properties required by the type for typescript reactjs are not present

I've come across an array with the following structure: export const SideBarTags = [ { name: 'Tutorials', link: '../tutorials', icon: faFileAlt, dropdownItems: null, active: false, }, { name: 'An ...

What is the reason behind TypeScript enclosing a class within an IIFE (Immediately Invoked Function

Behold the mighty TypeScript class: class Saluter { public static what(): string { return "Greater"; } public target: string; constructor(target: string) { this.target = target; } public salute(): string { ...

Angular - Dividing Values within Input Arrays

In the input field available to users, they can enter multiple inputs separated by commas. <div class="container"> Enter your values:<input type="text" multiple #inputCheck> <input type="submit"(cli ...

Tips for stopping webpack from creating compiled files in the source directory

I'm in the process of transitioning my AngularJs project from ES6 to TypeScript and I've integrated webpack with ts-loader. However, I've encountered an issue where the compiled files and source maps are saved in my directory instead of bei ...

Having T extend Record<string, any>, the keyof T does not return 'string' as a type

My goal is to achieve the following: type UserDataProps<FieldName extends keyof DataShape, DataShape extends Record<string, any>> = { id: string; value: DataShape[FieldName]; } const userDataBuilder = <FieldName extends keyof DataShape, ...

Creating a type declaration for an object by merging an Array of objects using Typescript

I am facing a challenge with merging objects in an array. Here is an example of what I am working with: const objectArray = { defaults: { size: { foo: 12, bar: { default: 12, active: 12 } }, color: {} } } ...

Issue encountered when attempting to run "ng test" in Angular (TypeScript) | Karma/Jasmine reports an AssertionError stating that Compilation cannot be undefined

My development setup includes Angular with TypeScript. Angular version: 15.1.0 Node version: 19.7.0 npm version: 9.5.1 However, I encountered an issue while running ng test: The error message displayed was as follows: ⠙ Generating browser application ...

Typescript enhances the functionality of the Express Request body

I need help with the following code snippet: const fff = async (req: express.Request, res: express.Response): Promise<void> => {...} How can I specify that req.body.xxx exists? I want it to be recognized when I reference req.body.xxx as a propert ...

How to smoothly transition between different components in Angular 2 with scroll actions

I am looking to dynamically change components based on user scrolling. Here is the behavior I want to achieve: When the user reaches the end of a component, the next component should load When the user reaches the top of a component, the previous compone ...

Guide on organizing the Object into a precise structure in Angular

I am looking to transform the current API response object into a more structured format. Current Output let temp = [ { "imagePath": "", "imageDescription": [ { "language": "en ...

What is the best way to disable a collapsed section in VS Code using comments?

I'm wondering how to properly comment out a "folded" section of code using VS Code. For instance, I want to comment out the collapsible region: if (a == b) { dance(); } I am familiar with the keyboard shortcut for folding regions: Ctrl + Shift + ...

Setting the "status" of a queue: A step-by-step guide

I created a function to add a job to the queue with the following code: async addJob(someParameters: SomeParameters): Promise<void> { await this.saveToDb(someParameters); try { await this.jobQueue.add('job', ...

Is it possible to retrieve cached data from React Query / Tan Stack Query outside of the React tree?

Currently, I am in the process of developing an error handler for our mobile app that will send user data to the server when unexpected errors occur. Previously, I was able to easily retrieve user information from a global state. However, after removing t ...