Tips for implementing absolute import paths in a library project

In my workspace, I have a library with two projects: one for the library itself and another for a test application.

├── projects
    ├── midi-app
    └── midi-lib

Within the workspace's tsconfig.json file, I set up paths for @app and @lib:

"paths": {
  "@app/*": ["projects/midi-app/src/app/*"],
  "@lib/*": ["projects/midi-lib/src/lib/*"],
  "midi-lib": [
    "dist/midi-lib"
  ],
  "midi-lib/*": [
    "dist/midi-lib/*"
  ]
}

In addition, there is a

projects/midi-lib/tsconfig.lib.json
file that extends the main tsconfig.json settings:

"extends": "../../tsconfig.json",

The library has a public-api.ts file which includes:

export * from './lib/midi-lib.module';

I can successfully use this library in the test application. However, when I try importing it as a Node module into a separate client application within a different workspace, I encounter numerous errors related to unknown paths like Can't resolve '@lib/...'

How can I make sure the library paths are accessible in a client application? Or how can I handle the translation of these paths during the library packaging process?

Additionally, I am curious why the extension is not the other way around - why doesn't the main tsconfig.json file extend the

projects/midi-lib/tsconfig.lib.json
file instead?

Here is my approach to packaging and using the library:

To package the library, I add the following scripts to the parent package.json file:

"copy-license": "cp ./LICENSE.md ./dist/midi-lib",
"copy-readme": "cp ./README.md ./dist/midi-lib",
"copy-files": "npm run copy-license && npm run copy-readme",
"build-lib": "ng build midi-lib",
"npm-pack": "cd dist/midi-lib && npm pack",
"package": "npm run build-lib && npm run copy-files && npm run npm-pack",

After adding these scripts, I run the command: npm run package

Then, I install the dependency by running:

npm install ../midi-lib/dist/midi-lib/midi-lib-0.0.1.tgz

And import the module in the application module. In the app.module.ts file:

