Export module SomeModuleName {..}

I have a question about the module keyword in JavaScript.

I am familiar with:

export { name1, name2, …, nameN };
export expression;
export function;
export const
..

But what is the significance of export module SomeModuleName {..} in JavaScript What is it used for and what are its advantages? I am considering if this pattern is helpful to learn.

In my case, I have a folder in my project called api, in which I keep two big files, namely rest.class.ts and stream.class.ts. It seems reasonable to me to consider these as part of a bigger “ApiModule”. So what I tried was create another file called api-module.ts.

So the file structure that I have looks like this:

api (folder)

  • stream.class.ts
  • rest.class.ts
  • api.module.ts

In the api.module.ts file I did the following:

import { RestClient } from "@api/classes/rest-client.class";
import { StreamClient } from "@api/classes/stream-client.class";

export module ApiModule {
    export const getRestInstance = RestClient.getInstance;
    export const getStreamInstance = StreamClient.getInstance;
}

In thise case, getInstance are public static members of the respective classes.

Doing this seems logical to me in semantic and intuitive terms. But my question is: what have I achieved by doing this? I could have done the same thing in many other ways.

It really begs the question of the special properties of the module keyword. I feel that I am not using all of its powers but I have not found a good guide/explanation or online documentation on how it is supposed to be used. And most libraries I have seen simply use an index.js file to bundle imports.

Can someone explain all of this to me?

Thank you so much!

2 Likes

You’ve organized everything into a logical file structure that separates concerns, keeps your file size smaller, makes it easy to find what you need… Isn’t that enough? I once worked on a project with over 100 modules. Things like that take some organization.

I feel that I am not using all of its powers but I have not found a good guide/explanation or online documentation on how it is supposed to be used.

It’s largely for organization, but also to “hide” details. Some file that is consuming your stream doesn’t want or need to know what is happening internally in the module. It doesn’t and shouldn’t care. Most people don’t know how a microwave does. They just know the interface - I put something inside, hit these buttons, out comes hot food.

And most libraries I have seen simply use an index.js file to bundle imports.

Yeah, I’m not a big fan of what you’ve done - it’s not terrible, I just don’t like renaming things too much. I would much rather import RestClient into the file and use it there, calling the method getInstance. Adding another name into the “mental name space” of getRestInstance is just going to get mentally taxing to me.

Personally, I’m of mixed opinions on the “index.js” thing. It just makes for less typing, but you end up with a project with 729 files called “index.js”. But it is a common pattern (and I do use it sometimes).

Oh wait, now I see that you’re thinking about API as the module and the other two as classes. Personally, I would think of each of those classes as modules, each having their own folders and support files, etc. Then I’d have a file like modules/index.ts that imports and exports those modules. I don’t know if there is any big advantage to grouping the rest and stream together. To me they are separate things. But maybe you know your code better than I do.

1 Like

Ultimately, just do what makes sense to you. Try to learn from others and try to learn from your mistakes. Sometimes you have to make mistakes to truly learn something. And sometimes those mistakes bear fruit. Do what makes sense to you. And realize that what “makes sense to you” will evolve.

1 Like

Do you know what this line is?

What exactly is that setting ? As far as I know module is just an object, but OP uses the word “keyword” so I am not sure what that would be doing.

1 Like

Yeah, I missed that. “module” is not a keyword in JS, afaik.

2 Likes

I see, so the whole line isn’t meaningful to you either? I think it is just incorrect.

1 Like

Yeah, what @kevinSmith says, module isn’t a keyword, and this

export module ApiModule {
    export const getRestInstance = RestClient.getInstance;
    export const getStreamInstance = StreamClient.getInstance;
}

Isn’t valid JS, you can’t export an export, it doesn’t make sense.

In JS, a file is a module. You can export code from it (make it available for import in other modules), and you can import code from other modules. So it lets you organise code. So you might do something like, I dunno:

import { RestClient } from "./rest-client.js";
import { StreamClient } from "./stream-client.js";

export class Api {
  constructor(opts) {
    // initialise some common options
  }

  initialiseRestClient() {
    const client = new RestClient(this.opts);
    // Do some stuff
  }

  ....etc
}

Ah, right if you’re talking about what I think you’re talking about then there is a reason for this. If you have a folder which has an index.ts file in, Typescript allows you to import under the name of folder instead of importing "foldername/index".

So say you have a load of modules in a folder, like

example/
  - foo.ts
  - bar.ts
  - baz.ts
  - index.ts

In the index file you can re-export the other three files like

export * from "./foo";
export * from "./bar";
export * from "./baz";

