Gabriel Marcondes
Gabriel Marcondes

Reputation: 1010

ES2015 module syntax is preferred over custom TypeScript modules and namespaces @typescript-eslint/no-namespace

I'm receiving the following error running npm start:

ES2015 module syntax is preferred over custom TypeScript modules and namespaces @typescript-eslint/no-namespace

    namespace InternalThings {...}

I tried to research this but it's very confusing.

Why does this is happening? How to fix it?

I tried to put some flags on my tsconfig.json but so far no success;

Upvotes: 78

Views: 96684

Answers (5)

Chen Peleg
Chen Peleg

Reputation: 2044

About using name spaces today (2024):

The Idea of that rule is that because of ES modules you don't need names spaces as much as before. That is true, for all of the examples above. There is one exception tough. If you want to wrap types and interfaces in some object, then using a name space is your only option. For example:

// eslint-disable-next-line @typescript-eslint/no-namespace
export namespace NameSpaceWithTypes {
  export type MyType = {...};
  export interface MyInterface {...}
}

Now, why would you use this? I use it if I have an app with tones of types, and some of them are "rare", so you can have a bit more "order", like this:

const someVariable : NameSpaceWithTypes.MyType = {...}

(the "order" thing is because you can't import MyType directly, you have to import the namespace)

but this is the only reason I can think of for using namespaces today.

Upvotes: 3

Nicholas Tower
Nicholas Tower

Reputation: 84922

This is a lint error, caused by this lint rule: https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/docs/rules/no-namespace.mdx

If you find the rule useful and want to keep it, then you'll need to modify your code to use import and export instead of namespace. See the documentation of the rule for what counts as a fix.

If you like the rule, but want to disable the rule for this line, add the following just above it:

// eslint-disable-next-line @typescript-eslint/no-namespace

If you don't like the rule and want to disable it entirely, then edit your .eslintrc file to have the following line:

rules: {
  "@typescript-eslint/no-namespace": "off"
}

Upvotes: 84

CTS_AE
CTS_AE

Reputation: 14813

Fix Lint Error While Maintaining Same API

If you would like to handle the lint error without breaking any current implementations you can do the following, but you should really look at the above answer before committing to this: https://stackoverflow.com/a/63574739/349659

Before

Implementation

export namespace Container {
  export function someCall() { }
  export function anotherCall() { }
}

Consumer

import { Container } from './Container'

Container.someCall()
Container.anotherCall()

After

Option 1

// These are essentially private
function someCall() { }
function anotherCall() { }

// We expose them here
// This feels like a step towards CommonJS, but is valid ES Module code
export const Container = {
  someCall,
  anotherCall,
}

Option 2

You could also define and encapsulate the function calls directly into the object as well like so:

export const Container = {
  someCall() {},
  anotherCall() {},
}

In Conclusion

If you have a large codebase and want to "quickly" appease your linter, you can do a refactor like above. Make sure to consider this answer https://stackoverflow.com/a/63574739/349659 and the reasoning behind it.

At the end of the day, the quickest fix that requires no code change is to simply turn off this linting rule as mentioned in this answer: https://stackoverflow.com/a/58271234/349659

If you're starting from scratch and run into this issue I would consider utilizing the modern implementation as the linter hints towards, but you may find that you enjoy namespaces and simply want them as well. If you're part of a team you may want to get their feedback first and and follow a team standard.


Edge Cases & Considerations

One case I've ran into is having multiple namespaces in the same file. In this scenario you may then have name collisions after removing the namespace.

Example

Before

export namespace Container {
  export function someCall() { }
  export function anotherCall() { }
}

export namespace AnotherContainer {
  export function someCall() { }
  export function anotherCall() { }
}

After

Renaming Collisions

In this scenario when you remove the namespace you can rename the collisions while maintaining the export like so:

function containerSomeCall() { }
function containerAnotherCall() { }

export const Container = {
  someCall: containerSomeCall,
  anotherCall: containerAnotherCall,
}

function anotherContainerSomeCall() { }
function anotherContainerAnotherCall() { }

export const AnotherContainer = {
  someCall: anotherContainerSomeCall,
  anotherCall: anotherContainerAnotherCall,
}
Decoupling the Code

Another option is to decouple them into their own files. If you want to maintain the exports of the original file though you will need to import and expose them which may seem duplicate, but may be an intermittent step towards a larger refactoring (later updating imports to point at the new files). This also allows you to start writing more modern ESM code too if you would like, while proxying new exports through the old module.

Container.ts

function someCall() { }
function anotherCall() { }

export const Container = {
  someCall,
  anotherCall,
}

AnotherContainer.ts

function someCall() { }
function anotherCall() { }

export const AnotherContainer = {
  someCall,
  anotherCall,
}

OriginalFile.ts

export * from './Container'
export * from './AnotherContainer'

We can proxy the new ESM modules through the old original module.

Upvotes: 14

Métoule
Métoule

Reputation: 14472

To fix this error, instead of:

export namespace InternalThings {
    export function myFunction() {
    }

    export class MyClass {
    }
}
import { InternalThings } from './internal-things';

InternalThings.myFunction();

you expose all the members of the namespace directly:

export function myFunction() {
}

export class MyClass {
}

and you import it like this:

import * as InternalThings from './internal-things';

InternalThings.myFunction();

The main idea is that users of your module can import only what they want, or name your module differently:

import * as CustomModuleName from './internal-things';

CustomModuleName.myFunction();
import { MyClass } from './internal-things';

let field = new MyClass();

Upvotes: 54

Sudhakar Ramasamy
Sudhakar Ramasamy

Reputation: 1759

The error is coming from eslint. You have to either ignore '@typescript-eslint/no-namespace' rule in the config or rewrite your code using ES6.

Custom TypeScript modules (module foo {}) and namespaces (namespace foo {}) are considered outdated ways to organize TypeScript code. ES2015 module syntax is now preferred (import/export)

Refer https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-namespace.md

Upvotes: 3

Related Questions