What is the best way to ensure ergonomic typing for a module that exports an object with a default class?

When I say "ergonomic," I mean something that doesn't force users to utilize the syntax of

InstanceType<typeof mod["Foo"]>
.

Currently, I am working on a .d.ts file for a JavaScript source file. The plan is to convert this JS file into TypeScript in the future, although it cannot be done at present.

The structure of the file is as follows:

// module.js
class Foo {
    sayFoo(){}
}

export default {
    Foo
};

With 'tsc,' the generated .d.ts looks like this:

// module.d.ts
declare class Foo {
    sayFoo(): void;
}
declare const _default: {
    Foo: typeof Foo;
};
export default _default;

The user has to go through some convoluted steps to access the class type:

import mod from "./module"

function takesFoo(foo: InstanceType<typeof mod["Foo"]>) {
    foo.sayFoo();
}

However, if I slightly manipulate the types using namespace:

export default NS;
// module.d.ts
declare namespace NS {
    export class Foo {
        sayFoo(): void
    }
}

then the user gets a much smoother interface at the type level!

import mod from "./module";
function takesFoo(foo: mod.Foo) {
    foo.sayFoo();
}

I could opt for using namespace, but then we'll face issues when the JS file is eventually converted to TS. Our options at that point would be:

  • (A) Avoid converting the library's entry point to TS altogether. Develop most of the library in TS and use a small JS file with a .d.ts to package the API. This approach would eliminate any assurances that we are adhering to our public API! Furthermore, it could lead to confusion.

  • (B) Enforce the complex type-level API onto users. Perhaps mitigate this by exporting certain interfaces and educating users about the dual nature of TS class types.

Is there another option I may have overlooked or misunderstood regarding type-level features in TS?

Answer №1

UPDATE: After further consideration, the solution may be much simpler than initially thought. However, my recommendation on utilizing default exports remains valid. It appears that this code may not actually execute as expected (though testing in a basic create-react-app environment shows it works fine).

When using your existing default export, the following code compiles successfully:

import { Foo } from "./module"

const x = new Foo();

function f(y: Foo) {
   y.sayFoo();
}

f(x);

Original response below:

