Transforming a sizeable Typescript undertaking into ES6 modules

I am faced with the challenge of transitioning a fairly large app (~650 files) that currently has a mix of ES6 modules and legacy global namespace internal modules and classes.

My goal is to convert everything to 100% ES6 modules.

Considering an Iterative Approach

To achieve this, I first need to convert all the global objects to ES6 modules by adding the "export" keyword.

However, as soon as I add "export", the global object becomes non-global and each file using that object will throw a compiler error "object not found" (i.e. Cannot read property 'xxx' of undefined).

To resolve this issue, I must add an "import" statement to the file, thereby switching it to an ES6 module which in turn removes the global status of all objects in that file, causing new "object not found" errors in other files.

In essence, this incremental approach seems to have its drawbacks.

Potential Simpler Solution:

  1. Go through all ts files
    1. Add export to all top-level classes, modules, and variables programmatically.
    2. Compile these into a list.
  2. Create a barrel (globalBarrel) file that re-exports all the exports gathered in the previous step.
  3. Revisit all ts files
    1. Add
      import {list, of, symbols, exported, by, the, barrel,file} from "./globalBarrel"
      to each file.
  4. Attempt a clean compilation?
    1. Expect some circular dependency issues which need to be resolved.
  5. For future file edits, utilize the vsCode "Organize Imports" command to eliminate unnecessary imports.

As quoted by @jfriend000:

Trying to automatically convert non-modular code into modules will likely result in chaos.

Transitioning everything to modules at once could simplify the true modularization process.

Questioning the Best Approach

Is this method the most effective? Any suggestions?

Answer №1

To progress towards this goal, I must first transform all the global objects into ES6 modules by introducing the "export" keyword.

However, once the "export" is added, the global object loses its global status, resulting in a compiler error in every file that utilizes that object; specifically, a "Cannot read property 'xxx' of undefined" error.

To address this issue, an "import" statement needs to be incorporated into the file, effectively converting it into an ES6 module. Consequently, this action further decentralizes all the objects within that file, leading to additional "object not found" errors in other files.

Indeed, the key to breaking free from this frustrating cycle is to assign each converted file to a global variable whenever you convert it into a module. This will allow referencing from non-module files without necessitating their conversion. Although this approach involves some boilerplate code, a proposed solution (found here) could potentially streamline this process.

Possibly, your current setup employs a bundling tool to guarantee that all non-module files are loaded and contribute to a unified global scope for mutual access to definitions. Begin by creating a file containing the following script and configure your bundler to prioritize its execution:

namespace Modules {
    // Ensure `Modules` is established during runtime.
    (() => {})();
}

Assuming you have the initial setup as follows:

// a.ts
namespace A {
    export const a = 42;
}

// b.ts
namespace B {
    console.log(A.a);
}

You can transition to the modified version below:

// a.ts
export const a = 42;

import * as A_ from "./a";
declare global {
    namespace Modules {
        export import A = A_;
    }
}
Modules.A = A_;

// b.ts
namespace B {
    console.log(Modules.A.a);
}

Subsequently, b.ts will gain the capability to access Modules.A with complete type information without being converted into a module itself (barring any load order complications with the bundler).

Answer №2

Do you think this approach is the most effective? Any ideas for improvement?

I have my doubts about the viability of this method, and I strongly believe it is far from the optimal solution.

Attempting to transform non-modular code into modules through automated processes will likely result in a chaotic outcome. While it may be possible to accomplish, the end result will only complicate matters further. Instead of enhancing your architecture, you will essentially be forcing a non-modular structure into a tangled web of interdependent modules. Complexity will increase rather than decrease.

A more sensible strategy would involve gradually redesigning specific areas of functionality to truly become modular. Create interfaces for different functionalities, package them as modules, and then systematically transition all users to import these interfaces. This can be done step by step, one area at a time. There's no need to overhaul the entire system at once and then struggle to reassemble everything.

Eradicating shared global variables requires a fundamental redesign of the system. Simply consolidating globals into a module that every part of the code must import does not enhance the design. If anything, it exacerbates the intertwined and dependent nature of the code.

The incremental modular redesign approach allows for a more manageable process. By focusing on one functional area at a time, you can gradually untangle the complexity without creating a massive mess. If the code heavily relies on global variables scattered throughout, significant redesign efforts will be necessary. This may involve creating data-containing objects or transferring global data into relevant modules with exported accessors.

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 Vue Router hooks are not being activated within the component when utilizing Typescript

I've been pondering this issue for quite some time. Despite my extensive search efforts, I am still unable to figure out why the route-hooks are not working in my component: 1. The component is being loaded from RouterView: <router-view class="z1 ...

Retrieve the final variable in an Observable sequence

In my code, I have a variable called 'messages' which stores messages from a conversation: messages: Observable<Message[]>; To populate the 'messages' variable, I do the following: const newMessage = new Message(objMessage); ne ...