import { MidiLibModule } from 'midi-lib';
@NgModule({
  imports: [
    MidiLibModule

Lastly, I insert the component in a template:

<midi-midi-lib></midi-midi-lib>

Upon installing the library in a client application, I notice various .d.ts files under the node_modules/midi-lib directories:

├── bundles
├── esm2015
│   └── lib
│       ├── device
│       ├── keyboard
│   ...
│   └── upload

One example is the lib/service/melody.service.d.ts file:

import { SoundtrackStore } from '@lib/store/soundtrack-store';
import { ParseService } from '@lib/service/parse.service';
import { CommonService } from './common.service';
export declare class MelodyService {
    private soundtrackStore;
    private parseService;
    private commonService;
    constructor(soundtrackStore: SoundtrackStore, parseService: ParseService, commonService: CommonService);
    addSomeMelodies(): void;
    private addSoundtrack;
    private generateNotes;
}

This file references the @lib path mapping, which is not recognized in the client application.

I also tried using the baseUrl property as a workaround, but it didn't solve the issue since the baseUrl value was not specified during library installation.

Why does running npm run package to package the library not resolve the paths mappings?

Answer №1

The establishment of the paths mapping in your tsconfig.json is specifically for compile-time purposes only. This does not impact the code generated by the TypeScript compiler itself. This could be the reason why you are experiencing an error at runtime. A suggestion was made to the TypeScript project, proposing that tsc should automatically translate module paths in the emitted code to align with the established mapping under paths. The response from the TS devs indicated that tsc is functioning as intended and the solution would require configuring a module loader that can perform a similar mapping at runtime as defined by paths.


Based on the details provided regarding your situation, here's what I believe you should consider doing:

If midi-app is primarily a test application without distribution requirements, you should be able to continue using the existing paths mapping without encountering issues. (Since there have been no mentioned problems while running this app, it appears that your tools are handling the runtime issue seamlessly.)

For midi-lib, it might be best to avoid relying on mappings set by paths and opt for relative paths instead. This approach ensures smoother consumption by others since this library is designed for external use. Consumers utilizing Webpack, Rollup, SystemJS, and similar tools will need to configure mappings specific to your library alongside their configurations.

Furthermore, the complexity of required configuration could escalate depending on how and where consumers utilize your library. While a global mapping may suffice when your package is the sole instance requiring such a mapping, scenarios involving multiple libraries necessitate intricate configurations to designate distinct mappings accordingly.

While the discussion has focused on module resolution during bundling or runtime loading, another consideration involves establishing specialized configurations within tsc compilation files which include .d.ts files.

By leveraging relative paths in your code, consumers of your library won't face challenges implementing unique configurations tailored to accommodate your library's requirements.


An exception to this strategy may apply if your intention is to publish your library as midi-lib. In such cases, modifying your paths map to reference midi-lib/* rather than @lib/* could be advantageous:

"midi-lib/*": ["projects/midi-lib/src/*"],

(Take note that the @ symbol holds no significance in relation to TypeScript. If your package warrants installation within a scope like @midi-project/midi-lib, ensure your tsconfig.json mapping includes the appropriate scope: "@midi-project/midi-lib/*": ...)

The objective is to establish a mapping enabling imports within your project just as consumers would import individual modules from your project. For instance, if a consumer fetches the ParseService module through

import { ParseService } from "midi-lib/lib/service/parse.service"
, prioritize employing the same import format wherever applicable. Ensuring uniform path usage between compile time and runtime (or bundling) operations streamlines functionality. While the translation from paths occurs during compile time via tsc, Node's module resolution algorithm (or its equivalents such as Webpack or Rollup) handle translations during runtime or bundling.

The reduction in manual efforts varies based on selected naming conventions and your library's structure.


In theory, a post ng build step could potentially substitute occurrences of @lib in module names within the output generated by ng build. However, challenges exist:

  1. The process isn't a simple tool activation; familiarity with tools such as rollup along with custom configuration creation is essential.

  2. Ideally, a readily available tool capable of transforming the necessary .d.ts files doesn't currently exist, necessitating personalized tool development.

  3. Adapting Angular AOT compilation metadata affected by the switch requires custom interventions due to current tool unavailability for such modifications.

  4. Potential complications emerge from future Angular version updates altering AOT compilation metadata formats or introducing new metadata types that demand adjustments. Personal encounters reflect similar upheavals following Angular upgrades affecting AOT compilation metadata procedures.

Answer №2

Like others mentioned before, typescript does not alter your @app and @lib imports. I have encountered the same issue while attempting to utilize absolute paths in a library package. The solution lies in preparing your library for publication using rollup or a similar tool.

Rollup offers numerous plugins, and while I won't delve into the complete setup process, what you require is a plugin that will rewrite your imports. It appears that this particular plugin accomplishes that: https://github.com/bitshiftza/rollup-plugin-ts-paths

In addition to this plugin, the rest of your rollup configuration will likely involve utilizing rollup-plugin-node-resolve, rollup-plugin-commonjs, and a typescript plugin (rollup-plugin-typescript), or alternatively exploring the newer approach of using babel. You can refer to various tutorials as there are prominent libraries coded in typescript that use rollup for code packaging (such as React).

Wishing you productive coding sessions!

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

Angular 2 TypeScript: Accelerating the Increment Number Speed

I'm working with a function in Angular 4 that is triggered when the arrow down key is pressed. Each time the arrow down key is hit, the counter increments by 1. In this function, I need to run another function if the counter reaches a certain speed. ...

Tips for managing a group of checkboxes in Angular 2 RC5

My task involves creating a form where users can edit their magazine subscriptions. Here is the code snippet I am working with: Component: export class OrderFormComponent { subscriptions = [ {id: 'weekly', display: 'Weekly new ...

Maintaining the order of subscribers during asynchronous operations can be achieved by implementing proper synchronization

In my Angular setup, there is a component that tracks changes in its route parameters. Whenever the params change, it extracts the ID and triggers a function to fetch the corresponding record using a promise. Once the promise resolves, the component update ...

Tips for setting up chrome-app typings in Typescript 2

I am looking to eliminate the typings in our Typescript project. After successfully removing most typings dependencies with Typescript 2, I'm left with just one for chrome-app: https://github.com/uProxy/uproxy/compare/master...fortuna:master When usi ...

The Sequence of Import Statements in Angular 2

The Angular Style Guide provides recommendations on Import line spacing: It is suggested to include one empty line between third-party imports and application imports. Consider arranging import lines in alphabetical order based on the module. For destruc ...

Tips for continuously running a loop function until retrieving a value from an API within a cypress project

Need help looping a function to retrieve the value from an API in my Cypress project. The goal is to call the API multiple times until we receive the desired value. let otpValue = ''; const loopFunc = () => { cy.request({ method: &ap ...

Utilizing localstorage data in angular 2: A comprehensive guide

Is there a way to utilize data stored in localstorage for another component? This is what the localstorage service looks like: localStorage.setItem('currentUser', JSON.stringify({ username: username, token: success, res: res.data })); I am inte ...

Resolving "SyntaxError: Unexpected identifier" when using Enzyme with configurations in jest.setup.js

I'm currently facing an issue while trying to create tests in Typescript using Jest and Enzyme. The problem arises with a SyntaxError being thrown: FAIL src/_components/Button/__tests__/Button.spec.tsx ● Test suite failed to run /Users/mika ...

Guide to Display chat.html Overlay on home.html Using Ionic 2

In my Ionic project, I have two pages: home.html chat.html I am looking to display chat.html over home.html at the bottom right as a chat window. How can I accomplish this? I have attempted to illustrate what I envision in an image: ...

I am having trouble reaching the _groups attribute in angular/d3js

I am encountering an issue when trying to access the "_groups" property in my code: function getMouseDate(scale){ var groupElement = d3.select("#group")._groups[0][0] var xCoordinate = scale.invert(d3.mouse(groupElement)[0]); co ...

Trigger an Angular2 component function from an HTML element by simply clicking a button

I'm just starting out with TypeScript and Angular2 and encountering an issue when trying to call a component function by clicking on an HTML button. When I use the **onclick="locateHotelOnMap()"** attribute on the HTML button element, I receive this ...

What is the best way to remove a specific row from an Angular Material table that does not have any filters

Here is my samplepage.component.ts code: import { Component } from '@angular/core'; @Component({ selector: 'app-batchticketvalidation', templateUrl: './batchticketvalidation.component.html', styleUrls: ['./batchtic ...

How do I manage 'for' loops in TypeScript while using the 'import * as' syntax?

When working with TypeScript, I encountered an issue while trying to import and iterate over all modules from a file. The compiler throws an error at build time. Can anyone help me figure out the correct settings or syntax to resolve this? import * as depe ...

Best practice for encapsulating property expressions in Angular templates

Repeating expression In my Angular 6 component template, I have the a && (b || c) expression repeated 3 times. I am looking for a way to abstract it instead of duplicating the code. parent.component.html <component [prop1]="1" [prop2]="a ...

Can you explain the concept of TestBed in Jasmine?

Recently, I have started using Jasmine with Angular 2 and have encountered an issue while working with the TestBed object in my test cases. The error message reads as follows: Please call "TestBed.compileComponents" before your test. Can anyone advise on ...

Is there a way to retrieve the object property within the subscribe function in order to display the HTML content?

Is there a way for me to update the HTML using the properties obtained within .subscribe? I am aware that .subscribe is asynchronous and therefore returns an undefined value initially, but how can I ensure it waits until the value is resolved? Currently, I ...

angular exploring the ins and outs of implementing nested child routing

Within my app-routing.module.ts file, I define the loading of child components like so: { path: 'account', loadChildren: () => import('./components/account/account.module').then((esm) => esm.AccountModule) }, Followin ...

Unable to utilize Google Storage within a TypeScript environment

I'm encountering an issue while attempting to integrate the Google Storage node.js module into my Firebase Cloud functions using TypeScript. //myfile.ts import { Storage } from '@google-cloud/storage'; const storageInstance = new Storage({ ...

Understanding how to infer the type of a function when it is passed as an argument

Looking at the images below, I am facing an issue with my function that accepts options in the form of an object where one of the arguments is a transform function. The problem is that while the type of the response argument is correctly inferred for the e ...