A guide on using tsc to build a local package

Unique Project Structure

I have a unique monorepo structure (utilizing npm workspaces) that includes a directory called api. This api directory houses an express API written in typescript. Additionally, the api folder relies on a local package called @myapp/server-lib, which contains TypeScript code.

The organization of directories is as follows:

.
├── api/
└── libs/
    └── server-lib/

Problem Statement

A challenge arises when building the api using tsc. The resulting build output includes require statements for @myapp/server-lib. However, upon deploying the API, the server fails to resolve @myapp/server-lib since it's not intended to be installed from the npm registry.

I'm seeking guidance on how to use tsc to compile @myapp/server-lib while removing the require statements and replacing them with references to the imported code. Essentially, I aim for functionality similar to what next-transpile-modules provides for Next.js.

In my attempts to address this issue, I experimented with utilizing TypeScript project references. However, this approach failed to compile the imported @myapp/server-lib. Additionally, I researched why I didn't come across this problem in my NextJS front-end (which resides in the same monorepo and is dependent on a slightly different local package). Ultimately, this led me to explore next-transpile-modules.

I would greatly appreciate any assistance or general tips regarding building a TypeScript project that utilizes a local package. Thank you!

UPDATE (12/28/2022)

I successfully resolved this issue by employing esbuild to build the api into a single file named out.js. This consolidated output includes all dependencies, including @myapp/server-lib.

The updated overall build process now follows these steps:

npx tsc --noEmit # checks types but does not output files
node build.js # leverages esbuild for project build

The corresponding build.js script is as follows:

const nativeNodeModulesPlugin = {
  name: 'native-node-modules',
  setup(build) {
    // If a ".node" file is imported within a module in the "file" namespace, resolve 
    // it to an absolute path and put it into the "node-file" virtual namespace.
    build.onResolve({ filter: /\.node$/, namespace: 'file' }, args => ({
      path: require.resolve(args.path, { paths: [args.resolveDir] }),
      namespace: 'node-file',
    }))

    // Files in the "node-file" virtual namespace call "require()" on the
    // path from esbuild of the ".node" file in the output directory.
    build.onLoad({ filter: /.*/, namespace: 'node-file' }, args => ({
      contents: `
        import path from ${JSON.stringify(args.path)}
        try { module.exports = require(path) }
        catch {}
      `,
    }))

    // If a ".node" file is imported within a module in the "node-file" namespace, put
    // it in the "file" namespace where esbuild's default loading behavior will handle
    // it. It is already an absolute path since we resolved it to one above.
    build.onResolve({ filter: /\.node$/, namespace: 'node-file' }, args => ({
      path: args.path,
      namespace: 'file',
    }))

    // Tell esbuild's default loading behavior to use the "file" loader for
    // these ".node" files.
    let opts = build.initialOptions
    opts.loader = opts.loader || {}
    opts.loader['.node'] = 'file'
  },
}

require("esbuild").build({
  entryPoints: ["./src/server.ts"], // server entrypoint
  platform: "node",
  target: "node16.0",
  outfile: "./build/out.js", // bundled single output file
  bundle: true,
  loader: {".ts": "ts"},
  plugins: [nativeNodeModulesPlugin], // handles native node modules (e.g., fs)
})
.then((res) => console.log(`⚡ Bundled!`))
.catch(() => process.exit(1));

Answer №1

Resolved (06/15/2023)

To tackle this issue, I successfully utilized the esbuild tool to compile the api code into a single consolidated file named out.js. By doing so, all necessary dependencies, such as @myapp/server-lib, were included in the final output.

The overall procedure for building the project has been modified and now follows this sequence:

npx tsc --noEmit # Verifies types without generating files
node build.js # Utilizes esbuild to construct the project

Let me provide you with a glimpse into the contents of the build.js script:

const nativeNodeModulesPlugin = {
  name: 'native-node-modules',
  setup(build) {
    // If an imported module within the "file" namespace contains a ".node" file,
    // it is resolved to its absolute path and placed in the "node-file" virtual namespace.
    build.onResolve({ filter: /\.node$/, namespace: 'file' }, args => ({
      path: require.resolve(args.path, { paths: [args.resolveDir] }),
      namespace: 'node-file',
    }))

    // Files encompassed by the "node-file" virtual namespace invoke "require()"
    // on the corresponding path from esbuild's ".node" file in the output directory.
    build.onLoad({ filter: /.*/, namespace: 'node-file' }, args => ({
      contents: `
        import path from ${JSON.stringify(args.path)}
        try { module.exports = require(path) }
        catch {}
      `,
    }))

    // Any ".node" file imported within a "node-file" namespace module is then placed
    // into the "file" namespace for esbuild's default loading behavior to handle.
    // The file path already stands as an absolute since we resolved it beforehand.
    build.onResolve({ filter: /\.node$/, namespace: 'node-file' }, args => ({
      path: args.path,
      namespace: 'file',
    }))

    // Specify that esbuild's default loading behavior leverages the "file" loader for these ".node" files.
    let opts = build.initialOptions
    opts.loader = opts.loader || {}
    opts.loader['.node'] = 'file'
  },
}

require("esbuild").build({
  entryPoints: ["./src/server.ts"], // Defines the server's entry point
  platform: "node",
  target: "node16.0",
  outfile: "./build/out.js", // Bundles everything into a single file
  bundle: true,
  loader: {".ts": "ts"},
  plugins: [nativeNodeModulesPlugin], // Handles native node modules (e.g., fs)
})
.then((res) => console.log(`⚡ Bundle Complete!`))
.catch(() => process.exit(1));

