Creating packages for multiple Typescript projects that rely on a shared local module

I am currently developing a series of VSTS extensions, with each extension being its own independent Node project complete with its own package.json file and node_modules folder. The structure of the folders is as follows:

 - MyExtension
   - package.json // containing all development dependencies
   - tslint.json
   - Tasks
     - tsconfig.json
     - Common
       - common.ts    // contains shared functionality across tasks
       - package.json // includes runtime dependencies for all projects
     - My1stTask
       - package.json // holds production dependencies
       - task.ts      // implementation of the task
     - ...
     - ...
     - My6thTask
       - package.json // holds production dependencies
       - task.ts      // implementation of the task

In order to fully separate VSTS build tasks, I have been duplicating the contents of the Common project into each task and then running tsc to convert them all to JavaScript.

While this method has worked, it requires constant copying of content from Common for testing purposes.

I attempted to use local file references by adding a dependency in each task's package.json to file:../common, which functions during development but doesn't include the common module in the task after generating the extension.

Coming from a C# background rather than Node development, I have searched extensively for a solution that integrates well with vsts-extensions.

  • npm pack does not seem to function as expected, as the extension demands that all files be present.
  • package.json/bundleDependencies shows promise but does not bundle the local file reference.
  • ///<reference path="../common/common.ts"/>
    works nicely for editing but fails to run after building the extension.
  • Project reference with prepend does not work, as build tasks require the commonjs module resolver. Systems and AMD are unable to load modules, and prepend only functions with the latter.

Is there a way to seamlessly make this setup work without resorting to bower or grunt, and simply ensuring that each MyXthTask has a copy of the local common module in their node_modules folder?

Answer №1

Attempting @matt-mccutchen's method proved unsuccessful for me when working with VSTS build tasks as they necessitate the use of commonjs:

"compilerOptions": {
  "module": "commonjs",
  "target": "es6", 

Fortunately, I discovered a workaround that suits my needs.

I created a tsconfig.json file in the Tasks directory to establish my default configurations and incorporate files from the Common library:

{
  "compileOnSave": true,
  "compilerOptions": {
    "module": "commonjs",
    "target": "es6",
    "sourceMap": true,
    "strict": false,
    "strictNullChecks": false,
    "removeComments": true
  },
  "files": [
    "./Common/uuidv5.d.ts",
    "./Common/Common.ts"
  ]
}

For each task, I then crafted a separate tsconfig.json setting the output folder to the project's current directory and inheriting configurations from the main tsconfig.json in the Tasks folder:

{
  "extends": "../tsconfig.json",
  "compilerOptions": {
      "outDir": "./", 
      "sourceRoot": "./"
  },
  "files": [
      "InstallExtension.ts"
  ]
}

This setup results in:

 - MyExtension
   - package.json // includes dev-dependencies
   - tslint.json
   - Tasks
     - tsconfig.json   // Default including Common
     - Common
       - common.ts     // shared functionality across tasks
       - package.json  // runtime dependencies for Common
       - tsconfig.json // build config for common files, inherits from ..\Task\tsconfig.json
     - My1stTask
       - package.json  // prod-dependencies for the task
       - task.ts       // task implementation
       - tsconfig.json // build config for task, inherits from ..\Task\tsconfig.json
     - ...
     - My6thTask
       - package.json // prod-dependencies
       - task.ts      // task implementation
       - tsconfig.json // build config for task, inherits from ..\Task\tsconfig.json

During task compilation, the following structure is produced:

     - My6thTask
       - Common
         - Common.js   // Compiled common
       - My6thTask
         - task.js     // Compiled task
       - package.json  // prod-dependencies
       - task.ts       // task implementation
       - task.json     // defines task UI
       - tsconfig.json // build configuration for task

Included in task.ts was:

///<reference path="../Common/Common.ts"/>
import * as common from "../Common/Common";

The execution handler in task.json was adjusted to point to the new location:

  "execution": {
    "Node": {
      "target": "InstallExtension/InstallExtension.js", // changed from: InstallExtension.js
      "argumentFormat": ""
    }
  }

These modifications have proven successful :D. Combined with utilizing glob-exec, my clean build time has been reduced to under a minute:

"initdev:npm": "npm install & glob-exec --parallel --foreach \"Tasks/*/tsconfig.json\" -- \"cd {{file.dir}} && npm install\"",
"compile:tasks": "glob-exec \"Tasks/*/tsconfig.json\" -- \"tsc -b {{files.join(' ')}}\"",
"lint:tasks": "glob-exec --parallel --foreach \"Tasks/*/tsconfig.json\" -- \"tslint -p {{file}}\"",

Answer №2

For those looking to streamline their tasks, consider utilizing project references with the prepend option. This method allows you to consolidate your output into a single file, provided that you have a compatible module loader.

If your project requires multiple-file outputs, check out this question for useful TypeScript insights and potential workarounds.

Answer №3

I had trouble with project references, so I decided to use webpack with ts-loader for building my projects.

I organized all the TypeScript code under a single root directory and used a single tsconfig file like this:

ts
 -core
 -plugin1
 -plugin2

With webpack 3.10, it is now possible to have multiple output configurations using a single config file:

const path = require("path");

const commonTsRule = {
  test: /\.tsx?$/,
  use: "ts-loader",
  exclude: /node_modules/,
};

const commonConfig = {
  devtool: "inline-source-map",
  mode: "development",
  resolve: {
    extensions: [".tsx", ".ts", ".js"],
  },
};

module.exports = [
  {
    ...commonConfig,
    entry: `${path.resolve(__dirname, "ts")}/plugin1/index.ts`,
    output: {
      filename: "bundle.js",
      path: path.resolve(__dirname, "build/plugin1/js"),
    },
    module: {
      rules: [
        {
          ...commonTsRule,
          // customize rule if needed
        },
      ],
    },
  },
  {
    ...commonConfig,
    entry: `${path.resolve(__dirname, "ts")}/plugin2/index.ts`,
    output: {
      filename: "bundle.js",
      path: path.resolve(__dirname, "build/plugin2/js"),
    },
    module: {
      rules: [
        {
          ...commonTsRule,
          // customize rule if needed
        },
      ],
    },
  },
];

This setup allows each project to be built from its own entry point to its designated output destination.

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

Leave the Private NPM Repository

Alright, maybe this is a silly question but I can't seem to locate the information anywhere and I'm sure I'm not the only one who has wondered... I've been relying on my company's internal NPM Repo for quite some time now that I&a ...

Switching a REACT NATIVE js file to an Android Activity is a process that involves integrating

Can anyone assist me with the process of transitioning from a REACT NATIVE Activity to a native android activity when a button is clicked in my JS file? I have scoured numerous resources but still need guidance on this. Your help would be greatly appreci ...

Tips for maintaining the menu state following a refresh

Is there a way to save the menu state when pressing F5? I'm looking for a similar functionality as seen on the Binance website. For example, clicking on the Sell NFT's submenu and then refreshing the page with F5 should maintain the menu state on ...

What could be the reason for my webpack project to encounter build failures when cloned from a GitHub repository?

I encountered an issue while working on a project stored in a GitHub repository. Initially, I had an outdated version of the project on one machine and decided to update it using a different machine. Before updating, I deleted the old version folder from ...

Is it more efficient to have deps.ts per workspace or shared among workspaces?

Currently, I am in the process of setting up my very first monorepo for a Deno-based application. In this monorepo, the workspaces will be referred to as "modules" that the API code can import from, with each module having its own test suite, among other t ...

Encountering issues with git while trying to execute 'npm

I'm encountering issues with a command that won't work. I reached out to the steroids.js forums, but it seems like this is more of a general git problem. All repositories with "git@" at the beginning of the URL are failing. command npm install ...

The installation of AngularJS libraries via npm was unsuccessful

After installing mean.io and running sudo npm install, I followed the commands in sequence: sudo npm install -g meanio mean init yourNewApp cd yourNewApp sudo npm install -g bower sudo npm install The process was supposed to download and install AngularJ ...

Encountering an issue when attempting to execute the Jenkins job for running Cypress automation tests: The cypress npm library has been successfully installed, yet the Cypress binary file

Encountered an error while executing the Jenkins job integrated for Cypress test cases. Running as SYSTEM Building in workspace C:\Users\Gaurav\CypressAutomation [CypressAutomation] $ cmd /c call C:\Windows\TEMP\jenkins1604066 ...

Error Found: Unexpected Colon (:) in Vue TypeScript File

Important Update: After thorough investigation, it appears that the issue is directly related to the boilerplate being used. As a temporary solution, it is recommended not to extract the TypeScript file but keep it within the .vue file for now. In a sim ...

Troubleshooting image upload issues with AWS S3 in Next.js version 13

I encountered a consistent problem with using the AWS SDK in NextJS to upload images. I keep getting error code 403 (Forbidden). Could there be other reasons why this error is occurring besides the accessKeyId and secretAccessKey being invalid? Below is my ...

Distribute your SolidJS Typescript components on npm

Recently, I developed a SolidJS TypeScript UI component and successfully published it to the npm registry. The project structure is organized as follows: |-app |-index.html |-src |-index.tsx |-App.tsx |-utils |- ... |-com ...

Updating .babelrc to include the paths of JavaScript files for transpilation

Using Babel, I successfully transpiled ES6 JavaScript to ES5 for the project found at I'm currently stuck on updating my .babelrc file in order to automatically transpile a specific ES6 file to a particular ES5 file. Can someone guide me on what cod ...

Error encountered during compilation while attempting to import a JSON file in Angular 7

One great aspect of angular 7 is its compatibility with typescript 3.1: https://alligator.io/angular/angular-7/ I have made the following changes to the tsconfig.json file, within the 'compilerOptions' section: "resolveJsonModule": true, "esMo ...

Error in Angular 7: params.map function is undefined

When attempting to launch my Angular 7 application with ng serve, I suddenly encountered the following error: ERROR in params.map is not a function I am unsure of the origin of this error as ng isn't providing much information. I have attempted to ...

propagate the amalgamation of tuples as an argument

I'm working with a function that returns a union type of tuples. I need to pass this return value to another function that can accept all its forms using the spread operator .... type TupleUnion = readonly [number, number] | readonly [number, number, ...

Steps for modifying the look of a button to display an arrow upon being clicked with CSS

Looking to enhance the visual appearance of a button by having an arrow emerge from it upon clicking, all done through CSS. Currently developing a React application utilizing TypeScript. Upon clicking the next button, the arrow should transition from the ...

Executing invisible reCAPTCHA2 in Angular 6: A step-by-step guide

Recently, I have been trying to implement an invisible captcha into my website. In order to achieve this, I turned to the guidance provided by Enngage on their ngx-captcha GitHub page: https://github.com/Enngage/ngx-captcha While following the instruction ...

Heroku is unable to locate a privately hosted NPM package

UPDATE: It appears that the issue is linked to one of my proprietary packages. Within my project, which was constructed using heroku-create-react-app, I am utilizing a private module. This snippet shows my .npmrc file located in the root directory of the ...

Access a .docx file on my device by utilizing the Microsoft JavaScript API

In my current project, I am developing a Microsoft Word add-in using TypeScript, React, and the Word API. One of the key features of this add-in will allow users to open a document located on their computer, such as "C:\Test\Test.docx", with just ...

The error message "Property 'map' is not found on type 'User' in React with typescript"

I am currently experimenting with React using TypeScript. I have set up useState with an object but I am encountering issues trying to use the map function with that object. Below is the error message I am facing: Property 'map' does not exist ...