(that just as an example, you could do other things in the index file like just export specific things or run some logic first or whatever).

Then in another file you can import like

import { fooFunction, barFunction, bazFunction } from "./example";

Rather than

import { fooFunction } from "./example/foo";
import { barFunction } from "./example/bar"; 
import { bazFunction } from "./example/baz";

This is inherited from Node (to allow Typescript and tooling to work in the same way), that’s how Node module resolution works.

It isn’t how standard Javascript modules work (file paths need to be fully qualified there), so although it’s often quite convenient it’s also a bit of a wart. For example, if I want to compile the TS to individual JS files that are modules, then making use of this pattern will result in broken code – when this gets compiled:

import { fooFunction, barFunction, bazFunction } from "./example";

It will compile unchanged, and there’s no a file called “example”, so the imports won’t work. Note that this problem is quite specific to compiling for modern, ESModule-based code – by default if you just run tsc on a load of code with default settings it’s going to bundle it all up into working JS just fine. But the JS ecosystem is gradually moving towards ESModules for everything, so this is likely to be more of a common issue, so IMO need to be a bit wary about using the pattern.


Also small thing re names:

stream.class.ts
rest.class.ts
api.module.ts

All of those are modules, so .module. is redundant. And whether stream and rest use classes is an implementation detail: they could not be classes and have essentially the exact same interface, it’s not important. stream, rest and api are better names

1 Like

Oh wait, now I see that you’re thinking about API as the module and the other two as classes. Personally, I would think of each of those classes as modules, each having their own folders and support files, etc. Then I’d have a file like modules/index.ts that imports and exports those modules. I don’t know if there is any big advantage to grouping the rest and stream together. To me they are separate things. But maybe you know your code better than I do.

Thank you for your answer Kevin!

I don’t know if this is a good way of doing it. There are many solutions that are much simpler and I would not have had to create this thread. However, I am trying to explore JS and TS and trying to learn new things.

But I think it could be useful to do a comparison on the various options, so below is a superficial one.

1. Consider the stream and rest classes as modules and simply import them as needed.

  • Advantages: simple, less abstraction, less code
  • Disadvantages: the only thing that itches me is the fact that those are class objects and there is an object from ES5 called module. My thinking is along the lines of: “surely there has to be a good reason for its existence”.

2. Use an index.ts file to export the two classes
Advantage:

  • simple, convenient, only one import statement in the target file

Disadvantage:

  • As you mentioned @kevinSmith, I could potentially litter my project with multiple index.ts files.

3. Create a file called api-module.ts or api.module.ts inside the api folder
Advantage:

  • Simple and convenient
  • Semantically correct

Disadvantage:

  • More names to keep track of

4. Create a file with a class object called Api as suggested by @DanCouper

Advantage:

  • Flexible i.e. can be done in many ways by e.g. creating public class members of RestClient and StreamClient, or extending the Api (parent class) class with both child classes.
  • The option can allow for further abstractions and adding e.g. various helper functions.

Disadvantage:

  • More code and abstraction can be confusing when multiple developers work on the project
  • Exporting a class that is exporting another class seems somewhat strange but works perfectly fine
  • More names to keep track of

5. Creating a module object as in the original post

Advantages

  • I have a feeling there is a good reason that this object exists in javascript, but I don’t know what, perhaps there are some features related to abstraction or controlling interface that can be helpful
  • Is semantically correct in my opinion, i.e., my folder is called Api and everyting within that folder should be exported as an ApiModule. Using module object somehow seems logical to me

Disadvantage

  • Further abstraction that is not entirely necessary
  • Maybe this is not at all what the module object is supposed to be used for and I might look like a fool for using it this way
  • More names to keep track of

6. Creating a file with a namespace called ApiModule or simply Api

Advantages

  • Is supposed to be simpler than using a module

Disadvantages

  • According to some StackOverflow statements read is supposed to provide less code isolation compared to a module (don’t ask me how).
  • Don’t know if it somehow appears in the global namespace, if so I don’t think is is a very good idea

I see, so the whole line isn’t meaningful to you either? I think it is just incorrect.

Thank you for correcting me. It is indeed a JS Object and I am still learning such simple things, hence why I made the mistake.

Thank you @DanCouper for your answer. This is also how I have seen module described but there are very few places (if any) that describe what the module object does and how it differs from files. Would it be correct to assume that file that is being exported and imported in JS is a module object?

Also small thing re names:
stream.class.ts
rest.class.ts
api.module.ts

All of those are modules, so `.module.` is redundant. And whether `stream` and `rest` use classes is an implementation detail: they could not be classes and have the exact same interface essentially, it’s not important. `stream`, `rest` and `api` are better names

