How to best emulate namespaces in Typescript?

282 views Asked by At

I realize that namespaces are essentially deprecated in ES6 and don't work well in Visual Code.

But I still want to use them. Why? Because I have code spread across multiple files that I want to group without creating dependencies between them.

Here's the use case. I have two different stores in separate files:

   // fooStore.ts
   class FooStore { numFoos = 0; }

   export const fooStore = new FooStore();

and

   // barStore.ts
   class BarStore { numBars = 0; }

   export const barStore = new BarStore();

For discoverability purposes, I would like to group these stores together so a developer could refer to stores.fooStore and stores.barStore.

One solution is to export an object that contains them both, like this:

export const stores = {
  fooStore,
  barStore
};

This works, but it has a big downside. If any code refers to stores. then all referenced stores and all of their dependencies are pulled in. Why is this a problem? Because I am working in a codebase that is a combination of both AngularJS classes (legacy code) and React/MobX code (what we're porting to), and some of our stores use AngularJS. When I write a unit test or Storybook story, I don't want to have to set up all the necessary dependencies for stores that I'm not even interested in.

What I would really like to do is something like this, but I understand it's not advised. So what should I do instead?

 // fooStore.ts
 class FooStore { numFoos = 0; }

 namespace Stores {
   export const fooStore = new FooStore();
}
2

There are 2 answers

2
Bergi On BEST ANSWER

One solution is to export an object that contains them

Yes, that'll cause problems since it requires the dependencies to be pulled in, to put the store instance in your object. Instead, use a module with named exports for this:

// stores/index.ts
export { fooStore } from './fooStore.ts';
export { barStore } from './barStore.ts';

Then you can use a namespace import to group them:

import * as stores from 'stores';

console.log(stores.fooStore.numFoos);

This will still permit tree shaking - the above module doesn't refer to stores.barStore, so that module doesn't need to be loaded (even if it is declared as a dependency).

0
klhr On

I can think of 2 mediocre ways to make this happen, but they both have drawbacks:

1. Use a lazy getter to load the store at the time it's accessed.

const stores = {
  get fooStore () {
    return require("path/to/fooStore");
  }
}

Dynamic requires like this may play poorly with your build step.

2. Have stores register themselves with the Store of Stores on initialization:

export const stores = {};
export class Store {
  constructor(storeName) {
    stores[storeName] = this;
  }
}

This does require loading the FooStore file before it will be usable on the Store of Stores.