Have you considered adding export before class Foo { to allow users to import it more easily?

This way, users can simply do:

import { Foo } from "./module";

and directly use Foo:

function takesFoo(foo: Foo) {

Alternatively, if Foo is the only item being exported and you want it as the default export, it would be best to export it individually rather than wrapping it in an object:

export default Foo; 

This allows users to import it as:

import Foo from "./module";

In my view, using default export makes sense when there is only one item to export - why wrap it in an object? If there are multiple items to export, they should be exported individually for flexible imports using { ... }.

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 utilization of React.Component inheritance in TypeScript

In my attempt to develop a base class using React.Component and derive two classes from the base class, I have encountered an issue. Here is how I structured the classes: interface BaseItemProps { base_prop: string; } class BaseItem<P, T> exten ...

Ways to simulate a variable imported in the module being tested without it being a function parameter can be achieved by using describe.each and changing the mock value for each test

I have a requirement to test a function within my TypeScript module. module-to-test.ts import { config } from './app-config'; export const isSomethingWhatINeedSelector = createSelector( firstDependencySelector, secondDependencySelector ...

A guide to finding the mean in Angular by utilizing JSON information

import { Component, OnInit } from "@angular/core"; import { MarkService } from "../app/services/marks.service"; @Component({ selector: "app-root", templateUrl: "./app.component.html", styleUrls: ["./app.component.scss"] }) export class AppComp ...

I'm having trouble figuring out the right folder organization for the custom typings specific to this module. Can you provide

I have developed custom type definitions for a module called @xmpp/client. Integrating these types into my TypeScript project has been challenging, as the compiler fails to recognize them. My typings are stored in a directory named types, resulting in the ...

Performing Jasmine unit testing on a component that relies on data from a service, which itself retrieves data from another service within an Angular 2+ application

Attempting to implement unit testing for a service using httpmock has been challenging. The service in question utilizes a method to make http get calls, but I have encountered difficulties in writing the test cases. saveservice.service.ts -- file const ...

Resetting the member variable value to zero

While running through the test, I noticed that the count variable in the stack1 class resets back to 0 when using the pop function. It's strange because during the push loop, the count increases as expected. However, when pop is called, the count rese ...

Comprehending Angular 5's Importing External Classes

I have developed a class structure for managing Schedules. I then brought in this class to my primary program. My issue arises from the fact that this class utilizes the HttpClient, which I was instructed to set up within the constructor. As a result, when ...

Intermittent issue with Angular 2 encountered while following the Hero Editor tutorial on angular.io

I am encountering an occasional error in the console while following the angular.io tutorial using Mozilla Firefox. The error does not seem to impact the functionality or rendering of my application, and it only happens sporadically. If you could provide ...

My app's custom barrel configurations don't appear to be functioning properly in Angular 2's system-config.ts

My system-config.ts file is as follows: 'use strict'; // SystemJS configuration file, for more information visit: // https://github.com/systemjs/systemjs // https://github.com/systemjs/systemjs/blob/master/docs/config-api.md /***************** ...

Is it Observable or Not? Typescript Typehint helping you determine

I'm facing an issue while writing a function with correct typehints. It seems to work fine with explicit type hinting but breaks down when trying to use automatic type hinting. Can someone please help me identify the error in my code and suggest how I ...

Do I have to wait for promises to be resolved or does expect(...).toBe handle it automatically?

Testing with Protractor can be quite challenging and confusing, especially for those new to it. I currently have the following setup: `SomeTestFile.spec.ts` describe('A test: ', () => { beforeEach(() => { .... } it (& ...

Count duplicated values in an array of objects using JavaScript ES6

I am working on creating a filter for my list of products to count all producers and display them as follows: Apple (3) I have managed to eliminate duplicates from the array: ["Apple", "Apple", "Apple"] using this helpful link: Get all non-unique values ...

Converting Vue 3 refs to different types using instanceof in Typescript

When working with Vue using the Options API, here is a code snippet I have: <script lang="ts"> import * as THREE from 'three'; export default { mounted() { console.assert(this.$refs.container instanceof HTMLCanvasElem ...

What is the best way to create a React component that renders a class component as a functional component?

My Objective: At the moment, I am in the process of developing an AuthUserRole HOC component to manage user roles like Manager and Employee. However, I encountered a tutorial that uses a functional component to return a class component as referenced here. ...

Determine the generic parameter of the output type by analyzing the resolved value of a data type within the function

I am looking to automatically determine the generic parameter of the return type by utilizing the resolved value of a type within the function. Consider the following: export type Context = any; export type Handler<T> = (ctx: Context) => Promise& ...

Can [] be considered a valid type in Typescript language?

I've come across this function: function stringToArray(s: string|[]): [] { return typeof s === 'string' ? JSON.parse(s.replace(/'/g, '"')) : s; } This function is functioning as expected without any type warnings. Bu ...

typescriptIs there a more efficient approach to typing optional values without a default value in

In my React application with TypeScript, I am using the following code to provide typed props: type ScheduleBoxContentProps = { desc: ReactNode, lottie: LottieProps, } & Partial<{className: string}>; I want the className prop to be optional ...

Utilize Array in Form Input with Index and Spread Operator

Looking to create a form that can handle array data with dynamic fields in TypeScript. Encountering the following error: Element implicitly has an 'any' type because expression of type 'any' can't be used to index type '{ nam ...

The path parameter in tsconfig and ESLint_integration

Setting up the paths option in my tsconfig.json file has been mostly successful. My tests run smoothly, and my program executes without issues. However, I am encountering a problem with ESLint not finding modules accessed through one of the paths specified ...

Error in TypeScript: Module 'stytch' and its corresponding type declarations could not be located. (Error code: ts(2307))

I'm currently developing a Next.js application and encountering an issue while attempting to import the 'stytch' module in TypeScript. The problem arises when TypeScript is unable to locate the module or its type declarations, resulting in t ...