Thank you for pointing this out to me, and I have been thinking about this a lot. I name them like this because I taught myself Angular and liked their style guide (see link below to the chapter on naming conventions).

I know this is perhaps only specific to Angular projects, but it somehow seems like a nice convention, although I can’t speak for the exact merits of doing it this way. One advantage on the to of my head is when searching for files, one can see at a glance what type of JS objects they contain.

For your reference, here is the Angular style guide: Angular

My current pattern is that I have folder called src/modules/LocalStorage where I have:

export * from './LocalStorage.types'
export * from './LocalStorage.hooks'

export { default } from './LocalStorage.module'

I get that some people might say that the module doesn’t need the extension “module”, but I like it to distinguish from other files I may have in there.

I don’t like putting an index.ts file in the src/modules folder to aggregate all the modules - I think it gets a little silly to have just one place to import everything. I’d rather import from the module that I need, keep aware of what the ultimate source is - that makes more sense to my brain.

I tend to use a default export if the file represents one thing, like the module. But the hooks and types folder will have several things so those are named exports.

But at some point it gets subjective and personal. Just use a system that is logical, easy to work with, and is consistent. Wherever you work will have their own way of doing it. Every place I’ve worked has been a little different. And I think part of learning this is experimenting and figuring out what works and what doesn’t and why. You can also spend some time cruising github and see what others have done.

This makes sense to me too. However, I don’t understand why I simply can’t do it in the class itself using the keywords public private static etc.?

Thank you Kevin, it is always useful to learn from other peoples experiences and personal preferences.

It is a thin line between personal preference and convention. You want to do what you believe is best. At the same time you want your project to be understood and liked by other developers who may have different preferences.

As you write, it seems to be that the general convention out there for Node.js projecs seems to be (based on me looking at GitHub projects and reading articles) to do it as you do it, namely to have src/modules/<list of modules grouped by functionality)

I am glad to hear that is how you do it also since I reorganized my project in the same way, a few weeks ago.

I keep the “bootstrapping” file (I call it main.ts) on the same level as the src folder, together with all the JS and TS configuration files.

There isn’t an object called module. There is a mechanism for organising and exporting/importing code between files (“modules”), and that mechanism kinda looks like and works in practice in a similar way to using objects as maps (if you squint). Under the hood, the interpreter constructs a record with data about the file (so, similar to how objects are often used in JS, as records, and in the JS code the syntax is similar to object code), but that’s an implementation detail.

I was only using that as an example.

If you have a file with a class that contains logic for a REST client, and a file with a class that contains logic for a stream client, and they are going to be used seperately, then you wouldn’t do what I suggested, there’s no point, just export them and the user can import them directly to where they are being used.

If it becomes onerous to keep writing import {example } from "./some/deeply/nested/path, then think about importing everything you want exposed into one common file and re-exporting from there – this is done not as a clever architectural pattern, but because if you have a project with a lot of files then it becomes a massive PITA to keep writing loads of import statements at the top of all your files.

If, on the other hand, you have some API that uses both client classes, or where, say, a user can pick one or the other, and where there is some common functionality that is backed by one or the other, then it makes sense to hide both of those clients and have a single API that the user interacts with. Then that is what gets exported because the two client modules aren’t something the user needs to care about.

You can’t make this decision as a general thing – it’s completely context sensitive, it’s entirely dependent upon what you want the public API to look like


I think you’re severely overthinking this. Modules exist to help organise your code. They are a way to logically group things that should be grouped together. If you weren’t to use modules then all that means is that everything would have to be in one big file, it’s almost literally as simple as that. Yes you can use modules in the same way as classes, some functionality overlaps. You can have non-exported values in the module, hidden, and just export functions or whatever that operate on them. But practically, the two things are orthoganal – it doesn’t make any difference if you have classes in files or not, those files are still modules.

Modules are just the standard, very highest level answer to “how do I break my entire program into manageable pieces”, they don’t in any way preclude using (in your case here) class-based, OO to organise parts of the program. Basically every language has modules bar [afaik] C (and that has header files which serve a similar purpose) – JS is a weird outlier in that it only got actual modules as part of the spec in 2015, but even before that almost every large JS codebase used module patterns to organise the code.

The likelihood of you needing to use namespaces is astronomically small because modules exist. Modules already provide the ability to group code – you just put code in a file, export the stuff that should be exposed, bang, it’s a module.

Thank you for taking your time to reply. But I am still slightly confused.

If I print the typeof the ApiModule it says it is an object. In the below example I have not exported it. My question is simply, is the below object considered a module or is that only reserved to files being exported?