Regarding my server, I execute the start script specified in the package.json file through the command node out.js. Notably, there are absolutely no specified dependencies or devDependencies, as all of them have been bundled into the out.js file.

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

npm causing problems with babel-cli

While working on building a section of my library with Babel, I've encountered some issues when running Babel commands through npm. In my npm script named "build," the following commands are executed: { "prebuild": "rm -rf && mkdir dist", ...

Encountering an 'Authentication Failed' message while attempting to access the Azure DevOps Artifacts feed via npm, resulting in an E401 error

I am facing a challenge while trying to establish a connection to an Azure DevOps Artifacts feed as I keep encountering an E401 error. Interestingly, when I attempted the same on a different device, the connection was successful. Additionally, I have gone ...

The Next.js API call for /api/contact was successful, but no response was sent back, potentially causing delays in processing other requests

Encountering the issue of API resolved without sending a response for /api/contact, this may result in stalled request on a specific API route within Next.js. Despite successfully sending emails using sendgrid, I am unable to receive a response to handle e ...

What causes the tweets' IDs to be altered by axios when parsing the response from the Twitter search API?

I am currently utilizing axios to send a GET request to the Twitter search API in order to fetch recent tweets that utilize a specific hashtag. To begin with, I ran tests on the twitter search API via Postman and noticed that the id and id_str tweet statu ...

What Mac OSX command can you use in Typescript to input the quote character for multiline text?

Just starting out with Angular 2 and working through the official tutorial at https://angular.io/docs/ts/latest/tutorial/toh-pt1.html. I've realized that to use multi-line template strings (string interpolation), I have to use the ` mark. Any tips fo ...

Synchronize numerous PouchDB databases with a single CouchDB database

After reading the PouchDB documentation, I learned that sync occurs between a local database and a remote CouchDB database. Currently, I am working on developing a native application that includes a unique local database for each user (multiple databases) ...

Encountered a hiccup when trying to launch a duplicated Node.js project on my

Hello, unfortunately I am encountering yet another issue after cloning a project from GitHub onto my PC. When running the project, an error is displayed. Does anyone have a solution for this problem? The error message reads: "C:\Program Files (x86)&bs ...

Creating a Personalized Color Palette Naming System with Material UI in TypeScript

I have been working on incorporating a custom color palette into my material ui theme. Following the guidance provided in the Material UI documentation available here Material UI Docs, I am trying to implement this feature. Here is an excerpt from my cod ...

I'm having trouble getting npm, git, and node to work on my system

I'm having some issues with my Windows 10 machine. I attempted to install node and git, but every time I try to use git or npm, it just returns the user pointer back. WindowsPC MINGW64 /c/Angular $ git clone https://github.com/angular/quickstart my-a ...

TypeScript - create an Interface that must have either the error field or the payload field, but

My Action type has the following requirements: MUST have a field type {String} CAN include a field payload {Object<string, any>} CAN include a field error {Error} Constraints: IF it contains the field payload THEN it cannot contain the field er ...

React Typescript - Unexpected character ',' encountered at line 1005

Currently, I am utilizing Typescript within a React application that integrates with Graphql. Encountering an error: ',' expected.ts(1005) After researching possible solutions, most suggest that the issue might be due to using an outdated ve ...

Utilizing Node.js and Express alongside EJS, iterating through objects and displaying them in a table

Today I embarked on my journey to learn Node.js and I am currently attempting to iterate through an object and display it in a table format. Within my router file: var obj = JSON.parse(`[{ "Name": "ArrowTower", "Class" ...

Managing access permissions for the node_modules directory on macOS, Linux, and Windows operating systems

I've been working on nodeJS applications on my Mac, and to test them I'm using Parallels to run a virtual Windows machine with Windows 10. However, when I run npm install on my Mac, I encounter an "access denied" error for the node_modules folder ...

I found myself pondering the significance of the {blogs: blogs} in my code

app.get("/articles", function(req, res){ Article.find({}, function(err, articles){ if(err){ console.log("an error occurred!!!"); }else{ res.render("homepage", `{articles: articles}`); } }); I created this c ...

A step-by-step guide to effectively stubbing a dependency within an express middleware using a combination of express-validator, mocha, chai, sinon,

**Edit: Re-created with a simple example that works first: I have an example file and two modules. moduleA has a dependency called moduleB // moduleA.js const ModuleB = require('./moduleB'); function functionA() { return 20 + ModuleB.functio ...

I am unable to retrieve the complete set of data from Redis using Redis-OM and Next.js

In my application, I have a blog feature where I use Redis as the database and redis-om for managing it. The model for the blog in the app looks like this: const model_blog = new Schema(Blog,{ title : {type : "string"}, description : {t ...

Is it possible to combine TypeScript modules into a single JavaScript file?

Hey there, I'm feeling completely lost with this. I've just started diving into Typescript with Grunt JS and I could really use some assistance. I already have a Grunt file set up that runs my TS files through an uglify process for preparing the ...

When initiating a new process with exec() in nodejs, is it executed concurrently with the current process? How does nodejs manage this situation?

Given that nodejs operates on a single thread, how exactly does it go about creating and managing new processes? const exec = require('child_process').exec; exec('my.bat', (err, stdout, stderr) => { if (err) { console.er ...

Navigating multiple databases while managing a single model in Mongoose

I am looking to create a system with 50 arbitrary databases, each containing the same collections but different data. I want to use a single Node.js web application built with ExpressJS and Mongoose. Here is a simplified example: In this scenario: The ...

Is it possible to incorporate underscore.js with express4?

Is it possible to utilize express4 with underscore.js, specifically for printing the variable "name" in index.ejs? Are there any alternative methods within underscore.js that work well with node.js and Express4 capabilities? Note: underscore.js and Expre ...