2

I want all the classes to be available under a namespace in a library-based project.

I'm missing a step when it comes to putting all classes under a namespace.

Assume i have two classes in two folders

  1. The first step is to export all of them under a library name or namespace
  2. The second step is to consume them in a test folder by accessing classes through their library name or namespace

Expectation/Question: To be able to access and consume classes through a common library name, for instance new mylib.Foo() or new mylib.Bar()

Definition

src/a/Foo.ts

export module mylib {
    export class Foo {
    }
}

src/b/Bar.ts

export module mylib {
    export class Bar {
    }
}

src/index.ts

import { Foo } from './a/Foo'
import { Bar } from './b/Bar'

export { Foo, Bar }

Testing

test/a/Foo.spec.ts

import { mylib } from "../src";

test("constructor"), () = {
   const foo = new mylib.Foo() // expectation
}

test/b/Bar.spec.ts

test("constructor"), () = {
   const bar = new mylib.Bar() // expectation
}
4
  • It's hard to answer a post that does not include a question. Please edit and clarify what you want to achieve. Commented Dec 3, 2023 at 13:54
  • please see Expectation/Question Commented Dec 3, 2023 at 13:55
  • Is it important to you to use the TypeScript module syntax, specifically, as opposed to modern standard JavaScript module syntax? (You're mixing the two above currently.) Commented Dec 3, 2023 at 14:03
  • 1
    I'm not fixated on any syntax; modern and standard are better. Just being able to package and consume classes (separate files) through a namespace would be sufficient. As long as library is usable for web/react and react-native. Commented Dec 3, 2023 at 14:04

1 Answer 1

1

Assuming you just want the end result you've asked about and aren't specifically trying to use TypeScript's module syntax, you can simplify a bit and then have a couple of options.

The simplification is that you don't need those export module mylib { /* ... */ } wrappers in Foo.ts and Bar.ts, just:

export class Foo
    // ...
}

and

export class Bar
    // ...
}

Then it becomes a question of how you want those imported from index.ts.

Module namespace object

What you have now in index.ts is actually just fine for one way it could be used: It creates two named exports, Foo and Bar. If you want to write code using those via a mylib object, you can create that on import:

import * as mylib from "../src";
//     ^^^^^^^^^^

That import syntax says "Import the module namespace object for the module and assign it to a binding called mylib." If the module namespace object doesn't already exist for that module, it'll be created.

Usage once it's imported is as shown in the question, mylib.Foo etc.

But doing it that way, it's also possible to use import { Foo } from "../src"; and/or import { Bar } from "../src";. Normally that's a good thing, but if you don't want to allow that, you won't want to do individual named exports.

Note that import * as mylib will defeat tree-shaking, if that's important to what you're doing. (Which is part of why import { Foo } and such are normally a good thing.) But it's the person using your library who makes that decision; your library doesn't prevent tree-shaking by itself.

Export an object

If you don't want to do the module namespace object approach above, you could export an object from index.ts:

export const mylib = { Foo, Bar };

...or perhaps export it frozen so properties can't be added, removed, or reassigned:

export const mylib = Object.freeze({ Foo, Bar });

Then import is as you've shown it in the question:

import { mylib } from "../src";

And usage is mylib.Foo etc.

Beware that this defeats tree-shaking, and not just based on how users use it, but at the library level (by not having Foo and Bar exports people could use instead). I wouldn't recommend it.

Both

Finally, you could support both named individual exports and a single named object export. In that case, the exports in index.ts would look like this:

export { Foo, Bar };
export const mylib = Object.freeze({Foo, Bar});

If you do that, you leave it up to people using the code whether to use import { Foo } or import { mylib }. That keeps the library friendly to tree-shaking, while also providing a named export for the library as a whole if that's useful.

Sign up to request clarification or add additional context in comments.

12 Comments

Thanks; this is working. Question: Is this a universal solution for all environments? (TypeScript, Web JS, React and React Native)? I'm relatively new to TypeScript.
@Source - Yes. JavaScript's native module format (sometimes called "ESM" for ECMAScript Modules) is directly supported by all modern browsers, recent Node.js versions, Deno, and Bun. But on top of that, TypeScript can output a different module format if necessary, so if you needed to support obsolete environments that didn't have ESM, you could do that too. See the documentation on the module option for details.
does it work with interfaces? I also defined interfaces, for example 'export interface IFoo{}, I have imported IFoo from the file but export const mylib = {IFoo, Foo};` doesn't like it, IFoo is highlighted red in my IDE.
@Source - Right. You include a types key in exports in your package.json when publishing that points to the types (as I understand it -- beware I've never published a library on npm). You might look at how Preact does it, since unlike React, Preact provides their own types for their lib. (React doesn't; its types are "aftermarket" if you will, from Definitely Typed rather than from the React team).
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.