The thing is when I read your answer and everything I found on the internet so far it appears that modules are only files that are being exported. But in the below example we have something we call module (the word before ApiModule, i.e. module ApiModule {..}). So I am trying to understand what the object does and how it differs from exporting a file.

I completely understand and agree that exported files are modules but what about my code below. Is that not also a module?

import { RestClient } from "@api/classes/rest-client.class";
import { StreamClient } from "@api/classes/stream-client.class";

export module ApiModule {
    export const getRestInstance = RestClient.getInstance;
    export const getStreamInstance = StreamClient.getInstance;
}

console.log(typeof ApiModule); //object

Interesting. When I put that code into my current environment, I get a sonarlint warning of:

Use ‘namespace’ instead of ‘module’ to declare custom TypeScript modules. sonarlint(typescript:S4156)

Looking into TS, I see that they do have something called modules.

But I think you are getting way ahead of yourself. You are taking an introductory flying lesson and are getting caught up in questions about how an F16 handles in high altitude dogfights.

I would just focus on simple JS modules for now. If I understand what you are trying to do, I would expect to see something like:

import { RestClient } from "@api/classes/rest-client.class";
import { StreamClient } from "@api/classes/stream-client.class";

export const ApiModule = {
  getRestInstance: RestClient.getInstance,
  getStreamInstance: StreamClient.getInstance,
}
1 Like

Oh I’ve just realised what you’re talking about, really sorry. I didn’t even realise the thing you’re talking about existed because wasn’t even aware of TS when it did [edit: beyond “it’s Microsoft’s extra stuff added to JS to make it more like C# that you get when you use Visual Studio”] (I’ve been a full-time developer for ~10 years, used TS for maybe 4 or 5 years??)

Don’t use this:

export module ApiModule
          ↑
   this here keyword

I’m not sure what documentation you’re looking at, but afaics it must be extremely old – there used to be a keyword called module in the very first versions of Typescript (and that the code you’ve written even compiles suggests that it can’t be removed due to backwards compatibility), but that was extremely confusing so it was renamed namespace.

From up the thread:

These two are literally the same thing.

You aren’t talking about modules, you’re talking about Typescript namespaces.

[Note: in following, when I write “module” I mean actual JS modules, not TS namespaces]

What namespaces do is pretty basic – they just wrap the code in another object (called whatever the namespace is called) and assign that to the global context.

As noted in the Typescript docs, this can be quite useful in a [front-end] web context, where there’s often just a single JS file all the Typescript code gets compiled to. And then that single file will be divided up into a set of objects (that get accessed like window.MyNamespace).

Basically, it’s what everyone had to do before actual JS modules existed. And before modules worked out-of-the-box in browsers (that’s only a very recent development, 99.9% of prod code is still bundled), that’s what code written using modules gets bundled to.

Because of this, there isn’t much point in using namespaces [outside of some very specific usecases I guess]. They existed before modules were really a thing. TS appeared in 2012, first-class support was in Visual Studio in 2013. The module spec was added to JS in 2015 (and the spec doesn’t specify how modules are loaded, so IRL it’s only really settling down now). But almost every usecase for namespaces is obseleted by modules. If you want to namespace some functionality, you can import * as SomeNamespace from "./some-file". And the graph of interconnected files is going to [depending on settings, assuming bundling] compile to something basically the same anyway.

Edit: and namespaces don’t have an obvious direct corollary in JS, which makes things confusing – they are specific to Typescript and have special rules (see also enums and public/private keywords in classes), unlike the majority of the rest of Typescript. They are things added to Typescript that now exist in a different form in JS (so they don’t compile 1-to-1, or, in the case of enums they are a thing that doesn’t exist in JS and has consequently has slightly wierd semantics). IMO I would always avoid using namespaces and public/private keywords (imo there’s no good reason to use them), and be very careful when using enums (again, avoiding unless necessary – string union types can normally replace them in most cases).

2 Likes

Thank you @DanCouper and @kevinSmith for your excellent answers. Before coming back to this thread I read a bit more and came to understand what you both wrote, that module is legacy and only works because of backwards compatibility issues. Nowadays it is namespace.

It took quite some time, but with your answers, I have convinced myself that neither module nor namespace is practical or useful. In 99.999% of the cases, modules with named imports will do exactly the same thing.

Yikes, this was much ado for nothing.

I will end with my takeaway from this thread by quoting @kevinSmith :

You are taking an introductory flying lesson and are getting caught up in questions about how an F16 handles in high altitude dogfights.

Thank you both, again, for your kind help!

1 Like

This topic was automatically closed 182 days after the last reply. New replies are no longer allowed.