Troub3leshooting Circular Dependency with Typescript, CommonJS & Browserify

I am currently in the process of transitioning a rather substantial TypeScript project from internal modules to external modules. The main reason behind this move is to establish a single core bundle that has the capability to load additional bundles if and when necessary. Additionally, I have the objective of ensuring that these bundles can also be executed on the server using NodeJS.

Initially, I attempted to build the core bundle using AMD and require.js, but encountered an issue with circular dependencies. Upon learning that such conflicts are common with require.js and that CommonJS is often recommended for large projects, I switched to that approach. However, even with browserify in place, I faced the exact same problem at the same point when executing the compiled bundle.

The project consists of approximately 10 base classes that heavily depend on each other, resulting in multiple circular dependencies. Despite efforts, it appears challenging to eliminate all instances of interdependence.

To clarify why eliminating circular dependencies seems unfeasible, consider the following simplified structure:

Triples consist of 3 Resources (subject, predicate, object)
Resource includes TripleCollections to track its usage in triples
Multiple functions within Resource rely on properties of Triple
Functions in Triple manage interactions with TripleCollections
TripleCollection features functions that utilize methods from Triple
Calling TripleCollection.getSubjects() returns a ResourceCollection
Invoking ResourceCollection.getTriples() provides a TripleCollection
Resources store objects of their triples in ResourceCollections
ResourceCollection employs various functions of Resource

After reviewing relevant discussions on SO (this source being particularly beneficial), it seems my options are limited to a few possibilities:

1) Consolidate base classes with circular dependencies into a single file.

This approach would result in one extensive file, requiring aliases for imports:

import core = require('core');
import BaseA = core.BaseA;

2) Utilize internal modules

While the core bundle functioned adequately (despite circular dependencies) using internal TypeScript modules and reference files, creating separate bundles for runtime loading will necessitate shims for all modules using require.js.


Although I am not fond of excessive aliasing, I intend to pursue option 1 initially. If successful, I can proceed with CommonJS and browserify, facilitating potential execution on the server via Node at a later stage. Should option 1 prove ineffective, option 2 will be considered as an alternative.

Q1: Are there any potential solutions I may have overlooked?

Q2: What constitutes the optimal setup for a TypeScript project featuring a core bundle that dynamically loads additional bundles (which support the core) upon request? Given the existence of circular dependencies, along with the requirement for cross-platform compatibility, what configuration would best serve these requirements?

Alternatively, am I attempting the implausible? :)

Answer №1

Put simply (and I like to keep things simple), if you find yourself in a situation where ModuleA and ModuleB are dependent on each other, they're not really functioning as separate modules. They may be in different files, but they're not behaving like true modules.

In this case, the classes have a high level of interdependence; you can't use one without requiring all of them. If restructuring the program to make the dependencies more one-way isn't feasible, it might be best to treat these classes as a single module.

If you decide to combine them into one module, you could still potentially break them down by reorganizing some of the dependencies. For instance, consider what commonalities exist between Triple and Resource. Is there a way to extract that into a shared class for both to depend on? Why does a Triple need knowledge of a TripleCollection? Perhaps it's due to hierarchical data representation. While not everything can be moved, eliminating any dependencies from the current design will help simplify it. For example, removing the two-way relationship between Triple and Resource.

You may not be able to make significant changes to the design at this time. In such cases, consolidating everything into one module can address the module loading concern and keep related code together, easing future modifications.

To sum up:

If dealing with a two-way module dependency, aim to convert it into a one-way dependency. Achieve this by rearranging code to establish one-way dependencies or grouping everything into a larger module that accurately reflects the coupling between modules.

My perspective on your options deviates slightly from those raised in your queries...

1) Attempt to refactor the code to minimize coupling and retain smaller modules.

If unsuccessful...

2) Consolidate all the core classes with circular dependencies into a single file.

I wouldn't recommend considering internal modules - external modules offer a superior approach to managing a complex program.

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

The error message "Type 'string' cannot be assigned to type 'Condition<UserObj>' while attempting to create a mongoose query by ID" is indicating a type mismatch issue

One of the API routes in Next has been causing some issues. Here is the code: import {NextApiRequest, NextApiResponse} from "next"; import dbConnect from "../../utils/dbConnect"; import {UserModel} from "../../models/user"; e ...

The ajv-based middy validator does not adhere to the specified date and time format

When it comes to validation, I rely on middy as my go-to package, which is powered by ajv. Below is an example of how I set up the JSON schema: serviceDate: { type: 'string', format: 'date-time' }, The structure o ...