Utilizing TypeScript to spread properties onto a component and destructure them from within components

I'm trying to optimize my use of props spreading and destructuring when working with components. Currently, I spread my props in this manner: <RepositoryItem {...node} /> Then, within the component, I destructure the props like so: interface ...

Having trouble accessing functions within the webpack bundle

As someone new to the world of JS library development, I have embarked on a journey to achieve the following goals: Creating a library with TypeScript Generating a bundle using webpack5 Publishing the library to npm Utilizing the library in other projects ...

Error message: The module cannot be found by the compiler. This issue is resolved when using Visual Code and Definitely

As a newcomer to Typescript, I'm encountering an issue that I believe might have a simple solution. After installing type definitions for a JavaScript library, the compiler is throwing an error that has me stumped. Working on a React Typescript projec ...

Exploring Angular2's DOMContentLoaded Event and Lifecycle Hook

Currently, I am utilizing Angular 2 (TS) and in need of some assistance: constructor(public element:ElementRef){} ngOnInit(){ this.DOMready() } DOMready() { if (this.element) { let testPosition = this.elemen ...

Is there a method to reduce the requirement for if-conditions in this situation?

After revisiting this previous inquiry, I successfully implemented multiple filters on my observable, based on the user's chosen filters. However, the issue arises from the uncertainty of whether a filter is applied and the varying behavior of each f ...

What is the best way to retrieve app.state in a Remix project when running a Cypress test?

One way Cypress can expose an app's state to the test runner is by using the following approach in React: class MyComponent extends React.Component { constructor (props) { super(props) // only expose the app during E2E tests if (window.C ...

Exploring nested promises in TypeScript and Angular 2

I have a method called fallbackToLocalDBfileOrLocalStorageDB, which returns a promise and calls another method named getDBfileXHR, also returning a promise. In the code snippet provided, I am unsure whether I need to use 'resolve()' explicitly o ...

The functionality to generate personalized worldwide timezone pipe is not functioning

I'm completely new to Angular and I've been working on creating a custom pipe for adjusting timezones. The idea is to allow users to select their preferred timezone and have the offset applied accordingly. To start, I created a file called timez ...

I'm having trouble with one of my filter pipes not displaying any results. Can anyone help me troub

I have recently included a new filter for DL, but it seems that the results are not showing up as expected. Any ideas on what changes I should implement? <div class="form-group float-left mr-4"> <strong>DL</strong> <br /> ...

Error message while attempting to update devextreme-datagrid: "Received HTTP failure response for an unknown URL: 0 Unknown Error"

Need help with updating the devextreme-datagrid. Can anyone assist? lineController.js router.put("/:id", (req, res) => { if (!ObjectId.isValid(req.params.id)) return res.status(400).send(`No record with given id : ${req.params.id}`); ...

Differing preferences for indentation styles can lead to conflicting prett

My eslint setup is as follows: { "env": { "es2020": true, "jest": true }, "extends": [ "eslint:recommended", "plugin:react/recommended", "plugin:import/recommended&q ...

Tips for aligning the types of objects transmitted from a Web API backend to a React/Redux frontend

After creating a backend for my app using .net, I now have CRUD operations available to me. When performing a POST action, the response includes an entire new item object: {"Id":"7575c661-a40b-4161-b535-bd332edccc71","Text":"as","CreatedAt":"2018-09-13T15 ...

What is the method for incorporating sorting into a mat-list?

I've searched for various solutions, but none seem to work with mat-list. It's crucial for me because mat-list is the only solution where drag&drop functionality works (I always face this issue with mat-table in tables and I can't find a ...

Retrieving the selected date from mat-datepicker into a FormControl

When creating a POST request to an API, I encountered an issue with the mat-datepicker field as it throws an error when inside the ngOnInit() call (since nothing is selected yet). Other fields like name, email, etc. work fine, but extracting a value from t ...

Encountered an issue during the transition from Angular 7 to Angular 9

After following the advice in the second response of this discussion, I successfully upgraded to Angular 9. However, I am now encountering an issue in the browser console when running my project. https://i.sstatic.net/esAXf.png Package.json "dependencie ...

exit out of React Dialog using a button

I have a scenario where I want to automatically open a dialog when the screen is visited, so I set the default state to true. To close the dialog, I created a custom button that, when clicked, should change the state to false. However, the dialog does no ...

Distinguish between two varieties using parameters

Let's consider the following simplified example: type Reference<T extends {identifier: string}> = T['identifier'] In this type, TypeScript recognizes that it is a reference to an object where the identifier property is of type string ...

The Typescript Select is displaying an incorrect value

Here's the code snippet I've been working with: <select #C (change)="changeSelect(zone.id, C.value)"> <option *ngFor="let town of townsLocal" [attr.value]="town.data" [attr.selected]="town.data === zone.town && 'selected& ...