How to dynamically insert variables into a separate HTML file while creating a VS Code extension

Currently working on a vscode extension, I'm facing an issue with containing the html in a string (like this: https://github.com/microsoft/vscode-extension-samples/blob/main/webview-view-sample/src/extension.ts). It leads to a large file size and lack ...

Implementing atob in Angular's interface

Looking for a solution to a token storage issue, my initial thought is that interfaces might be the way to go. Presently, my login code looks like this: import { Component } from '@angular/core'; import { FormBuilder } from '@angular/forms&a ...

Is there a way I can replace this for loop with the array.some function?

I am looking to update the filterOutEmails function in the following class to use array.some instead of the current code. export class UsertableComponent { dataSource: MatTableDataSource<TrialUser> createTableFromServer = (data: TrialUsers[], ...

Automatically divide the interface into essential components and additional features

Consider the following interfaces: interface ButtonProps { text: string; } interface DescriptiveButtonProps extends ButtonProps { visible: boolean, description: string; } Now, let's say we want to render a DescriptiveButton that utilize ...

Tips for building a diverse array of data types and effectively utilizing them based on their specific type in Typescript

Trying to store both custom types, Graphic and Asset, in the same array is proving to be a challenge. The goal is to access them and retain their individual type information. const trail: Array<Graphic | Asset> = []; for (let index = 0; index < t ...

When using create-react-app with JEST to run tests, TypeScript errors are not displayed

When I write incorrect TypeScript code in my project set up with create-react-app, running tests using npm test does not show any errors in the terminal. Is this normal behavior? It would be helpful to see these errors to avoid writing incorrect TypeScript ...

developing TypeScript classes in individual files and integrating them into Angular 2 components

We are currently putting together a new App using Angular2 and typescript. Is there a more organized method for defining all the classes and interfaces in separate files and then referencing them within angular2 components? import {Component, OnInit, Pi ...

Encountering overload error with Vue 3 and Axios integration

Currently utilizing Vue 3, Vite, Axios, and TypeScript. While my function functions properly in development, it throws an error in my IDE and during the build process. get count() { axios({ method: "get", url: "/info/count", h ...

Function Type Mapping

I am in the process of creating a function type that is based on an existing utility type defining a mapping between keys and types: type TypeMap = { a: A; b: B; } The goal is to construct a multi-signature function type where the key is used as a ...

Exploring the benefits of useContext in Expo router

Currently, I am working with the latest Expo-Router version which incorporates file-based navigation. To establish a universal language context in my application, I have utilized React's context API along with the useReducer hook. However, I am encoun ...

Convert all existing objects to strings

I have a type that consists of properties with different data types type ExampleType = { one: string two: boolean three: 'A' | 'Union' } Is there an easier way to define the same type but with all properties as strings? type Exam ...

Encountering an issue with creating an App Runner on AWS CDK

Attempting to deploy my application using App Runner within AWS via CDK. Utilizing the reference from https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-apprunner.Service.html. Upon deployment, encountering the following error: create_failed: R ...

When I attempt to return an object from a function and pass the reference to a prop, TypeScript throws an error. However, the error does not occur if the object is directly placed in

Currently, I have the following code block: const getDataForChart = () => { const labels = ['January', 'February', 'March', 'April', 'May', 'June', 'July']; const test = { ...

An error was encountered when attempting to define a file that contains both a class and an interface with an expected sem

Seeking guidance on creating a Typescript file with a class and interface: export class Merchant { constructor( public id: string, public name: string, public state_raw: string, public users: string, ) {} }; export interface MerchantL ...

The function parameter in Angular's ngModelChange behaves differently than $event

How can I pass a different parameter to the $event in the function? <div class='col-sm'> <label class="col-3 col-form-label">Origen</label> <div class="col-4"> <select ...

Importing configuration file in CRA Typescript with values for post-deployment modifications

I am currently working on a React app that utilizes Create React App and Typescript. My goal is to read in configuration values, such as API URLs. I have a config.json file containing this data, here's a sample snippet with placeholder information: { ...

Steps to eliminate the typescript template from create-react-app

Initially, I decided to incorporate TypeScript into my React project, which led me to run the command npx create-react-app my-app --template typescript. However, now I'm looking for a way to revert back and remove TypeScript from my setup. Is there a ...

Specify the return type based on specific parameter value

I'm facing a situation where I have two definitions that are identical, but I need them to behave differently based on the value of the limit parameter. Specifically, I want the first definition to return Promise<Cursor<T>> when limit is g ...